Skip to content

Commit cd672e2

Browse files
committed
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.
1 parent 500a47b commit cd672e2

File tree

7 files changed

+169
-88
lines changed

7 files changed

+169
-88
lines changed

build/config.mk

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@
1111
#
1212
ifeq ($(MODE),)
1313
ENABLE_FTRACE = 1
14-
CONFIG_OFLAGS ?= -g
14+
CONFIG_OFLAGS ?= -g -ggdb
1515
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
1616
CONFIG_CPPFLAGS += -DSYSDEBUG
1717
TARGET_ARCH ?= -msse3
1818
endif
1919
ifeq ($(MODE), x86_64)
2020
ENABLE_FTRACE = 1
21-
CONFIG_OFLAGS ?= -g
21+
CONFIG_OFLAGS ?= -g -ggdb
2222
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
2323
CONFIG_CPPFLAGS += -DSYSDEBUG
2424
endif
2525
ifeq ($(MODE), aarch64)
2626
ENABLE_FTRACE = 1
27-
CONFIG_OFLAGS ?= -g
27+
CONFIG_OFLAGS ?= -g -ggdb
2828
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
2929
CONFIG_CPPFLAGS += -DSYSDEBUG
3030
endif
@@ -38,13 +38,13 @@ endif
3838
#
3939
ifeq ($(MODE), zero)
4040
ENABLE_FTRACE = 1
41-
CONFIG_OFLAGS ?= -g
41+
CONFIG_OFLAGS ?= -g -ggdb
4242
OVERRIDE_CFLAGS += -O0
4343
OVERRIDE_CXXFLAGS += -O0
4444
CONFIG_CPPFLAGS += -DSYSDEBUG
4545
endif
4646
ifeq ($(MODE), aarch64-zero)
47-
CONFIG_OFLAGS ?= -g
47+
CONFIG_OFLAGS ?= -g -ggdb
4848
OVERRIDE_CFLAGS += -O0 -fdce
4949
OVERRIDE_CXXFLAGS += -O0 -fdce
5050
CONFIG_CPPFLAGS += -DSYSDEBUG
@@ -81,7 +81,7 @@ endif
8181
#
8282
ifeq ($(MODE), opt)
8383
ENABLE_FTRACE = 1
84-
CONFIG_OFLAGS ?= -g
84+
CONFIG_OFLAGS ?= -g -ggdb
8585
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG
8686
CONFIG_CCFLAGS += $(BACKTRACES) -O3 -fmerge-all-constants
8787
TARGET_ARCH ?= -march=native
@@ -98,7 +98,7 @@ endif
9898
# - Turns off support for other operating systems
9999
#
100100
ifeq ($(MODE), optlinux)
101-
CONFIG_OFLAGS ?= -g
101+
CONFIG_OFLAGS ?= -g -ggdb
102102
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
103103
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
104104
CONFIG_COPTS += -mred-zone
@@ -140,7 +140,7 @@ endif
140140
#
141141
ifeq ($(MODE), asan)
142142
ENABLE_FTRACE = 1
143-
CONFIG_OFLAGS ?= -g
143+
CONFIG_OFLAGS ?= -g -ggdb
144144
CONFIG_CPPFLAGS += -D__SANITIZE_ADDRESS__
145145
CONFIG_CCFLAGS += $(BACKTRACES) -O2 -DSYSDEBUG
146146
CONFIG_COPTS += -fsanitize=address
@@ -160,7 +160,7 @@ endif
160160
#
161161
ifeq ($(MODE), dbg)
162162
ENABLE_FTRACE = 1
163-
CONFIG_OFLAGS ?= -g
163+
CONFIG_OFLAGS ?= -g -ggdb
164164
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_ADDRESS__ -D__SANITIZE_UNDEFINED__
165165
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline
166166
CONFIG_COPTS += -fsanitize=address -fsanitize=undefined
@@ -170,7 +170,7 @@ QUOTA ?= -C64 -L300
170170
endif
171171
ifeq ($(MODE), aarch64-dbg)
172172
ENABLE_FTRACE = 1
173-
CONFIG_OFLAGS ?= -g
173+
CONFIG_OFLAGS ?= -g -ggdb
174174
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__
175175
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline -fdce
176176
CONFIG_COPTS += -fsanitize=undefined
@@ -189,7 +189,7 @@ endif
189189
#
190190
ifeq ($(MODE), sysv)
191191
ENABLE_FTRACE = 1
192-
CONFIG_OFLAGS ?= -g
192+
CONFIG_OFLAGS ?= -g -ggdb
193193
CONFIG_CCFLAGS += $(BACKTRACES) -O2
194194
CONFIG_CPPFLAGS += -DSYSDEBUG -DSUPPORT_VECTOR=121
195195
TARGET_ARCH ?= -msse3

