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 <mbarriolinares@gmail.com>

* defer libc::dlclose(handle);

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* fix double backtrace on panic

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* add guards for Linux X86_64

- remove comments
- uncomment MContext_t for Linux AARCH64

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

* fix guards, missed in two places

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>

---------

Signed-off-by: Manuel Barrio Linares <mbarriolinares@gmail.com>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
This commit is contained in:
Manu Linares
2025-12-19 23:07:23 -03:00
committed by GitHub
parent 85166bc706
commit d2f59c5b3f
3 changed files with 103 additions and 29 deletions

View File

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

View File

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

View File

@@ -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: