diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index 8c6e967bd..12a118ad5 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -135,11 +135,14 @@ macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#val } -fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem) +fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem) { void*[256] buffer; void*[] backtraces = backtrace::capture_current(&buffer); - backtraces_to_ignore++; + if (added_backtrace) + { + backtraces[++backtraces_to_ignore] = added_backtrace; + } @stack_mem(2048; Allocator mem) { BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces); @@ -941,23 +944,20 @@ macro void* get_returnaddress(int n) } module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS); -import libc, std::io; +import libc, std::io, std::os::posix; fn void sig_panic(String message) { default_panic(message, "???", "???", 0); } -SignalFunction old_bus_error; -SignalFunction old_segmentation_fault; - -fn void sig_bus_error(CInt i) +fn void sig_bus_error(CInt i, void* info, void* context) { $if !env::NATIVE_STACKTRACE: sig_panic("Illegal memory access."); $else $if $defined(io::stderr): - if (!print_backtrace("Illegal memory access.", 1)) + if (!print_backtrace("Illegal memory access.", 2, posix::stack_instruction(context))) { io::eprintn("\nERROR: 'Illegal memory access'."); } @@ -966,13 +966,13 @@ fn void sig_bus_error(CInt i) $$trap(); } -fn void sig_segmentation_fault(CInt i) +fn void sig_segmentation_fault(CInt i, void* p1, void* context) { $if !env::NATIVE_STACKTRACE: sig_panic("Out of bounds memory access."); $else $if $defined(io::stderr): - if (!print_backtrace("Out of bounds memory access.", 1)) + if (!print_backtrace("Out of bounds memory access.", 2, posix::stack_instruction(context))) { io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow."); } @@ -981,17 +981,12 @@ fn void sig_segmentation_fault(CInt i) $$trap(); } -fn void install_signal_handler(CInt signal, SignalFunction func) @local -{ - SignalFunction old = libc::signal(signal, func); - // Restore - if ((iptr)old > 1024) libc::signal(signal, old); -} + // Clean this up fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE) { - install_signal_handler(libc::SIGBUS, &sig_bus_error); - install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault); + posix::install_signal_handler(libc::SIGBUS, &sig_bus_error); + posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault); } diff --git a/lib/std/core/runtime_test.c3 b/lib/std/core/runtime_test.c3 index 964c462b2..087a5e19c 100644 --- a/lib/std/core/runtime_test.c3 +++ b/lib/std/core/runtime_test.c3 @@ -4,8 +4,8 @@ module std::core::runtime; import std::core::test @public; import std::core::mem::allocator @public; -import libc, std::time, std::io, std::sort; -import std::os::env; +import libc, std::time, std::io, std::sort, std::os; + alias TestFn = fn void(); @@ -86,17 +86,21 @@ fn bool terminal_has_ansi_codes() @local => @pool() $endif } -fn void sig_bus_error(CInt i) @local +fn void sig_bus_error(CInt i, void*, void* context) @local @if(env::POSIX) { - test_panic("Bus error", "Unknown", "Unknown", 0); + panic_test("Bus error", "Unknown", "Unknown", 1, posix::stack_instruction(context)); } -fn void sig_segmentation_fault(CInt i) @local +fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POSIX) { - test_panic("Segmentation fault", "Unknown", "Unknown", 0); + panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context)); } fn void test_panic(String message, String file, String function, uint line) @local +{ + panic_test(message, file, function, line); +} +fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @local { if (test_context.is_in_panic) return; test_context.is_in_panic = true; @@ -106,7 +110,7 @@ fn void test_panic(String message, String file, String function, uint line) @loc if (test_context.assert_print_backtrace) { $if env::NATIVE_STACKTRACE: - builtin::print_backtrace(message, 0); + builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace); $endif } io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message); @@ -186,9 +190,9 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private io::printn("There are no test units to run."); return true; // no tests == technically a pass } -$if !env::NO_LIBC: - libc::signal(libc::SIGBUS, &sig_bus_error); - libc::signal(libc::SIGSEGV, &sig_segmentation_fault); +$if !env::NO_LIBC && env::POSIX: + posix::install_signal_handler(libc::SIGBUS, &sig_bus_error); + posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault); $endif foreach (&unit : tests) { diff --git a/lib/std/os/macos/process.c3 b/lib/std/os/macos/process.c3 new file mode 100644 index 000000000..ab484c13d --- /dev/null +++ b/lib/std/os/macos/process.c3 @@ -0,0 +1,44 @@ +module std::os::darwin @if(env::DARWIN); + +alias __Darwin_sigset_t = uint; +alias __Darwin_size_t = usz; + +struct __Darwin_arm_exception_state64 +{ + ulong __far; /* Virtual Fault Address */ + uint __esr; /* Exception syndrome */ + uint __exception; /* number of arm exception taken */ +} + +struct __Darwin_arm_thread_state64 +{ + ulong[29] __x; /* General purpose registers x0-x28 */ + ulong __fp; /* Frame pointer x29 */ + ulong __lr; /* Link register x30 */ + ulong __sp; /* Stack pointer x31 */ + ulong __pc; /* Program counter */ + ulong __cpsr; /* Current program status register */ + ulong __pad; /* Same size for 32-bit or 64-bit clients */ +} + +struct __Darwin_arm_neon_state64 +{ + uint128[32] __v; + uint __fpsr; + uint __fpcr; +} + +struct __Darwin_mcontext64 +{ + __Darwin_arm_exception_state64 __es; + __Darwin_arm_thread_state64 __ss; + __Darwin_arm_neon_state64 __ns; +} + +struct __Darwin_sigaltstack +{ + void* ss_sp; /* signal stack base */ + __Darwin_size_t ss_size; /* signal stack length */ + int ss_flags; /* SA_DISABLE and/or SA_ONSTACK */ +} + diff --git a/lib/std/os/posix/process.c3 b/lib/std/os/posix/process.c3 index a549325bd..72a0ea238 100644 --- a/lib/std/os/posix/process.c3 +++ b/lib/std/os/posix/process.c3 @@ -1,5 +1,5 @@ module std::os::posix @if(env::POSIX); -import libc; +import libc, std::os::darwin; struct Posix_spawn_file_actions_t { @@ -60,6 +60,15 @@ alias BacktraceFn = fn CInt(void** buffer, CInt size); extern fn CInt backtrace(void** buffer, CInt size) @if(env::OPENBSD); +fn void install_signal_handler(CInt signal, SigActionFunction func) +{ + Sigaction action = { + .sa_sigaction = func, + }; + Sigaction old; + libc::sigaction(signal, &action, &old); +} + fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD) { if (size < 1) return 0; @@ -75,12 +84,13 @@ fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD) } // Loop through the return addresses until we hit a signal. // This avoids using the frame address. - SignalFunction restore_backtrace = fn void(CInt) { - libc::longjmp(&backtrace_jmpbuf, 1); - }; - SignalFunction sig_bus = libc::signal(libc::SIGBUS, restore_backtrace); - SignalFunction sig_segv = libc::signal(libc::SIGSEGV, restore_backtrace); - SignalFunction sig_ill = libc::signal(libc::SIGILL, restore_backtrace); + Sigaction restore_backtrace = { + .sa_sigaction = fn void(CInt, void*, void*) { libc::longjmp(&backtrace_jmpbuf, 1); }, + }; + 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; @@ -90,8 +100,47 @@ fn CInt backtrace(void** buffer, CInt size) @if(!env::OPENBSD) buffer[i] = builtin::get_returnaddress(i); if (!buffer[i]) break; } - libc::signal(libc::SIGBUS, sig_bus); - libc::signal(libc::SIGSEGV, sig_segv); - libc::signal(libc::SIGILL, sig_ill); + + Sigaction old; + libc::sigaction(libc::SIGBUS, &sig_bus, &old); + libc::sigaction(libc::SIGSEGV, &sig_segv, &old); + libc::sigaction(libc::SIGILL, &sig_ill, &old); + return i; } + +struct PosixUContext_t @if(env::DARWIN) +{ + int uc_onstack; + __Darwin_sigset_t uc_sigmask; /* signal mask used by this context */ + __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 */ +} + +alias PosixUContext_t @if(!env::DARWIN) = void; + +macro void* stack_instruction(PosixUContext_t* uc) +{ + $switch: + $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; + $case env::LINUX && env::X86: + 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; + $case env::FREEBSD && env::X86_64: + return uc.uc_mcontext.mc_rip; + $case env::OPENBSD && env::X86_64: + return uc.sc_rip; + $case env::NETBSD: + uc.uc_mcontext.__gregs[_REG_RIP];*/ + $default: + return null; + $endswitch +} diff --git a/releasenotes.md b/releasenotes.md index 1080dd563..8099ab436 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -36,6 +36,7 @@ - `*(int*)1` incorrectly yielded an assert in LLVM IR lowering #2584. - Fix issue when tests encounter a segmentation fault or similar. - With project.json, when overriding with an empty list the base settings would still be used. #2583 +- Add sigsegv stacktrace in test and regular errors for Darwin Arm64. #1105 ### Stdlib changes