libc/calls/BUILD.mk

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ o/$(MODE)/libc/calls/pledge-linux.o: private \
147147
-fPIC \
148148
-ffreestanding
149149

150+
# we want -Os because:
151+
# it makes a big difference
152+
# it gets called very rarely
153+
o/$(MODE)/libc/calls/sigcrashsig.o: private \
154+
CFLAGS += \
155+
-Os
156+
150157
# these assembly files are safe to build on aarch64
151158
o/$(MODE)/libc/calls/getcontext.o: libc/calls/getcontext.S
152159
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $<

libc/calls/sig.c

Lines changed: 27 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
301301
}
302302

303303
// sends signal to another specific thread which is ref'd
304-
static int __sig_killer(struct PosixThread *pt, int sig, int sic) {
304+
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
305305
unsigned rva = __sighandrvas[sig];
306306
unsigned flags = __sighandflags[sig];
307307

@@ -462,66 +462,7 @@ textwindows void __sig_generate(int sig, int sic) {
462462
ALLOW_SIGNALS;
463463
}
464464

465-
static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
466-
switch (ep->ExceptionRecord->ExceptionCode) {
467-
case kNtSignalBreakpoint:
468-
*code = TRAP_BRKPT;
469-
return SIGTRAP;
470-
case kNtSignalIllegalInstruction:
471-
*code = ILL_ILLOPC;
472-
return SIGILL;
473-
case kNtSignalPrivInstruction:
474-
*code = ILL_PRVOPC;
475-
return SIGILL;
476-
case kNtSignalInPageError:
477-
case kNtStatusStackOverflow:
478-
*code = SEGV_MAPERR;
479-
return SIGSEGV;
480-
case kNtSignalGuardPage:
481-
case kNtSignalAccessViolation:
482-
*code = SEGV_ACCERR;
483-
return SIGSEGV;
484-
case kNtSignalInvalidHandle:
485-
case kNtSignalInvalidParameter:
486-
case kNtSignalAssertionFailure:
487-
*code = SI_USER;
488-
return SIGABRT;
489-
case kNtStatusIntegerOverflow:
490-
*code = FPE_INTOVF;
491-
return SIGFPE;
492-
case kNtSignalFltDivideByZero:
493-
*code = FPE_FLTDIV;
494-
return SIGFPE;
495-
case kNtSignalFltOverflow:
496-
*code = FPE_FLTOVF;
497-
return SIGFPE;
498-
case kNtSignalFltUnderflow:
499-
*code = FPE_FLTUND;
500-
return SIGFPE;
501-
case kNtSignalFltInexactResult:
502-
*code = FPE_FLTRES;
503-
return SIGFPE;
504-
case kNtSignalFltDenormalOperand:
505-
case kNtSignalFltInvalidOperation:
506-
case kNtSignalFltStackCheck:
507-
case kNtSignalIntegerDivideByZero:
508-
case kNtSignalFloatMultipleFaults:
509-
case kNtSignalFloatMultipleTraps:
510-
*code = FPE_FLTINV;
511-
return SIGFPE;
512-
case kNtSignalDllNotFound:
513-
case kNtSignalOrdinalNotFound:
514-
case kNtSignalEntrypointNotFound:
515-
case kNtSignalDllInitFailed:
516-
*code = SI_KERNEL;
517-
return SIGSYS;
518-
default:
519-
*code = ep->ExceptionRecord->ExceptionCode;
520-
return SIGSEGV;
521-
}
522-
}
523-
524-
static char *__sig_stpcpy(char *d, const char *s) {
465+
static textwindows char *__sig_stpcpy(char *d, const char *s) {
525466
size_t i;
526467
for (i = 0;; ++i) {
527468
if (!(d[i] = s[i])) {
@@ -530,8 +471,24 @@ static char *__sig_stpcpy(char *d, const char *s) {
530471
}
531472
}
532473

533-
static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
534-
struct CosmoTib *tib) {
474+
static textwindows wontreturn void __sig_death(int sig, const char *thing) {
475+
#ifndef TINY
476+
intptr_t hStderr;
477+
char sigbuf[21], s[128], *p;
478+
hStderr = GetStdHandle(kNtStdErrorHandle);
479+
p = __sig_stpcpy(s, "Terminating on ");
480+
p = __sig_stpcpy(p, thing);
481+
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
482+
p = __sig_stpcpy(p,
483+
". Pass --strace and/or ShowCrashReports() for details.\n");
484+
WriteFile(hStderr, s, p - s, 0, 0);
485+
#endif
486+
__sig_terminate(sig);
487+
}
488+
489+
static textwindows void __sig_unmaskable(struct NtExceptionPointers *ep,
490+
int code, int sig,
491+
struct CosmoTib *tib) {
535492

536493
// log vital crash information reliably for --strace before doing much
537494
// 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,
549506
// exception, then print a friendly helpful hint message to stderr
550507
unsigned rva = __sighandrvas[sig];
551508
if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN) {
552-
#ifndef TINY
553-
intptr_t hStderr;
554-
char sigbuf[21], s[128], *p;
555-
hStderr = GetStdHandle(kNtStdErrorHandle);
556-
p = __sig_stpcpy(s, "Terminating on uncaught ");
557-
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
558-
p = __sig_stpcpy(
559-
p, ". Pass --strace and/or ShowCrashReports() for details.\n");
560-
WriteFile(hStderr, s, p - s, 0, 0);
561-
#endif
562-
__sig_terminate(sig);
509+
__sig_death(sig, "uncaught ");
563510
}
564511

565512
// 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,
580527
}
581528

582529
// call the user signal handler
583-
// with a temporarily replaced signal mask
584530
// and a modifiable view of the faulting code's cpu state
531+
// temporarily replace signal mask while calling crash handler
532+
// abort process if sig is already blocked to avoid crash loop
585533
// note ucontext_t is a hefty data structures on top of NtContext
586534
ucontext_t ctx = {0};
587535
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,
591539
blocksigs |= 1ull << (sig - 1);
592540
ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
593541
memory_order_acquire);
542+
if (ctx.uc_sigmask & (1ull << (sig - 1))) {
543+
__sig_death(sig, "masked ");
544+
__sig_terminate(sig);
545+
}
594546
__sig_handler(rva)(sig, &si, &ctx);
595547
atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask,
596548
memory_order_release);

