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

@ -11,20 +11,20 @@
#
ifeq ($(MODE),)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
CONFIG_CPPFLAGS += -DSYSDEBUG
TARGET_ARCH ?= -msse3
endif
ifeq ($(MODE), x86_64)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
CONFIG_CPPFLAGS += -DSYSDEBUG
endif
ifeq ($(MODE), aarch64)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
CONFIG_CPPFLAGS += -DSYSDEBUG
endif
@ -38,13 +38,13 @@ endif
#
ifeq ($(MODE), zero)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
OVERRIDE_CFLAGS += -O0
OVERRIDE_CXXFLAGS += -O0
CONFIG_CPPFLAGS += -DSYSDEBUG
endif
ifeq ($(MODE), aarch64-zero)
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
OVERRIDE_CFLAGS += -O0 -fdce
OVERRIDE_CXXFLAGS += -O0 -fdce
CONFIG_CPPFLAGS += -DSYSDEBUG
@ -81,7 +81,7 @@ endif
#
ifeq ($(MODE), opt)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG
CONFIG_CCFLAGS += $(BACKTRACES) -O3 -fmerge-all-constants
TARGET_ARCH ?= -march=native
@ -98,7 +98,7 @@ endif
# - Turns off support for other operating systems
#
ifeq ($(MODE), optlinux)
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
CONFIG_COPTS += -mred-zone
@ -140,7 +140,7 @@ endif
#
ifeq ($(MODE), asan)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CPPFLAGS += -D__SANITIZE_ADDRESS__
CONFIG_CCFLAGS += $(BACKTRACES) -O2 -DSYSDEBUG
CONFIG_COPTS += -fsanitize=address
@ -160,7 +160,7 @@ endif
#
ifeq ($(MODE), dbg)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_ADDRESS__ -D__SANITIZE_UNDEFINED__
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline
CONFIG_COPTS += -fsanitize=address -fsanitize=undefined
@ -170,7 +170,7 @@ QUOTA ?= -C64 -L300
endif
ifeq ($(MODE), aarch64-dbg)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline -fdce
CONFIG_COPTS += -fsanitize=undefined
@ -189,7 +189,7 @@ endif
#
ifeq ($(MODE), sysv)
ENABLE_FTRACE = 1
CONFIG_OFLAGS ?= -g
CONFIG_OFLAGS ?= -g -ggdb
CONFIG_CCFLAGS += $(BACKTRACES) -O2
CONFIG_CPPFLAGS += -DSYSDEBUG -DSUPPORT_VECTOR=121
TARGET_ARCH ?= -msse3

View file

@ -147,6 +147,13 @@ o/$(MODE)/libc/calls/pledge-linux.o: private \
-fPIC \
-ffreestanding
# we want -Os because:
# it makes a big difference
# it gets called very rarely
o/$(MODE)/libc/calls/sigcrashsig.o: private \
CFLAGS += \
-Os
# these assembly files are safe to build on aarch64
o/$(MODE)/libc/calls/getcontext.o: libc/calls/getcontext.S
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $<

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);

View file

@ -1,6 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_
#include "libc/calls/struct/sigset.h"
#include "libc/nt/struct/ntexceptionpointers.h"
#include "libc/thread/posixthread.internal.h"
#define SIG_HANDLED_NO_RESTART 1
@ -17,11 +18,12 @@ extern struct Signals __sig;
bool __sig_ignored(int);
int __sig_check(void);
int __sig_crash_sig(struct NtExceptionPointers *, int *);
int __sig_get(sigset_t);
int __sig_kill(struct PosixThread *, int, int);
int __sig_mask(int, const sigset_t *, sigset_t *);
int __sig_relay(int, int, sigset_t);
int __sig_raise(int, int);
int __sig_get(sigset_t);
int __sig_relay(int, int, sigset_t);
void __sig_delete(int);
void __sig_generate(int, int);
void __sig_init(void);

107
libc/calls/sigcrashsig.c Normal file
View file

@ -0,0 +1,107 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/sig.internal.h"
#include "libc/intrin/pushpop.internal.h"
#include "libc/nt/enum/signal.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/struct/ntexceptionpointers.h"
// this is all a mandatory dependency of winmain
// so, we trade away maintanibility for tininess
// see libc/sysv/consts.sh for canonical magnums
#define SIGILL_ pushpop(4)
#define SIGTRAP_ pushpop(5)
#define SIGABRT_ pushpop(6)
#define SIGFPE_ pushpop(8)
#define SIGSEGV_ pushpop(11)
#define SIGSYS_ pushpop(31)
#define TRAP_BRKPT_ pushpop(1)
#define ILL_ILLOPC_ pushpop(1)
#define ILL_PRVOPC_ pushpop(5)
#define SEGV_MAPERR_ pushpop(1)
#define SEGV_ACCERR_ pushpop(2)
#define SI_USER_ pushpop(0)
#define FPE_FLTDIV_ pushpop(3)
#define FPE_FLTOVF_ pushpop(4)
#define FPE_INTOVF_ pushpop(2)
#define FPE_FLTUND_ pushpop(5)
#define FPE_FLTRES_ pushpop(6)
#define FPE_FLTINV_ pushpop(7)
#define SI_KERNEL_ 0x80
textwindows 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_;
}
}

View file

@ -18,6 +18,7 @@
*/
#include "libc/assert.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/weaken.h"
@ -34,6 +35,7 @@
#include "libc/nt/pedef.internal.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/signals.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/memtrack.internal.h"
@ -49,6 +51,7 @@
#define abi __msabi textwindows dontinstrument
// clang-format off
__msabi extern typeof(AddVectoredExceptionHandler) *const __imp_AddVectoredExceptionHandler;
__msabi extern typeof(CreateFileMapping) *const __imp_CreateFileMappingW;
__msabi extern typeof(DuplicateHandle) *const __imp_DuplicateHandle;
__msabi extern typeof(FreeEnvironmentStrings) *const __imp_FreeEnvironmentStringsW;
@ -153,6 +156,11 @@ static bool32 HasEnvironmentVariable(const char16_t *name) {
return __imp_GetEnvironmentVariableW(name, buf, ARRAYLEN(buf));
}
static abi unsigned OnWinCrash(struct NtExceptionPointers *ep) {
int code, sig = __sig_crash_sig(ep, &code);
TerminateThisProcess(sig);
}
// main function of windows init process
// i.e. first process spawned that isn't forked
static abi wontreturn void WinInit(const char16_t *cmdline) {
@ -180,6 +188,9 @@ static abi wontreturn void WinInit(const char16_t *cmdline) {
}
}
// so crash signals can be reported to cosmopolitan bash
__imp_AddVectoredExceptionHandler(true, (void *)OnWinCrash);
// allocate memory for stack and argument block
_mmi.p = _mmi.s;
_mmi.n = ARRAYLEN(_mmi.s);

View file

@ -512,6 +512,8 @@ syscon compat SA_ONESHOT 0x80000000 0x80000000 4 4 4 4 4 0x800000
# The New Technology NT is polyfilled as Linux.
# Unsupported values are encoded as 0x80000000.
#
# NOTE: Some of these Windows constants are duplicated in sigcrashsig.c
#
# group name GNU/Systemd GNU/Systemd (Aarch64) XNU's Not UNIX! MacOS (Arm64) FreeBSD OpenBSD NetBSD The New Technology Commentary
syscon sicode SI_USER 0 0 0x010001 0x010001 0x010001 0 0 0 # sent by kill(2); openbsd defines si_code<=0 as originating from user
syscon sicode SI_QUEUE -1 -1 0x010002 0x010002 0x010002 -2 -1 -1 # sent by sigqueue(2)