mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-28 17:04:34 +00:00
194 lines
5.2 KiB
Odin
194 lines
5.2 KiB
Odin
//+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 {
|
|
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
|
|
} |