From 7012a41365ee4d834b77b7b0bd8902d70e6b3fc5 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Mon, 16 Mar 2026 08:53:37 +0100 Subject: [PATCH] add POSIX stacktrace crash handler and fix build script path --- build.sh | 2 +- src/core/core_platform.c | 459 ++++++++++++++++++--------------------- 2 files changed, 211 insertions(+), 250 deletions(-) diff --git a/build.sh b/build.sh index f24bc3c..5749c68 100644 --- a/build.sh +++ b/build.sh @@ -4,6 +4,6 @@ if [ ! -d build ]; then mkdir build fi cd build -clang ../src/testing/testing_main.c -o testing_main -I../src -g -Wall -Wextra -Werror -fdiagnostics-absolute-paths -Wno-single-bit-bitfield-constant-conversion -Wno-unsequenced -Wno-missing-field-initializers -Wno-missing-braces -ldl -lm -lbacktrace +clang $(realpath ../src/testing/testing_main.c) -o testing_main -I../src -g -Wall -Wextra -Werror -fdiagnostics-absolute-paths -Wno-single-bit-bitfield-constant-conversion -Wno-unsequenced -Wno-missing-field-initializers -Wno-missing-braces -ldl -lm -lbacktrace ./testing_main cd .. diff --git a/src/core/core_platform.c b/src/core/core_platform.c index 6b3c7ed..8cbd338 100644 --- a/src/core/core_platform.c +++ b/src/core/core_platform.c @@ -94,198 +94,6 @@ fn void os_core_init(void) { #include #include -typedef struct b_stacktrace_tag* b_stacktrace_handle; - -/* - Returns a stack-trace handle from the point of view of the caller which - can be expanded to a string via b_stacktrace_to_string. - The handle is allocated with `malloc` and needs to be freed with `free` -*/ -fn b_stacktrace_handle b_stacktrace_get(); - -/* - Converts a stack-trace handle to a human-readable string. - The string is allocated with `malloc` and needs to be freed with `free` -*/ -fn char* b_stacktrace_to_string(b_stacktrace_handle stacktrace); - -/* - Returns a human-readable stack-trace string from the point of view of the - caller. - The string is allocated with `malloc` and needs to be freed with `free` -*/ -fn char* b_stacktrace_get_string(void); - -/* version */ - -typedef struct print_buf { - char* buf; - int pos; - int size; -} print_buf; - -static print_buf buf_init(void) { - print_buf ret = { - (char*) malloc(1024), - 0, - 1024 - }; - return ret; -} - -static void buf_printf(print_buf* b, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - const int len = stbsp_vsnprintf(NULL, 0, fmt, args) + 1; - va_end(args); - - const int new_end = b->pos + len; - - if (new_end > b->size) { - while (new_end > b->size) b->size *= 2; - b->buf = (char*)realloc(b->buf, b->size); - } - - va_start(args, fmt); - b->pos += stbsp_vsnprintf(b->buf + b->pos, len, fmt, args); - va_end(args); -} - -char* b_stacktrace_get_string(void) { - b_stacktrace_handle h = b_stacktrace_get(); - char* ret = b_stacktrace_to_string(h); - free(h); - return ret; -} - -#define B_STACKTRACE_MAX_DEPTH 1024 -#define B_STACKTRACE_ERROR_FLAG ((DWORD64)1 << 63) - -typedef struct b_stacktrace_entry { - DWORD64 AddrPC_Offset; - DWORD64 AddrReturn_Offset; -} b_stacktrace_entry; - -static int SymInitialize_called = 0; - -b_stacktrace_handle b_stacktrace_get(void) { - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - CONTEXT context; - STACKFRAME64 frame; /* in/out stackframe */ - DWORD imageType; - b_stacktrace_entry* ret = (b_stacktrace_entry*)malloc(B_STACKTRACE_MAX_DEPTH * sizeof(b_stacktrace_entry)); - int i = 0; - - if (!SymInitialize_called) { - SymInitialize(process, NULL, TRUE); - SymInitialize_called = 1; - } - - RtlCaptureContext(&context); - - memset(&frame, 0, sizeof(frame)); -#ifdef _M_IX86 - imageType = IMAGE_FILE_MACHINE_I386; - frame.AddrPC.Offset = context.Eip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Ebp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Esp; - frame.AddrStack.Mode = AddrModeFlat; -#elif _M_X64 - imageType = IMAGE_FILE_MACHINE_AMD64; - frame.AddrPC.Offset = context.Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Rsp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Rsp; - frame.AddrStack.Mode = AddrModeFlat; -#elif _M_IA64 - imageType = IMAGE_FILE_MACHINE_IA64; - frame.AddrPC.Offset = context.StIIP; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.IntSp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrBStore.Offset = context.RsBSP; - frame.AddrBStore.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.IntSp; - frame.AddrStack.Mode = AddrModeFlat; -#else - #error "Platform not supported!" -#endif - - while (1) { - b_stacktrace_entry* cur = ret + i++; - if (i == B_STACKTRACE_MAX_DEPTH) { - cur->AddrPC_Offset = 0; - cur->AddrReturn_Offset = 0; - break; - } - - if (!StackWalk64(imageType, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { - cur->AddrPC_Offset = frame.AddrPC.Offset; - cur->AddrReturn_Offset = B_STACKTRACE_ERROR_FLAG; /* mark error */ - cur->AddrReturn_Offset |= GetLastError(); - break; - } - - cur->AddrPC_Offset = frame.AddrPC.Offset; - cur->AddrReturn_Offset = frame.AddrReturn.Offset; - - if (frame.AddrReturn.Offset == 0) { - break; - } - } - - return (b_stacktrace_handle)(ret); -} - -char* b_stacktrace_to_string(b_stacktrace_handle h) { - const b_stacktrace_entry* entries = (b_stacktrace_entry*)h; - int i = 0; - HANDLE process = GetCurrentProcess(); - print_buf out = buf_init(); - IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + 1024); - symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - symbol->MaxNameLength = 1024; - - while (1) { - IMAGEHLP_LINE64 lineData; - DWORD lineOffset = 0; - DWORD64 symOffset = 0; - const b_stacktrace_entry* cur = entries + i++; - - if (cur->AddrReturn_Offset & B_STACKTRACE_ERROR_FLAG) { - DWORD error = cur->AddrReturn_Offset & 0xFFFFFFFF; - buf_printf(&out, "StackWalk64 error: %d @ %p\n", error, cur->AddrPC_Offset); - break; - } - - if (cur->AddrPC_Offset == cur->AddrReturn_Offset) { - buf_printf(&out, "Stack overflow @ %p\n", cur->AddrPC_Offset); - break; - } - - SymGetLineFromAddr64(process, cur->AddrPC_Offset, &lineOffset, &lineData); - buf_printf(&out, "%s(%d): ", lineData.FileName, lineData.LineNumber); - - if (SymGetSymFromAddr64(process, cur->AddrPC_Offset, &symOffset, symbol)) { - buf_printf(&out, "%s\n", symbol->Name); - } - else { - buf_printf(&out, " Unkown symbol @ %p\n", cur->AddrPC_Offset); - } - - if (cur->AddrReturn_Offset == 0) { - break; - } - } - - free(symbol); - return out.buf; -} - fn void *os_vmem_reserve(usize size) { void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); return result; @@ -349,6 +157,9 @@ fn void os_core_init(void) { tcx->perm = perm; } #elif PLATFORM_POSIX + #if defined(__linux__) && !defined(_GNU_SOURCE) + #define _GNU_SOURCE + #endif #include #include #include @@ -357,13 +168,215 @@ fn void os_core_init(void) { #include #include #include + #include #include #include #if !PLATFORM_EMSCRIPTEN - #include - #include + +typedef struct b_stacktrace_tag* b_stacktrace_handle; +b_stacktrace_handle b_stacktrace_get(); +int b_stacktrace_depth(b_stacktrace_handle stacktrace); +char* b_stacktrace_to_string(b_stacktrace_handle stacktrace); +char* malloc_backtrace(void); + +typedef struct print_buf { + char* buf; + int pos; + int size; +} print_buf; + +static print_buf buf_init(void) { + print_buf ret = { + (char*) malloc(1024), + 0, + 1024 + }; + return ret; +} + +static void buf_printf(print_buf* b, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + const int len = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + const int new_end = b->pos + len; + + if (new_end > b->size) { + while (new_end > b->size) b->size *= 2; + b->buf = (char*)realloc(b->buf, b->size); + } + + va_start(args, fmt); + b->pos += vsnprintf(b->buf + b->pos, len, fmt, args); + va_end(args); +} + +char* malloc_backtrace(void) { + b_stacktrace_handle h = b_stacktrace_get(); + char* ret = b_stacktrace_to_string(h); + free(h); + return ret; +} + +#define B_STACKTRACE_MAX_DEPTH 1024 +typedef struct b_stacktrace { + void* trace[B_STACKTRACE_MAX_DEPTH]; + int trace_size; +} b_stacktrace; + +b_stacktrace_handle b_stacktrace_get(void) { + b_stacktrace* ret = (b_stacktrace*)malloc(sizeof(b_stacktrace)); + ret->trace_size = backtrace(ret->trace, B_STACKTRACE_MAX_DEPTH); + return (b_stacktrace_handle)(ret); +} + +int b_stacktrace_depth(b_stacktrace_handle h) { + const b_stacktrace* stacktrace = (b_stacktrace*)h; + return stacktrace->trace_size; +} + +char* b_stacktrace_to_string(b_stacktrace_handle h) { + const b_stacktrace* stacktrace = (b_stacktrace*)h; + char** messages = backtrace_symbols(stacktrace->trace, stacktrace->trace_size); + print_buf out = buf_init(); + int i; + + int skip_count = 5; + for (i = 0; i < stacktrace->trace_size; ++i) { + void* tracei = stacktrace->trace[i]; + char* msg = messages[i]; + + if (i < skip_count) { + continue; + } + + /* calculate load offset */ + Dl_info info; + dladdr(tracei, &info); + if (info.dli_fbase == (void*)0x400000) { + /* address from executable, so don't offset */ + info.dli_fbase = NULL; + } + + while (*msg && *msg != '(') ++msg; + *msg = 0; + + { + char cmd[1024]; + char line[2048]; + + FILE* fp; + snprintf(cmd, 1024, "addr2line -e %s -f -C -p %p 2>/dev/null", messages[i], (void*)((char*)tracei - (char*)info.dli_fbase)); + + fp = popen(cmd, "r"); + if (!fp) { + buf_printf(&out, "Failed to generate trace further...\n"); + break; + } + + while (fgets(line, sizeof(line), fp)) { + buf_printf(&out, " "); + if (strstr(line, "?? ")) { + /* just output address if nothing can be found */ + buf_printf(&out, "%p\n", tracei); + } + else { + int len = strlen(line); + char *at = strstr(line, " at "); + if (at) { + char *function = line; + int function_len = at - line; + char *file = at + 4; + int file_len = strlen(file) - 1; + buf_printf(&out, " %.*s %.*s (%s)\n", file_len, file, function_len, function, messages[i]); + } else { + buf_printf(&out, " %.*s (%s)\n", len - 1, line, messages[i]); + } + } + } + + pclose(fp); + } + } + + free(messages); + return out.buf; +} + +void print_backtrace(void) { + char *backtrace = malloc_backtrace(); + puts(backtrace); + free(backtrace); +} + +const char *signal_to_string(int signal) { + switch (signal) { + case SIGINT: return "SIGINT"; + case SIGILL: return "SIGILL"; + case SIGABRT: return "SIGABRT"; + case SIGFPE: return "SIGFPE"; + case SIGSEGV: return "SIGSEGV"; + case SIGTERM: return "SIGTERM"; + case SIGHUP: return "SIGALRM"; + case SIGQUIT: return "SIGQUIT"; + case SIGTRAP: return "SIGTRAP"; + case SIGKILL: return "SIGKILL"; + case SIGPIPE: return "SIGPIPE"; + case SIGALRM: return "SIGHUP"; + case SIGPOLL: return "SIGPOLL"; + default: return ""; + } +} + +void backtrace_handle_signal(int signal, siginfo_t* info, void* context) { + (void)context; // unused + printf("Stack trace (%s/%d):", signal_to_string(signal), signal); + if (signal == SIGSEGV) { + printf(" at addr: %p", info->si_addr); + } + puts(""); + print_backtrace(); + exit(1); +} + +void os__setup_backtrace() { + struct sigaction sa; + sa.sa_sigaction = backtrace_handle_signal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_SIGINFO; + + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGABRT, &sa, NULL); + sigaction(SIGFPE, &sa, NULL); + sigaction(SIGBUS, &sa, NULL); + sigaction(SIGILL, &sa, NULL); + sigaction(SIGTRAP, &sa, NULL); +#ifdef SIGSYS + sigaction(SIGSYS, &sa, NULL); #endif +#ifdef SIGXCPU + sigaction(SIGXCPU, &sa, NULL); +#endif +#ifdef SIGXFSZ + sigaction(SIGXFSZ, &sa, NULL); +#endif +#ifdef SIGPIPE + sigaction(SIGPIPE, &sa, NULL); +#endif +#ifdef SIGTERM + sigaction(SIGTERM, &sa, NULL); +#endif +#ifdef SIGINT + sigaction(SIGINT, &sa, NULL); +#endif +#ifdef SIGQUIT + sigaction(SIGQUIT, &sa, NULL); +#endif +} +#endif + fn void *os_vmem_reserve(usize size) { void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); return result == MAP_FAILED ? NULL : result; @@ -399,68 +412,16 @@ fn f64 os_parse_float(char *str) { return strtod(str, NULL); } -gb struct backtrace_state *backtrace_state = NULL; - -fn void os_core_backtrace_error_callback(void *data, const char *msg, int errnum) { - unused(data); - errorf("libbacktrace error: %s (errnum: %d)\n", msg, errnum); -} - -fn int os_core_backtrace_print_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) { - unused(data); - unused(pc); - b32 printed = false; - if (filename != NULL) { - char buffer[512]; - char *f = realpath(filename, buffer); - printf("%s:%d:1: ", f, lineno); - printed = true; - } - if (function != NULL) { - printf("%s", function); - printed = true; - } - if (printed) { - printf("\n"); - } - return 0; -} - -#if !PLATFORM_EMSCRIPTEN -fn void os_unix_crash_handler(int signal, siginfo_t* info, void* context) { - unused(context); - unused(signal); - unused(info); - backtrace_full(backtrace_state, 2, os_core_backtrace_print_callback, os_core_backtrace_error_callback, NULL); - exit(1); -} - -fn void os_unix_register_crash_handler() { - backtrace_state = backtrace_create_state(NULL, 1, os_core_backtrace_error_callback, NULL); - - struct sigaction sa; - sa.sa_sigaction = os_unix_crash_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART | SA_SIGINFO; - - sigaction(SIGSEGV, &sa, NULL); - sigaction(SIGABRT, &sa, NULL); - sigaction(SIGBUS, &sa, NULL); - sigaction(SIGILL, &sa, NULL); - sigaction(SIGFPE, &sa, NULL); -} -#endif - fn void os_core_small_init(ma_arena_t *arena) { +#if !PLATFORM_EMSCRIPTEN + os__setup_backtrace(); +#endif tcx = ma_push_type(arena, thread_ctx_t); tcx->log.break_on_error = true; tcx->log.break_on_fatal = true; tcx->log.break_on_warning = true; tcx->log.print_file_and_line = true; tcx->log.log_proc = default_log_proc; -#if !PLATFORM_EMSCRIPTEN - os_unix_register_crash_handler(); -#endif } fn void os_core_init(void) {