aboutsummaryrefslogtreecommitdiff
path: root/core/debug
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2024-04-28 12:43:27 +0100
committergingerBill <bill@gingerbill.org>2024-04-28 12:43:27 +0100
commit6c185a5dca739b4aab034449f0627925e0137173 (patch)
treeab4cc374aabc0657bf64559b77cc4da4e8c0283d /core/debug
parentf428f26c8e18a4f74b396adf6f4d21526c3c9d5e (diff)
Add `core:debug/trace` for Linux
Diffstat (limited to 'core/debug')
-rw-r--r--core/debug/trace/trace_linux.odin191
-rw-r--r--core/debug/trace/trace_nil.odin2
2 files changed, 192 insertions, 1 deletions
diff --git a/core/debug/trace/trace_linux.odin b/core/debug/trace/trace_linux.odin
new file mode 100644
index 000000000..529a83470
--- /dev/null
+++ b/core/debug/trace/trace_linux.odin
@@ -0,0 +1,191 @@
+//+private file
+//+build linux
+package debug_trace
+
+import "base:runtime"
+import "core:strings"
+import "core:fmt"
+import "core:c"
+
+// NOTE: Relies on C++23 which adds <stacktrace> and becomes ABI and that can be used
+foreign import stdcpplibbacktrace "system:stdc++_libbacktrace"
+
+foreign import libdl "system:dl"
+
+backtrace_state :: struct {}
+backtrace_error_callback :: proc "c" (data: rawptr, msg: cstring, errnum: c.int)
+backtrace_simple_callback :: proc "c" (data: rawptr, pc: uintptr) -> c.int
+backtrace_full_callback :: proc "c" (data: rawptr, pc: uintptr, filename: cstring, lineno: c.int, function: cstring) -> c.int
+backtrace_syminfo_callback :: proc "c" (data: rawptr, pc: uintptr, symname: cstring, symval: uintptr, symsize: uintptr)
+
+@(default_calling_convention="c", link_prefix="__glibcxx_")
+foreign stdcpplibbacktrace {
+ backtrace_create_state :: proc(
+ filename: cstring,
+ threaded: c.int,
+ error_callback: backtrace_error_callback,
+ data: rawptr,
+ ) -> ^backtrace_state ---
+ backtrace_simple :: proc(
+ state: ^backtrace_state,
+ skip: c.int,
+ callback: backtrace_simple_callback,
+ error_callback: backtrace_error_callback,
+ data: rawptr,
+ ) -> c.int ---
+ backtrace_pcinfo :: proc(
+ state: ^backtrace_state,
+ pc: uintptr,
+ callback: backtrace_full_callback,
+ error_callback: backtrace_error_callback,
+ data: rawptr,
+ ) -> c.int ---
+ backtrace_syminfo :: proc(
+ state: ^backtrace_state,
+ addr: uintptr,
+ callback: backtrace_syminfo_callback,
+ error_callback: backtrace_error_callback,
+ data: rawptr,
+ ) -> c.int ---
+
+ // NOTE(bill): this is technically an internal procedure, but it is exposed
+ backtrace_free :: proc(
+ state: ^backtrace_state,
+ p: rawptr,
+ size: c.size_t, // unused
+ error_callback: backtrace_error_callback, // unused
+ data: rawptr, // unused
+ ) ---
+}
+
+Dl_info :: struct {
+ dli_fname: cstring,
+ dli_fbase: rawptr,
+ dli_sname: cstring,
+ dli_saddr: rawptr,
+}
+
+@(default_calling_convention="c")
+foreign libdl {
+ dladdr :: proc(addr: rawptr, info: ^Dl_info) -> c.int ---
+}
+
+@(private="package")
+_Context :: struct {
+ state: ^backtrace_state,
+}
+
+@(private="package")
+_init :: proc(ctx: ^Context) -> (ok: bool) {
+ defer if !ok do destroy(ctx)
+
+ ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
+ return ctx.impl.state != nil
+}
+
+@(private="package")
+_destroy :: proc(ctx: ^Context) -> bool {
+ if ctx != nil {
+ backtrace_free(ctx.impl.state, nil, 0, nil, nil)
+ }
+ return true
+}
+
+@(private="package")
+_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> (frames: []Frame) {
+ Backtrace_Context :: struct {
+ ctx: ^Context,
+ rt_ctx: runtime.Context,
+ frames: [MAX_FRAMES]Frame,
+ frame_count: int,
+ }
+
+ btc := &Backtrace_Context{
+ ctx = ctx,
+ rt_ctx = context,
+ }
+ backtrace_simple(
+ ctx.impl.state,
+ c.int(skip + 2),
+ proc "c" (user: rawptr, address: uintptr) -> c.int {
+ btc := (^Backtrace_Context)(user)
+ address := Frame(address)
+ if address == 0 {
+ return 1
+ }
+ btc.frames[btc.frame_count] = address
+ btc.frame_count += 1
+ return 0
+ },
+ nil,
+ btc,
+ )
+
+ res := btc.frames[:btc.frame_count]
+ if len(res) > 0 {
+ frames = make([]Frame, btc.frame_count, allocator)
+ copy(frames, res)
+ }
+ return
+}
+
+@(private="package")
+_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location {
+ Backtrace_Context :: struct {
+ rt_ctx: runtime.Context,
+ allocator: runtime.Allocator,
+ frame: Frame_Location,
+ }
+
+ btc := &Backtrace_Context{
+ rt_ctx = context,
+ allocator = allocator,
+ }
+ done := backtrace_pcinfo(
+ ctx.impl.state,
+ uintptr(frame),
+ proc "c" (data: rawptr, address: uintptr, file: cstring, line: c.int, symbol: cstring) -> c.int {
+ btc := (^Backtrace_Context)(data)
+ context = btc.rt_ctx
+
+ frame := &btc.frame
+
+ if file != nil {
+ frame.file_path = strings.clone_from_cstring(file, btc.allocator)
+ } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_fname != "" {
+ frame.file_path = strings.clone_from_cstring(info.dli_fname, btc.allocator)
+ }
+ if symbol != nil {
+ frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
+ } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_sname != "" {
+ frame.procedure = strings.clone_from_cstring(info.dli_sname, btc.allocator)
+ } else {
+ frame.procedure = fmt.aprintf("(procedure: 0x%x)", allocator=btc.allocator)
+ }
+ frame.line = i32(line)
+ return 0
+ },
+ nil,
+ btc,
+ )
+ if done != 0 {
+ return btc.frame
+ }
+
+ // NOTE(bill): pcinfo cannot resolve, but it might be possible to get the procedure name at least
+ backtrace_syminfo(
+ ctx.impl.state,
+ uintptr(frame),
+ proc "c" (data: rawptr, address: uintptr, symbol: cstring, _ignore0, _ignore1: uintptr) {
+ if symbol != nil {
+ btc := (^Backtrace_Context)(data)
+ context = btc.rt_ctx
+ btc.frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
+ }
+ },
+ nil,
+ btc,
+ )
+
+ return btc.frame
+} \ No newline at end of file
diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin
index ab08aee04..926bd18cc 100644
--- a/core/debug/trace/trace_nil.odin
+++ b/core/debug/trace/trace_nil.odin
@@ -1,4 +1,4 @@
-//+build !windows
+//+build !windows !linux
package debug_trace
_Context :: struct {