Improve crash signal reporting on Windows

This change fixes a bug where exiting a crash signal handler on Windows
after adding the signal to uc_sigmask, but not correcting the CPU state
would cause the signal handler to loop infinitely, causing process hang

Another issue is that very tiny programs, that don't link posix signals
would not have their SIGILL / SIGSEGV / etc. status reported to Cosmo's
bash shell when terminating on crash. That's fixed by a tiny handler in
WinMain() that knows how to map WIN32 crash codes to the POSIX flavors.
This commit is contained in:
Justine Tunney 2024-05-30 13:42:59 -07:00
parent 500a47bc2f
commit cd672e251f
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
7 changed files with 169 additions and 88 deletions

View file

@ -301,7 +301,7 @@ static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
}
// sends signal to another specific thread which is ref'd
static int __sig_killer(struct PosixThread *pt, int sig, int sic) {
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
unsigned rva = __sighandrvas[sig];
unsigned flags = __sighandflags[sig];
@ -462,66 +462,7 @@ textwindows void __sig_generate(int sig, int sic) {
ALLOW_SIGNALS;
}
static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
switch (ep->ExceptionRecord->ExceptionCode) {
case kNtSignalBreakpoint:
*code = TRAP_BRKPT;
return SIGTRAP;
case kNtSignalIllegalInstruction:
*code = ILL_ILLOPC;
return SIGILL;
case kNtSignalPrivInstruction:
*code = ILL_PRVOPC;
return SIGILL;
case kNtSignalInPageError:
case kNtStatusStackOverflow:
*code = SEGV_MAPERR;
return SIGSEGV;
case kNtSignalGuardPage:
case kNtSignalAccessViolation:
*code = SEGV_ACCERR;
return SIGSEGV;
case kNtSignalInvalidHandle:
case kNtSignalInvalidParameter:
case kNtSignalAssertionFailure:
*code = SI_USER;
return SIGABRT;
case kNtStatusIntegerOverflow:
*code = FPE_INTOVF;
return SIGFPE;
case kNtSignalFltDivideByZero:
*code = FPE_FLTDIV;
return SIGFPE;
case kNtSignalFltOverflow:
*code = FPE_FLTOVF;
return SIGFPE;
case kNtSignalFltUnderflow:
*code = FPE_FLTUND;
return SIGFPE;
case kNtSignalFltInexactResult:
*code = FPE_FLTRES;
return SIGFPE;
case kNtSignalFltDenormalOperand:
case kNtSignalFltInvalidOperation:
case kNtSignalFltStackCheck:
case kNtSignalIntegerDivideByZero:
case kNtSignalFloatMultipleFaults:
case kNtSignalFloatMultipleTraps:
*code = FPE_FLTINV;
return SIGFPE;
case kNtSignalDllNotFound:
case kNtSignalOrdinalNotFound:
case kNtSignalEntrypointNotFound:
case kNtSignalDllInitFailed:
*code = SI_KERNEL;
return SIGSYS;
default:
*code = ep->ExceptionRecord->ExceptionCode;
return SIGSEGV;
}
}
static char *__sig_stpcpy(char *d, const char *s) {
static textwindows char *__sig_stpcpy(char *d, const char *s) {
size_t i;
for (i = 0;; ++i) {
if (!(d[i] = s[i])) {
@ -530,8 +471,24 @@ static char *__sig_stpcpy(char *d, const char *s) {
}
}
static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
struct CosmoTib *tib) {
static textwindows wontreturn void __sig_death(int sig, const char *thing) {
#ifndef TINY
intptr_t hStderr;
char sigbuf[21], s[128], *p;
hStderr = GetStdHandle(kNtStdErrorHandle);
p = __sig_stpcpy(s, "Terminating on ");
p = __sig_stpcpy(p, thing);
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
p = __sig_stpcpy(p,
". Pass --strace and/or ShowCrashReports() for details.\n");
WriteFile(hStderr, s, p - s, 0, 0);
#endif
__sig_terminate(sig);
}
static textwindows void __sig_unmaskable(struct NtExceptionPointers *ep,
int code, int sig,
struct CosmoTib *tib) {
// log vital crash information reliably for --strace before doing much
// we don't print this without the flag since raw numbers scare people
@ -549,17 +506,7 @@ static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
// exception, then print a friendly helpful hint message to stderr
unsigned rva = __sighandrvas[sig];
if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN) {
#ifndef TINY
intptr_t hStderr;
char sigbuf[21], s[128], *p;
hStderr = GetStdHandle(kNtStdErrorHandle);
p = __sig_stpcpy(s, "Terminating on uncaught ");
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
p = __sig_stpcpy(
p, ". Pass --strace and/or ShowCrashReports() for details.\n");
WriteFile(hStderr, s, p - s, 0, 0);
#endif
__sig_terminate(sig);
__sig_death(sig, "uncaught ");
}
// if this signal handler is configured to auto-reset to the default
@ -580,8 +527,9 @@ static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
}
// call the user signal handler
// with a temporarily replaced signal mask
// and a modifiable view of the faulting code's cpu state
// temporarily replace signal mask while calling crash handler
// abort process if sig is already blocked to avoid crash loop
// note ucontext_t is a hefty data structures on top of NtContext
ucontext_t ctx = {0};
siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr};
@ -591,6 +539,10 @@ static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
memory_order_acquire);
if (ctx.uc_sigmask & (1ull << (sig - 1))) {
__sig_death(sig, "masked ");
__sig_terminate(sig);
}
__sig_handler(rva)(sig, &si, &ctx);
atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask,
memory_order_release);