libc/calls/sig.internal.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_
22
#define COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_
33
#include "libc/calls/struct/sigset.h"
4+
#include "libc/nt/struct/ntexceptionpointers.h"
45
#include "libc/thread/posixthread.internal.h"
56

67
#define SIG_HANDLED_NO_RESTART 1
@@ -17,11 +18,12 @@ extern struct Signals __sig;
1718

1819
bool __sig_ignored(int);
1920
int __sig_check(void);
21+
int __sig_crash_sig(struct NtExceptionPointers *, int *);
22+
int __sig_get(sigset_t);
2023
int __sig_kill(struct PosixThread *, int, int);
2124
int __sig_mask(int, const sigset_t *, sigset_t *);
22-
int __sig_relay(int, int, sigset_t);
2325
int __sig_raise(int, int);
24-
int __sig_get(sigset_t);
26+
int __sig_relay(int, int, sigset_t);
2527
void __sig_delete(int);
2628
void __sig_generate(int, int);
2729
void __sig_init(void);

libc/calls/sigcrashsig.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
2+
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
3+
╞══════════════════════════════════════════════════════════════════════════════╡
4+
│ Copyright 2024 Justine Alexandra Roberts Tunney │
5+
│ │
6+
│ Permission to use, copy, modify, and/or distribute this software for │
7+
│ any purpose with or without fee is hereby granted, provided that the │
8+
│ above copyright notice and this permission notice appear in all copies. │
9+
│ │
10+
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
11+
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
12+
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
13+
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
14+
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
15+
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
16+
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
17+
│ PERFORMANCE OF THIS SOFTWARE. │
18+
╚─────────────────────────────────────────────────────────────────────────────*/
19+
#include "libc/calls/sig.internal.h"
20+
#include "libc/intrin/pushpop.internal.h"
21+
#include "libc/nt/enum/signal.h"
22+
#include "libc/nt/enum/status.h"
23+
#include "libc/nt/struct/ntexceptionpointers.h"
24+
25+
// this is all a mandatory dependency of winmain
26+
// so, we trade away maintanibility for tininess
27+
// see libc/sysv/consts.sh for canonical magnums
28+
29+
#define SIGILL_ pushpop(4)
30+
#define SIGTRAP_ pushpop(5)
31+
#define SIGABRT_ pushpop(6)
32+
#define SIGFPE_ pushpop(8)
33+
#define SIGSEGV_ pushpop(11)
34+
#define SIGSYS_ pushpop(31)
35+
36+
#define TRAP_BRKPT_ pushpop(1)
37+
#define ILL_ILLOPC_ pushpop(1)
38+
#define ILL_PRVOPC_ pushpop(5)
39+
#define SEGV_MAPERR_ pushpop(1)
40+
#define SEGV_ACCERR_ pushpop(2)
41+
#define SI_USER_ pushpop(0)
42+
#define FPE_FLTDIV_ pushpop(3)
43+
#define FPE_FLTOVF_ pushpop(4)
44+
#define FPE_INTOVF_ pushpop(2)
45+
#define FPE_FLTUND_ pushpop(5)
46+
#define FPE_FLTRES_ pushpop(6)
47+
#define FPE_FLTINV_ pushpop(7)
48+
#define SI_KERNEL_ 0x80
49+
50+
textwindows int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
51+
switch (ep->ExceptionRecord->ExceptionCode) {
52+
case kNtSignalBreakpoint:
53+
*code = TRAP_BRKPT_;
54+
return SIGTRAP_;
55+
case kNtSignalIllegalInstruction:
56+
*code = ILL_ILLOPC_;
57+
return SIGILL_;
58+
case kNtSignalPrivInstruction:
59+
*code = ILL_PRVOPC_;
60+
return SIGILL_;
61+
case kNtSignalInPageError:
62+
case kNtStatusStackOverflow:
63+
*code = SEGV_MAPERR_;
64+
return SIGSEGV_;
65+
case kNtSignalGuardPage:
66+
case kNtSignalAccessViolation:
67+
*code = SEGV_ACCERR_;
68+
return SIGSEGV_;
69+
case kNtSignalInvalidHandle:
70+
case kNtSignalInvalidParameter:
71+
case kNtSignalAssertionFailure:
72+
*code = SI_USER_;
73+
return SIGABRT_;
74+
case kNtStatusIntegerOverflow:
75+
*code = FPE_INTOVF_;
76+
return SIGFPE_;
77+
case kNtSignalFltDivideByZero:
78+
*code = FPE_FLTDIV_;
79+
return SIGFPE_;
80+
case kNtSignalFltOverflow:
81+
*code = FPE_FLTOVF_;
82+
return SIGFPE_;
83+
case kNtSignalFltUnderflow:
84+
*code = FPE_FLTUND_;
85+
return SIGFPE_;
86+
case kNtSignalFltInexactResult:
87+
*code = FPE_FLTRES_;
88+
return SIGFPE_;
89+
case kNtSignalFltDenormalOperand:
90+
case kNtSignalFltInvalidOperation:
91+
case kNtSignalFltStackCheck:
92+
case kNtSignalIntegerDivideByZero:
93+
case kNtSignalFloatMultipleFaults:
94+
case kNtSignalFloatMultipleTraps:
95+
*code = FPE_FLTINV_;
96+
return SIGFPE_;
97+
case kNtSignalDllNotFound:
98+
case kNtSignalOrdinalNotFound:
99+
case kNtSignalEntrypointNotFound:
100+
case kNtSignalDllInitFailed:
101+
*code = SI_KERNEL_;
102+
return SIGSYS_;
103+
default:
104+
*code = ep->ExceptionRecord->ExceptionCode;
105+
return SIGSEGV_;
106+
}
107+
}

0 commit comments

Comments
 (0)