diff options
| author | Andreas T Jonsson <mail@andreasjonsson.se> | 2024-05-02 09:27:46 +0200 |
|---|---|---|
| committer | Andreas T Jonsson <mail@andreasjonsson.se> | 2024-05-02 09:27:46 +0200 |
| commit | 7feff1c11335be9c0d804c3ca93050b7d154aad8 (patch) | |
| tree | 62c89fcafad6b6c9445cb37153e62a6b23223d39 /core/debug | |
| parent | 6bbdbb4447b0a2b5b485ae4351016b05ae79758f (diff) | |
| parent | fd582015fe2bbaabc42f78caefec1bd95f4d1465 (diff) | |
Merged with master
Diffstat (limited to 'core/debug')
| -rw-r--r-- | core/debug/trace/doc.odin | 51 | ||||
| -rw-r--r-- | core/debug/trace/trace.odin | 47 | ||||
| -rw-r--r-- | core/debug/trace/trace_cpp.odin | 195 | ||||
| -rw-r--r-- | core/debug/trace/trace_nil.odin | 18 | ||||
| -rw-r--r-- | core/debug/trace/trace_windows.odin | 68 |
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 |