aboutsummaryrefslogtreecommitdiff
path: root/core/debug/trace
diff options
context:
space:
mode:
authorAndreas T Jonsson <mail@andreasjonsson.se>2024-05-02 09:27:46 +0200
committerAndreas T Jonsson <mail@andreasjonsson.se>2024-05-02 09:27:46 +0200
commit7feff1c11335be9c0d804c3ca93050b7d154aad8 (patch)
tree62c89fcafad6b6c9445cb37153e62a6b23223d39 /core/debug/trace
parent6bbdbb4447b0a2b5b485ae4351016b05ae79758f (diff)
parentfd582015fe2bbaabc42f78caefec1bd95f4d1465 (diff)
Merged with master
Diffstat (limited to 'core/debug/trace')
-rw-r--r--core/debug/trace/doc.odin51
-rw-r--r--core/debug/trace/trace.odin47
-rw-r--r--core/debug/trace/trace_cpp.odin195
-rw-r--r--core/debug/trace/trace_nil.odin18
-rw-r--r--core/debug/trace/trace_windows.odin68
5 files changed, 379 insertions, 0 deletions
diff --git a/core/debug/trace/doc.odin b/core/debug/trace/doc.odin
new file mode 100644
index 000000000..e65548769
--- /dev/null
+++ b/core/debug/trace/doc.odin
@@ -0,0 +1,51 @@
+/*
+A debug stack trace library. Only works when debug symbols are enabled `-debug`.
+
+Example:
+ import "base:runtime"
+ import "core:debug/trace"
+
+ import "core:fmt"
+
+ global_trace_ctx: trace.Context
+
+ debug_trace_assertion_failure_proc :: proc(prefix, message: string, loc := #caller_location) -> ! {
+ runtime.print_caller_location(loc)
+ runtime.print_string(" ")
+ runtime.print_string(prefix)
+ if len(message) > 0 {
+ runtime.print_string(": ")
+ runtime.print_string(message)
+ }
+ runtime.print_byte('\n')
+
+ ctx := &trace_ctx
+ if !trace.in_resolve(ctx) {
+ buf: [64]trace.Frame
+ runtime.print_string("Debug Trace:\n")
+ frames := trace.frames(ctx, 1, buf[:])
+ for f, i in frames {
+ fl := trace.resolve(ctx, f, context.temp_allocator)
+ if fl.loc.file_path == "" && fl.loc.line == 0 {
+ continue
+ }
+ runtime.print_caller_location(fl.loc)
+ runtime.print_string(" - frame ")
+ runtime.print_int(i)
+ runtime.print_byte('\n')
+ }
+ }
+ runtime.trap()
+ }
+
+ main :: proc() {
+ trace.init(&global_trace_ctx)
+ defer trace.destroy(&global_trace_ctx)
+
+ context.assertion_failure_proc = debug_trace_assertion_failure_proc
+
+ ...
+ }
+
+*/
+package debug_trace \ No newline at end of file
diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin
new file mode 100644
index 000000000..134609b05
--- /dev/null
+++ b/core/debug/trace/trace.odin
@@ -0,0 +1,47 @@
+package debug_trace
+
+import "base:intrinsics"
+import "base:runtime"
+
+Frame :: distinct uintptr
+
+Frame_Location :: struct {
+ using loc: runtime.Source_Code_Location,
+ allocator: runtime.Allocator,
+}
+
+delete_frame_location :: proc(fl: Frame_Location) -> runtime.Allocator_Error {
+ allocator := fl.allocator
+ delete(fl.loc.procedure, allocator) or_return
+ delete(fl.loc.file_path, allocator) or_return
+ return nil
+}
+
+Context :: struct {
+ in_resolve: bool, // atomic
+ impl: _Context,
+}
+
+init :: proc(ctx: ^Context) -> bool {
+ return _init(ctx)
+}
+
+destroy :: proc(ctx: ^Context) -> bool {
+ return _destroy(ctx)
+}
+
+@(require_results)
+frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
+ return _frames(ctx, skip, frames_buffer)
+}
+
+@(require_results)
+resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) {
+ return _resolve(ctx, frame, allocator)
+}
+
+
+@(require_results)
+in_resolve :: proc "contextless" (ctx: ^Context) -> bool {
+ return intrinsics.atomic_load(&ctx.in_resolve)
+} \ No newline at end of file
diff --git a/core/debug/trace/trace_cpp.odin b/core/debug/trace/trace_cpp.odin
new file mode 100644
index 000000000..894046c45
--- /dev/null
+++ b/core/debug/trace/trace_cpp.odin
@@ -0,0 +1,195 @@
+//+private file
+//+build linux, darwin
+package debug_trace
+
+import "base:intrinsics"
+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 "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> (frames: []Frame) {
+ Backtrace_Context :: struct {
+ ctx: ^Context,
+ frames: []Frame,
+ frame_count: int,
+ }
+
+ btc := &Backtrace_Context{
+ ctx = ctx,
+ frames = frames_buffer,
+ }
+ 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
+ }
+ if btc.frame_count == len(btc.frames) {
+ return 1
+ }
+ btc.frames[btc.frame_count] = address
+ btc.frame_count += 1
+ return 0
+ },
+ nil,
+ btc,
+ )
+
+ if btc.frame_count > 0 {
+ frames = btc.frames[:btc.frame_count]
+ }
+ return
+}
+
+@(private="package")
+_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location {
+ intrinsics.atomic_store(&ctx.in_resolve, true)
+ defer intrinsics.atomic_store(&ctx.in_resolve, false)
+
+ 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
new file mode 100644
index 000000000..40478898b
--- /dev/null
+++ b/core/debug/trace/trace_nil.odin
@@ -0,0 +1,18 @@
+//+build !windows !linux !darwin
+package debug_trace
+
+_Context :: struct {
+}
+
+_init :: proc(ctx: ^Context) -> (ok: bool) {
+ return true
+}
+_destroy :: proc(ctx: ^Context) -> bool {
+ return true
+}
+_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame {
+ return nil
+}
+_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) {
+ return
+}
diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin
new file mode 100644
index 000000000..de1461e96
--- /dev/null
+++ b/core/debug/trace/trace_windows.odin
@@ -0,0 +1,68 @@
+//+private
+//+build windows
+package debug_trace
+
+import "base:intrinsics"
+import "base:runtime"
+
+import win32 "core:sys/windows"
+import "core:fmt"
+
+_Context :: struct {
+ hProcess: win32.HANDLE,
+ lock: win32.SRWLOCK,
+}
+
+_init :: proc "contextless" (ctx: ^Context) -> (ok: bool) {
+ defer if !ok { _destroy(ctx) }
+ ctx.impl.hProcess = win32.GetCurrentProcess()
+ win32.SymInitialize(ctx.impl.hProcess, nil, true) or_return
+ win32.SymSetOptions(win32.SYMOPT_LOAD_LINES)
+ return true
+}
+
+_destroy :: proc "contextless" (ctx: ^Context) -> bool {
+ if ctx != nil {
+ win32.SymCleanup(ctx.impl.hProcess)
+ }
+ return true
+}
+
+_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
+ frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, u32(len(frames_buffer)), ([^]rawptr)(&frames_buffer[0]), nil)
+ for i in 0..<frame_count {
+ // NOTE: Return address is one after the call instruction so subtract a byte to
+ // end up back inside the call instruction which is needed for SymFromAddr.
+ frames_buffer[i] -= 1
+ }
+ return frames_buffer[:frame_count]
+}
+
+
+_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (fl: Frame_Location) {
+ intrinsics.atomic_store(&ctx.in_resolve, true)
+ defer intrinsics.atomic_store(&ctx.in_resolve, false)
+
+ // NOTE(bill): Dbghelp is not thread-safe
+ win32.AcquireSRWLockExclusive(&ctx.impl.lock)
+ defer win32.ReleaseSRWLockExclusive(&ctx.impl.lock)
+
+ data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte
+ symbol := (^win32.SYMBOL_INFOW)(&data[0])
+ symbol.SizeOfStruct = size_of(symbol)
+ symbol.MaxNameLen = 255
+ if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) {
+ fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator)
+ } else {
+ fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator)
+ }
+
+ line: win32.IMAGEHLP_LINE64
+ line.SizeOfStruct = size_of(line)
+ if win32.SymGetLineFromAddrW64(ctx.impl.hProcess, win32.DWORD64(frame), &{}, &line) {
+ fl.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator)
+ fl.line = i32(line.LineNumber)
+ }
+
+ return
+} \ No newline at end of file