Pārlūkot izejas kodu

Add `core:debug/trace` for Linux

gingerBill 1 gadu atpakaļ
vecāks
revīzija
6c185a5dca
2 mainītis faili ar 192 papildinājumiem un 1 dzēšanām
  1. 191 0
      core/debug/trace/trace_linux.odin
  2. 1 1
      core/debug/trace/trace_nil.odin

+ 191 - 0
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
+}

+ 1 - 1
core/debug/trace/trace_nil.odin

@@ -1,4 +1,4 @@
-//+build !windows
+//+build !windows !linux
 package debug_trace
 package debug_trace
 
 
 _Context :: struct {
 _Context :: struct {