From d2f59c5b3f30b93373fe5f9d80b1d2b5c744c5d9 Mon Sep 17 00:00:00 2001 From: Manu Linares Date: Fri, 19 Dec 2025 23:07:23 -0300 Subject: [PATCH] Linux: implement signal stacktrace using SA_SIGINFO + sigaltstack (#2653) * Linux: implement signal stacktrace using SA_SIGINFO + sigaltstack Adds proper signal handlers for SIGSEGV/SIGBUS/SIGILL on Linux, enables backtraces from signal context, and exits with correct POSIX signal codes (128+signal). Fixes missing "signal stacktrace" support Signed-off-by: Manuel Barrio Linares * defer libc::dlclose(handle); Signed-off-by: Manuel Barrio Linares * fix double backtrace on panic Signed-off-by: Manuel Barrio Linares * add guards for Linux X86_64 - remove comments - uncomment MContext_t for Linux AARCH64 Signed-off-by: Manuel Barrio Linares * fix guards, missed in two places Signed-off-by: Manuel Barrio Linares --------- Signed-off-by: Manuel Barrio Linares Co-authored-by: Christoffer Lerno --- lib/std/core/builtin.c3 | 33 ++++++++++++-- lib/std/libc/os/posix.c3 | 14 +++--- lib/std/os/posix/process.c3 | 85 ++++++++++++++++++++++++++++--------- 3 files changed, 103 insertions(+), 29 deletions(-) diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index b1515e8af..c8d0887b3 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -208,6 +208,7 @@ fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_ba fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE) { + in_panic = true; $if $defined(io::stderr) && env::PANIC_MSG: if (!print_backtrace(message, 2)) { @@ -224,7 +225,7 @@ macro void abort(String string = "Unrecoverable error reached", ...) @format(0) $$trap(); } -bool in_panic @local = false; +bool in_panic @private = false; fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE) { @@ -997,7 +998,7 @@ fn void sig_bus_error(CInt i, void* info, void* context) } $endif $endif - $$trap(); + os::fastexit(128 + i); } fn void sig_segmentation_fault(CInt i, void* p1, void* context) @@ -1012,15 +1013,39 @@ fn void sig_segmentation_fault(CInt i, void* p1, void* context) } $endif $endif - $$trap(); + os::fastexit(128 + i); } +fn void sig_illegal_instruction(CInt i, void* p1, void* context) +{ + if (in_panic) os::fastexit(128 + i); + $if !env::NATIVE_STACKTRACE: + sig_panic("Illegal instruction."); + $else + $if $defined(io::stderr): + if (!print_backtrace("Illegal instruction.", 2, posix::stack_instruction(context))) + { + io::eprintn("\nERROR: Illegal instruction."); + } + $endif + $endif + os::fastexit(128 + i); +} +char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX); // Clean this up fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE) { + $if env::LINUX: + Stack_t ss = { + .ss_sp = &sig_stack, + .ss_size = sig_stack.len + }; + libc::sigaltstack(&ss, null); + $endif + posix::install_signal_handler(libc::SIGBUS, &sig_bus_error); posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault); + posix::install_signal_handler(libc::SIGILL, &sig_illegal_instruction); } - diff --git a/lib/std/libc/os/posix.c3 b/lib/std/libc/os/posix.c3 index 09f67d8df..d0e5a9f08 100644 --- a/lib/std/libc/os/posix.c3 +++ b/lib/std/libc/os/posix.c3 @@ -27,7 +27,7 @@ const CUInt SA_RESTART = env::LINUX ? 0x10000000 : 0x0002; const CUInt SA_RESETHAND = env::LINUX ? 0x80000000 : 0x0004; const CUInt SA_SIGINFO = env::LINUX ? 0x00000004 : 0x0040; -alias Sigset_t @if(!env::LINUX) = uint; +alias Sigset_t @if(env::DARWIN || env::BSD_FAMILY) = uint; alias Sigset_t @if(env::LINUX) = ulong[16]; alias SigActionFunction = fn void(CInt, void*, void*); @@ -38,10 +38,13 @@ struct Sigaction SignalFunction sa_handler; SigActionFunction sa_sigaction; } - CInt sa_flags @if(env::BSD_FAMILY); - Sigset_t sa_mask; // 128 - CInt sa_flags @if(!env::BSD_FAMILY); - void* sa_restorer @if(env::LINUX); + + CInt sa_flags @if(env::DARWIN || env::BSD_FAMILY ); + Sigset_t sa_mask @if(env::DARWIN || env::BSD_FAMILY); + + Sigset_t sa_mask @if(env::LINUX); + CInt sa_flags @if(env::LINUX); + void* sa_restorer @if(env::LINUX); } struct Stack_t @@ -62,6 +65,7 @@ struct Stack_t extern fn CInt sigaltstack(Stack_t* ss, Stack_t* old_ss); extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction) @if(!env::NETBSD); extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction) @cname("__sigaction_siginfo") @if(env::NETBSD); +extern fn CInt sigemptyset(Sigset_t* set); module libc::termios @if(env::LIBC &&& env::POSIX); diff --git a/lib/std/os/posix/process.c3 b/lib/std/os/posix/process.c3 index 44646bfac..c00e78a3f 100644 --- a/lib/std/os/posix/process.c3 +++ b/lib/std/os/posix/process.c3 @@ -62,11 +62,20 @@ extern fn CInt backtrace(void** buffer, CInt size) @if(env::OPENBSD || env::NETB fn void install_signal_handler(CInt signal, SigActionFunction func) { - Sigaction action = { - .sa_sigaction = func, - }; - Sigaction old; - libc::sigaction(signal, &action, &old); + CInt flags = libc::SA_SIGINFO; + + $if env::LINUX: + flags |= libc::SA_ONSTACK; + $endif + + Sigaction action = { + .sa_sigaction = func, + .sa_flags = flags + }; + libc::sigemptyset(&action.sa_mask); + + Sigaction old; + libc::sigaction(signal, &action, &old); } fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD && !env::NETBSD) @@ -75,8 +84,8 @@ fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD && !env::NETBSD) void* handle = libc::dlopen("libc.so.6", libc::RTLD_LAZY|libc::RTLD_NODELETE); if (handle) { + defer libc::dlclose(handle); BacktraceFn backtrace_fn = libc::dlsym(handle, "backtrace"); - libc::dlclose(handle); if (backtrace_fn) { return backtrace_fn(buffer, size); @@ -85,20 +94,23 @@ fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD && !env::NETBSD) // Loop through the return addresses until we hit a signal. // This avoids using the frame address. Sigaction restore_backtrace = { - .sa_sigaction = fn void(CInt, void*, void*) { libc::longjmp(&backtrace_jmpbuf, 1); }, - }; + .sa_sigaction = fn void(CInt, void*, void*) { libc::longjmp(&backtrace_jmpbuf, 1); }, + .sa_flags = libc::SA_SIGINFO + }; Sigaction sig_bus, sig_segv, sig_ill; libc::sigaction(libc::SIGBUS, &restore_backtrace, &sig_bus); libc::sigaction(libc::SIGSEGV, &restore_backtrace, &sig_segv); libc::sigaction(libc::SIGILL, &restore_backtrace, &sig_ill); - void*[128] buffer_first; int i = 0; - for (i = 0; i < size; i++) + if (libc::setjmp(&backtrace_jmpbuf) == 0) { - if (libc::setjmp(&backtrace_jmpbuf) == 1) break; - buffer[i] = builtin::get_returnaddress(i); - if (!buffer[i]) break; + for (i = 0; i < size; i++) + { + void* addr = builtin::get_returnaddress(i); + if (!addr) break; + buffer[i] = addr; + } } Sigaction old; @@ -116,10 +128,43 @@ struct PosixUContext_t @if(env::DARWIN) __Darwin_sigaltstack uc_stack; /* stack used by this context */ PosixUContext_t* uc_link; /* pointer to resuming context */ __Darwin_size_t uc_mcsize; /* size of the machine context passed in */ - __Darwin_mcontext64* uc_mcontext; /* pointer to machine specific context */ + __Darwin_mcontext64* uc_mcontext; /* pointer to machine specific context */ } -alias PosixUContext_t @if(!env::DARWIN) = void; +/* taken from sys/ucontext.h */ +typedef Greg_t @if(env::LINUX && env::X86_64) = long; +const REG_RIP @if(env::LINUX && env::X86_64) = 16; +struct LibcFPState @if(env::LINUX && env::X86_64) { char[512] __data; } + +struct MContext_t @align(16) @if(env::LINUX && env::X86_64) +{ + Greg_t[23] gregs; + LibcFPState* fpregs; + ulong[8] __reserved1; +} + +struct MContext_t @align(16) @if(env::LINUX && env::AARCH64) +{ + ulong fault_address; + ulong[31] regs; + ulong sp; + ulong pc; + ulong pstate; + char[4096] __reserved; +} + +struct PosixUContext_t @if(env::LINUX && env::X86_64) +{ + ulong uc_flags; + PosixUContext_t* uc_link; + Stack_t uc_stack; + MContext_t uc_mcontext; + Sigset_t uc_sigmask; + LibcFPState __fpregs_mem; + ulong[4] __ssp; +} + +alias PosixUContext_t @if(!env::DARWIN && !(env::LINUX && env::X86_64) )= void; macro void* stack_instruction(PosixUContext_t* uc) { @@ -127,13 +172,13 @@ macro void* stack_instruction(PosixUContext_t* uc) $case env::DARWIN && env::AARCH64: return (void*)uc.uc_mcontext.__ss.__pc + 1; /* $case env::DARWIN && env::X86_64: - return uc.uc_mcontext.__ss.__rip; + return (void*)uc.uc_mcontext.__ss.__rip; $case env::LINUX && env::X86: - return uc.uc_mcontext.gregs[REG_EIP]; + return uc.uc_mcontext.gregs[REG_EIP];*/ $case env::LINUX && env::X86_64: - return uc.uc_mcontext.gregs[REG_RIP]; - $case env::LINUX && env::AARCH64: - return uc.uc_mcontext.pc; + return (void*)uc.uc_mcontext.gregs[REG_RIP]; +/* $case env::LINUX && env::AARCH64: + return (void*)uc.uc_mcontext.pc; $case env::FREEBSD && env::X86_64: return uc.uc_mcontext.mc_rip; $case env::OPENBSD && env::X86_64: