-
Notifications
You must be signed in to change notification settings - Fork 18k
Cannot longjmp from signal handler to recover from SIG{SEGV,ILL,...} on macOS #44501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Try setting (I'm not sure this kind of use of signal handler are supported by the Go runtime.) |
Thanks for the tip! I also realized when testing that that I was testing the wrong program from earlier so I edited the description to say that Testing out In that case is this something that's officially unsupported? Do we have no recourse to implement this form of a WebAssembly runtime in Go? Or are there perhaps other sycalls/etc we can do to protect against this? |
Thanks for the reply. Good to know it works.
By "this", you mean For the latter, I actually don't know. The Go runtime internally does a lot of stack switches. It would not be surprising if another context switch mechanism (e.g. setjmp/longjmp) works together with it. It may work, or not, I don't know. If it can be supported without much extra complexity, I think we could probably support it.
I guess you could block preemption signal (SIGURG) in the code. Perhaps other signals as well, e.g. profiling signals (SIGPROF). |
Ah yes sorry to clarify I mean the longjmp or siglongjmp-from-signal-handler and whether that's supported. I'd personally like to know more about it and what's causing the crash and what we can do to prevent it. While we could block some signals on entry/exit into wasm it seems better to figure out a more precise solution if possible. For example why doesn't this crash happen on Linux when it happens on macOS? |
(Without looking into the detail) My guess it could be due to something weird of the longjmp and sigaltstack implementation on macOS (possibly bug). It seems the kernel delivers the signal on the user stack while we have sigaltstack set and the signal handler is registered with SA_ONSTACK. I can reproduce this in C.
On macOS,
At the first iteration the signal is delivered on the sigaltstack, but afterwards it is delivered on the main stack. Whereas on Linux,
The signal is always delivered on the sigaltstack. The Go runtime requires signals be delivered on the sigaltstack, and it crashes when it is delivered on the user stack. |
Oh dear, signals not being delivered on the sigaltstack does indeed sound like a bug in macOS itself! Either that or intended behavior of sigsetjmp/siglongjmp, unsure. I'd imagine that explains at least some of this, but if |
According to macOS's published sources That at least explains the problem with using Implementing that technique appears to fix the issue with Go as well, I'm unable to see any more runtime errors. Notably with this lib.c#include <assert.h>
#include <setjmp.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#if 1
#define platform_jmp_buf sigjmp_buf
#define platform_setjmp(a) sigsetjmp(a, 0)
#define platform_longjmp(a, v) siglongjmp(a, v)
#else
#define platform_jmp_buf jmp_buf
#define platform_setjmp(a) setjmp(a)
#define platform_longjmp(a, v) longjmp(a, v)
#endif
struct sigaction PREV;
static int MY_TRAP = 0;
static platform_jmp_buf JMP;
static void do_jump(void) {
platform_longjmp(JMP, 1);
assert(0);
}
static void signal_handler(int signal, siginfo_t *info, void *data) {
if (MY_TRAP) {
MY_TRAP = 0;
((ucontext_t*)data)->uc_mcontext->__ss.__rip = (uint64_t) do_jump;
((ucontext_t*)data)->uc_mcontext->__ss.__rsp -= 8;
return;
}
// If we don't handle this delegate to the previous handler.
if (PREV.sa_flags & SA_SIGINFO) {
PREV.sa_sigaction(signal, info, data);
} else if (PREV.sa_handler == SIG_DFL || PREV.sa_handler == SIG_IGN) {
sigaction(signal, &PREV, NULL);
} else {
PREV.sa_handler(signal);
}
}
void setup_signal_handler(void) {
struct sigaction handler;
handler.sa_sigaction = signal_handler;
handler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
sigemptyset(&handler.sa_mask);
int rc = sigaction(SIGSEGV, &handler, &PREV);
assert(rc == 0);
}
void do_trap(void) {
assert(MY_TRAP == 0);
if (platform_setjmp(JMP)) {
assert(MY_TRAP == 0);
} else {
MY_TRAP = 1;
*(int*) 3 = 5;
assert(0);
}
} While it still seems odd that the usage of |
Calling I'm going to close this issue because I think the request is impossible to implement in Go. Please comment if you disagree. |
I don't think it's correct to say that This is not an issue about Go trying to FWIW I'm having pretty poor experience reporting issues here having them be quickly deemed not bugs in Go. I tried to do my fair share in reducing, investigating, and documenting how to reproduce this issue. I did so in a different issue which was also deemed not a bug in Go, then was later fixed. I feel like I'm just being brushed off here as if I can't possibly know what I'm doing with signals, and I'm not really sure how to productively work with that. |
My apologies for my incorrect assumption. I jumped from the fact that setting Still, this doesn't seem to be a bug in the Go runtime, and I don't see anything that the Go runtime could do to fix it. |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes (downloaded 1.16 from the weebsite)
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
Given these files:
`go.mod`
`main.go`
`lib.h`
`lib.c`
I compiled this via
go build
. I then ran the resulting binary with./gorepro
. I changed the#if 0
to#if 1
as well and looked at the results.What did you expect to see?
I expected both binaries to succeed, regardless of
#if 0
or#if 1
What did you see instead?
The Go binary would also crash eventually (sometimes taking more time than others). The
#if 1
case usingsigsetjmp
to recover would often crash much faster than the#if 0
case. In the#if 0
case, however, this wouldstill eventually crash.Here's an example of the crashes:
`#if 0` - using `setjmp`
`#if 1` - using `sigsetsjmp`
In Go 1.15 I saw different crashes withsigsetjmp
than withsetjmp
, but in Go 1.16 it looks like both crash in the same manner.edit: turns out I was testing the wrong binary,
sigsetjmp
andsetjmp
do indeed have separate crash signatures.For some background this was originally reported as bytecodealliance/wasmtime-go#60. Wasmtime is a WebAssembly runtime where WebAssembly traps translate to
ud2
on x86_64 platforms, raising a SIGILL. We were seeing trouble after recently switching fromsetjmp
tosigsetjmp
but I've seen crashes for quite some time even usingsetjmp
(as seen here). I've tried to reduce this to not having a whole WebAssembly runtime and instead just having one C file to poke around. The C mirrors what the runtime currently does in a rough manner.The text was updated successfully, but these errors were encountered: