From 45ab272c610c3ca4143bb9f37a849310d3d97c51 Mon Sep 17 00:00:00 2001 From: Stone Tickle Date: Wed, 24 Dec 2025 10:54:47 -0500 Subject: [PATCH] add optional native backtrace printing --- include/platform/backtrace.h | 26 +++ meson.build | 6 +- meson_options.txt | 7 + src/amalgam.c | 1 + src/main.c | 13 ++ src/platform/meson.build | 23 +++ src/platform/null/backtrace.c | 13 ++ src/platform/posix/backtrace.c | 320 +++++++++++++++++++++++++++++++++ 8 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 include/platform/backtrace.h create mode 100644 src/platform/null/backtrace.c create mode 100644 src/platform/posix/backtrace.c diff --git a/include/platform/backtrace.h b/include/platform/backtrace.h new file mode 100644 index 00000000..2f28695c --- /dev/null +++ b/include/platform/backtrace.h @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: Stone Tickle + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MUON_PLATFORM_BACKTRACE_H +#define MUON_PLATFORM_BACKTRACE_H + +#include + +#include "datastructures/arr.h" + +struct platform_backtrace_frame { + void *addr, *symbol; + const char *symbol_name, *file_name; + ptrdiff_t offset; +}; + +struct platform_backtrace { + struct arr frames; +}; + +void platform_backtrace_capture(struct arena *a, struct platform_backtrace *bt); + +extern bool have_platform_backtrace_capture; +#endif diff --git a/meson.build b/meson.build index 24b407cd..d186781a 100644 --- a/meson.build +++ b/meson.build @@ -106,16 +106,18 @@ foreach native : [false, true] '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-Wold-style-definition', + '-Wno-frame-address', '-Woverflow', '-Wstrict-aliasing=2', '-Wstrict-prototypes', '-Wundef', '-Wvla', '-fstrict-aliasing', - '-std=c99', + '-fno-omit-frame-pointer', '-fmacro-prefix-map=@0@/='.format( fs.relative_to(meson.current_source_dir(), meson.current_build_dir()), ), + '-std=c99', ] endif @@ -139,6 +141,7 @@ foreach native : [false, true] endforeach include_dir = [include_directories('include')] +muon_export_dynamic = false subdir('tools') subdir('src') @@ -195,6 +198,7 @@ muon = executable( c_args: muon_c_args, cpp_args: muon_c_args, install: true, + export_dynamic: muon_export_dynamic, ) subdir('tests') diff --git a/meson_options.txt b/meson_options.txt index 463c3410..d837cd2e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -55,6 +55,13 @@ option( description: 'select readline implementation', ) +option( + 'native_backtrace', + type: 'feature', + value: 'auto', + description: 'enable native backtrace capture', +) + ################################################################################ # build settings ################################################################################ diff --git a/src/amalgam.c b/src/amalgam.c index cde1b6d5..18622f04 100644 --- a/src/amalgam.c +++ b/src/amalgam.c @@ -117,6 +117,7 @@ #include "platform/assert.c" #include "platform/filesystem.c" #include "platform/mem.c" +#include "platform/null/backtrace.c" #include "platform/os.c" #include "platform/path.c" #include "platform/run_cmd.c" diff --git a/src/main.c b/src/main.c index e637a5dc..7e446027 100644 --- a/src/main.c +++ b/src/main.c @@ -36,6 +36,7 @@ #include "options.h" #include "opts.h" #include "platform/assert.h" +#include "platform/backtrace.h" #include "platform/init.h" #include "platform/os.h" #include "platform/path.h" @@ -1324,6 +1325,7 @@ cmd_version(struct workspace *wk, uint32_t argc, uint32_t argi, char *const argv #ifdef __SANITIZE_MEMORY__ { "msan", true }, #endif + { "native backtrace", have_platform_backtrace_capture }, }; uint32_t i; @@ -1467,6 +1469,16 @@ signal_handler(int signal, const char *signal_name, void *_ctx) LOG_I("caught signal %d (%s)", signal, signal_name); + struct platform_backtrace bt = { 0 }; + platform_backtrace_capture(wk->a, &bt); + + LOG_I("native backtrace (%d frames):", bt.frames.len); + for (uint32_t i = 0; i < bt.frames.len; i++) { + const struct platform_backtrace_frame *frame = arr_get(&bt.frames, i); + LOG_I("%p <%s+%d> at %s", frame->addr, frame->symbol_name, (int)frame->offset, frame->file_name); + } + + log_flush(); if (wk->vm.run) { vm_error(wk, "encountered unhandled runtime error"); @@ -1475,6 +1487,7 @@ signal_handler(int signal, const char *signal_name, void *_ctx) backend_print_stack(wk); } + log_flush(); } int diff --git a/src/platform/meson.build b/src/platform/meson.build index caeb6758..3ba279e3 100644 --- a/src/platform/meson.build +++ b/src/platform/meson.build @@ -31,6 +31,7 @@ if host_machine.system() == 'windows' platform_sources += files( 'windows/rpath_fixer.c', 'windows/win32_error.c', + 'null/backtrace.c', ) endif @@ -40,4 +41,26 @@ if platform == 'posix' else platform_sources += files('posix/rpath_fixer.c') endif + + native_backtrace = get_option('native_backtrace').enabled() + if get_option('native_backtrace').auto() + libdl = cc.find_library('dl', required: false) + + native_backtrace = ( + cc.has_function('dladdr', prefix: '#include ', args: ['-D_BSD_SOURCE'], dependencies: libdl) + and cc.has_function('__builtin_frame_address') + and cc.has_function('__builtin_return_address') + ) + + if native_backtrace and libdl.found() + deps += libdl + endif + endif + + if native_backtrace + muon_export_dynamic = true + platform_sources += files('posix/backtrace.c') + else + platform_sources += files('null/backtrace.c') + endif endif diff --git a/src/platform/null/backtrace.c b/src/platform/null/backtrace.c new file mode 100644 index 00000000..a3ddb6b1 --- /dev/null +++ b/src/platform/null/backtrace.c @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: Stone Tickle + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "platform/backtrace.h" + +bool have_platform_backtrace_capture = false; + +void +platform_backtrace_capture(struct arena *a, struct platform_backtrace *bt) +{ +} diff --git a/src/platform/posix/backtrace.c b/src/platform/posix/backtrace.c new file mode 100644 index 00000000..693cb8f1 --- /dev/null +++ b/src/platform/posix/backtrace.c @@ -0,0 +1,320 @@ +/* + * SPDX-FileCopyrightText: Stone Tickle + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "compat.h" + +// dladdr requires _GNU_SOURCE or _BSD_SOURCE +#undef _POSIX_C_SOURCE +#define _BSD_SOURCE + +#include + +#include "platform/backtrace.h" + +bool have_platform_backtrace_capture = true; + +static void * +backtrace_getreturnaddr(uint8_t level) +{ + switch (level) { + case 0: return __builtin_return_address(1); + case 1: return __builtin_return_address(2); + case 2: return __builtin_return_address(3); + case 3: return __builtin_return_address(4); + case 4: return __builtin_return_address(5); + case 5: return __builtin_return_address(6); + case 6: return __builtin_return_address(7); + case 7: return __builtin_return_address(8); + case 8: return __builtin_return_address(9); + case 9: return __builtin_return_address(10); + case 10: return __builtin_return_address(11); + case 11: return __builtin_return_address(12); + case 12: return __builtin_return_address(13); + case 13: return __builtin_return_address(14); + case 14: return __builtin_return_address(15); + case 15: return __builtin_return_address(16); + case 16: return __builtin_return_address(17); + case 17: return __builtin_return_address(18); + case 18: return __builtin_return_address(19); + case 19: return __builtin_return_address(20); + case 20: return __builtin_return_address(21); + case 21: return __builtin_return_address(22); + case 22: return __builtin_return_address(23); + case 23: return __builtin_return_address(24); + case 24: return __builtin_return_address(25); + case 25: return __builtin_return_address(26); + case 26: return __builtin_return_address(27); + case 27: return __builtin_return_address(28); + case 28: return __builtin_return_address(29); + case 29: return __builtin_return_address(30); + case 30: return __builtin_return_address(31); + case 31: return __builtin_return_address(32); + case 32: return __builtin_return_address(33); + case 33: return __builtin_return_address(34); + case 34: return __builtin_return_address(35); + case 35: return __builtin_return_address(36); + case 36: return __builtin_return_address(37); + case 37: return __builtin_return_address(38); + case 38: return __builtin_return_address(39); + case 39: return __builtin_return_address(40); + case 40: return __builtin_return_address(41); + case 41: return __builtin_return_address(42); + case 42: return __builtin_return_address(43); + case 43: return __builtin_return_address(44); + case 44: return __builtin_return_address(45); + case 45: return __builtin_return_address(46); + case 46: return __builtin_return_address(47); + case 47: return __builtin_return_address(48); + case 48: return __builtin_return_address(49); + case 49: return __builtin_return_address(50); + case 50: return __builtin_return_address(51); + case 51: return __builtin_return_address(52); + case 52: return __builtin_return_address(53); + case 53: return __builtin_return_address(54); + case 54: return __builtin_return_address(55); + case 55: return __builtin_return_address(56); + case 56: return __builtin_return_address(57); + case 57: return __builtin_return_address(58); + case 58: return __builtin_return_address(59); + case 59: return __builtin_return_address(60); + case 60: return __builtin_return_address(61); + case 61: return __builtin_return_address(62); + case 62: return __builtin_return_address(63); + case 63: return __builtin_return_address(64); + case 64: return __builtin_return_address(65); + case 65: return __builtin_return_address(66); + case 66: return __builtin_return_address(67); + case 67: return __builtin_return_address(68); + case 68: return __builtin_return_address(69); + case 69: return __builtin_return_address(70); + case 70: return __builtin_return_address(71); + case 71: return __builtin_return_address(72); + case 72: return __builtin_return_address(73); + case 73: return __builtin_return_address(74); + case 74: return __builtin_return_address(75); + case 75: return __builtin_return_address(76); + case 76: return __builtin_return_address(77); + case 77: return __builtin_return_address(78); + case 78: return __builtin_return_address(79); + case 79: return __builtin_return_address(80); + case 80: return __builtin_return_address(81); + case 81: return __builtin_return_address(82); + case 82: return __builtin_return_address(83); + case 83: return __builtin_return_address(84); + case 84: return __builtin_return_address(85); + case 85: return __builtin_return_address(86); + case 86: return __builtin_return_address(87); + case 87: return __builtin_return_address(88); + case 88: return __builtin_return_address(89); + case 89: return __builtin_return_address(90); + case 90: return __builtin_return_address(91); + case 91: return __builtin_return_address(92); + case 92: return __builtin_return_address(93); + case 93: return __builtin_return_address(94); + case 94: return __builtin_return_address(95); + case 95: return __builtin_return_address(96); + case 96: return __builtin_return_address(97); + case 97: return __builtin_return_address(98); + case 98: return __builtin_return_address(99); + case 99: return __builtin_return_address(100); + case 100: return __builtin_return_address(101); + case 101: return __builtin_return_address(102); + case 102: return __builtin_return_address(103); + case 103: return __builtin_return_address(104); + case 104: return __builtin_return_address(105); + case 105: return __builtin_return_address(106); + case 106: return __builtin_return_address(107); + case 107: return __builtin_return_address(108); + case 108: return __builtin_return_address(109); + case 109: return __builtin_return_address(110); + case 110: return __builtin_return_address(111); + case 111: return __builtin_return_address(112); + case 112: return __builtin_return_address(113); + case 113: return __builtin_return_address(114); + case 114: return __builtin_return_address(115); + case 115: return __builtin_return_address(116); + case 116: return __builtin_return_address(117); + case 117: return __builtin_return_address(118); + case 118: return __builtin_return_address(119); + case 119: return __builtin_return_address(120); + case 120: return __builtin_return_address(121); + case 121: return __builtin_return_address(122); + case 122: return __builtin_return_address(123); + case 123: return __builtin_return_address(124); + case 124: return __builtin_return_address(125); + case 125: return __builtin_return_address(126); + case 126: return __builtin_return_address(127); + case 127: return __builtin_return_address(128); + default: return 0; + } +} + +static void * +backtrace_getframeaddr(uint8_t level) +{ + switch (level) { + case 0: return __builtin_frame_address(1); + case 1: return __builtin_frame_address(2); + case 2: return __builtin_frame_address(3); + case 3: return __builtin_frame_address(4); + case 4: return __builtin_frame_address(5); + case 5: return __builtin_frame_address(6); + case 6: return __builtin_frame_address(7); + case 7: return __builtin_frame_address(8); + case 8: return __builtin_frame_address(9); + case 9: return __builtin_frame_address(10); + case 10: return __builtin_frame_address(11); + case 11: return __builtin_frame_address(12); + case 12: return __builtin_frame_address(13); + case 13: return __builtin_frame_address(14); + case 14: return __builtin_frame_address(15); + case 15: return __builtin_frame_address(16); + case 16: return __builtin_frame_address(17); + case 17: return __builtin_frame_address(18); + case 18: return __builtin_frame_address(19); + case 19: return __builtin_frame_address(20); + case 20: return __builtin_frame_address(21); + case 21: return __builtin_frame_address(22); + case 22: return __builtin_frame_address(23); + case 23: return __builtin_frame_address(24); + case 24: return __builtin_frame_address(25); + case 25: return __builtin_frame_address(26); + case 26: return __builtin_frame_address(27); + case 27: return __builtin_frame_address(28); + case 28: return __builtin_frame_address(29); + case 29: return __builtin_frame_address(30); + case 30: return __builtin_frame_address(31); + case 31: return __builtin_frame_address(32); + case 32: return __builtin_frame_address(33); + case 33: return __builtin_frame_address(34); + case 34: return __builtin_frame_address(35); + case 35: return __builtin_frame_address(36); + case 36: return __builtin_frame_address(37); + case 37: return __builtin_frame_address(38); + case 38: return __builtin_frame_address(39); + case 39: return __builtin_frame_address(40); + case 40: return __builtin_frame_address(41); + case 41: return __builtin_frame_address(42); + case 42: return __builtin_frame_address(43); + case 43: return __builtin_frame_address(44); + case 44: return __builtin_frame_address(45); + case 45: return __builtin_frame_address(46); + case 46: return __builtin_frame_address(47); + case 47: return __builtin_frame_address(48); + case 48: return __builtin_frame_address(49); + case 49: return __builtin_frame_address(50); + case 50: return __builtin_frame_address(51); + case 51: return __builtin_frame_address(52); + case 52: return __builtin_frame_address(53); + case 53: return __builtin_frame_address(54); + case 54: return __builtin_frame_address(55); + case 55: return __builtin_frame_address(56); + case 56: return __builtin_frame_address(57); + case 57: return __builtin_frame_address(58); + case 58: return __builtin_frame_address(59); + case 59: return __builtin_frame_address(60); + case 60: return __builtin_frame_address(61); + case 61: return __builtin_frame_address(62); + case 62: return __builtin_frame_address(63); + case 63: return __builtin_frame_address(64); + case 64: return __builtin_frame_address(65); + case 65: return __builtin_frame_address(66); + case 66: return __builtin_frame_address(67); + case 67: return __builtin_frame_address(68); + case 68: return __builtin_frame_address(69); + case 69: return __builtin_frame_address(70); + case 70: return __builtin_frame_address(71); + case 71: return __builtin_frame_address(72); + case 72: return __builtin_frame_address(73); + case 73: return __builtin_frame_address(74); + case 74: return __builtin_frame_address(75); + case 75: return __builtin_frame_address(76); + case 76: return __builtin_frame_address(77); + case 77: return __builtin_frame_address(78); + case 78: return __builtin_frame_address(79); + case 79: return __builtin_frame_address(80); + case 80: return __builtin_frame_address(81); + case 81: return __builtin_frame_address(82); + case 82: return __builtin_frame_address(83); + case 83: return __builtin_frame_address(84); + case 84: return __builtin_frame_address(85); + case 85: return __builtin_frame_address(86); + case 86: return __builtin_frame_address(87); + case 87: return __builtin_frame_address(88); + case 88: return __builtin_frame_address(89); + case 89: return __builtin_frame_address(90); + case 90: return __builtin_frame_address(91); + case 91: return __builtin_frame_address(92); + case 92: return __builtin_frame_address(93); + case 93: return __builtin_frame_address(94); + case 94: return __builtin_frame_address(95); + case 95: return __builtin_frame_address(96); + case 96: return __builtin_frame_address(97); + case 97: return __builtin_frame_address(98); + case 98: return __builtin_frame_address(99); + case 99: return __builtin_frame_address(100); + case 100: return __builtin_frame_address(101); + case 101: return __builtin_frame_address(102); + case 102: return __builtin_frame_address(103); + case 103: return __builtin_frame_address(104); + case 104: return __builtin_frame_address(105); + case 105: return __builtin_frame_address(106); + case 106: return __builtin_frame_address(107); + case 107: return __builtin_frame_address(108); + case 108: return __builtin_frame_address(109); + case 109: return __builtin_frame_address(110); + case 110: return __builtin_frame_address(111); + case 111: return __builtin_frame_address(112); + case 112: return __builtin_frame_address(113); + case 113: return __builtin_frame_address(114); + case 114: return __builtin_frame_address(115); + case 115: return __builtin_frame_address(116); + case 116: return __builtin_frame_address(117); + case 117: return __builtin_frame_address(118); + case 118: return __builtin_frame_address(119); + case 119: return __builtin_frame_address(120); + case 120: return __builtin_frame_address(121); + case 121: return __builtin_frame_address(122); + case 122: return __builtin_frame_address(123); + case 123: return __builtin_frame_address(124); + case 124: return __builtin_frame_address(125); + case 125: return __builtin_frame_address(126); + case 126: return __builtin_frame_address(127); + case 127: return __builtin_frame_address(128); + default: return 0; + } +} + +static void +platform_backtrace_resolve(struct platform_backtrace *bt) +{ + for (uint32_t i = 0; i < bt->frames.len; i++) { + struct platform_backtrace_frame *frame = arr_get(&bt->frames, i); + Dl_info info = { 0 }; + if (dladdr(frame->addr, &info) != 0) { + frame->symbol_name = info.dli_sname; + frame->file_name = info.dli_fname; + frame->symbol = info.dli_saddr ? info.dli_saddr : frame->addr; + frame->offset = (char *)frame->addr - (char *)frame->symbol; + } + } +} + +void +platform_backtrace_capture(struct arena *a, struct platform_backtrace *bt) +{ + *bt = (struct platform_backtrace){ 0 }; + + arr_init(a, &bt->frames, 32, struct platform_backtrace_frame); + + for (uint32_t i = 1; backtrace_getframeaddr(i + 1); i++) { + void *returnaddr = backtrace_getreturnaddr(i); + if (!returnaddr) { + break; + } + arr_push(a, &bt->frames, &(struct platform_backtrace_frame){ .addr = returnaddr }); + } + platform_backtrace_resolve(bt); +}