mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-01 03:38:55 +00:00
185 lines
5.0 KiB
Odin
185 lines
5.0 KiB
Odin
#+vet explicit-allocators
|
|
package debug_trace
|
|
|
|
import "base:runtime"
|
|
|
|
import "core:fmt"
|
|
import "core:sync"
|
|
|
|
// Size of a constant backtrace, as used by the tracking allocator for example.
|
|
BACKTRACE_SIZE :: #config(ODIN_TRACE_SIZE, 16)
|
|
|
|
/*
|
|
Use the instrumentation based trace mode, instead of debug info based.
|
|
This mode has a little bit of runtime performance impact, but is supported on all targets.
|
|
*/
|
|
INSTRUMENTATION_MODE :: #config(ODIN_TRACE_INSTRUMENTATION_MODE, false)
|
|
|
|
/*
|
|
When using the instrumentation mode, but you also want to use Odin's instrumentation features
|
|
for something else. You can define this, and call this package's instrumentation procedures:
|
|
`instrumentation_enter`, and `instrumentation_exit`, in your own instrumentation enter/exit procedures.
|
|
*/
|
|
CUSTOM_INSTRUMENTATION :: #config(ODIN_TRACE_CUSTOM_INSTRUMENTATION, false)
|
|
|
|
/*
|
|
The path/command to invoke for symbolization.
|
|
Linux only.
|
|
*/
|
|
SYMBOLIZER_PROGRAM :: #config(ODIN_TRACE_SYMBOLIZER_PROGRAM, "addr2line")
|
|
|
|
// The string that is used when allocation failed for a symbol/file path.
|
|
OOM_MARKER :: "??OOM"
|
|
|
|
Capture :: []Capture_Entry
|
|
|
|
Capture_Const :: struct {
|
|
trace: [BACKTRACE_SIZE]Capture_Entry,
|
|
len: int,
|
|
}
|
|
|
|
// Platform specific.
|
|
Capture_Entry :: distinct _Capture_Entry
|
|
|
|
/*
|
|
Capture a constantly sized trace (defined by `-define:ODIN_TRACE_SIZE`).
|
|
|
|
The trace starts at the stack frame that called this procedure,
|
|
you can start higher up the stack using the `skip` argument.
|
|
*/
|
|
capture :: #force_no_inline proc(skip := 0) -> (bt: Capture_Const) {
|
|
bt.len = _capture(bt.trace[:], skip)
|
|
return
|
|
}
|
|
|
|
/*
|
|
Capture must be deleted by the caller.
|
|
*/
|
|
|
|
/*
|
|
Capture a trace of the given size.
|
|
|
|
The trace starts at the stack frame that called this procedure,
|
|
you can start higher up the stack using the `skip` argument.
|
|
|
|
The capture must be deleted by the caller.
|
|
*/
|
|
capture_n :: #force_no_inline proc(max_len: i32, skip := 0, allocator := context.allocator) -> Capture {
|
|
bt := make([]Capture_Entry, max_len, allocator)
|
|
n := _capture(bt[:], skip)
|
|
return bt[:n]
|
|
}
|
|
|
|
/*
|
|
Capture a trace into the given preallocated capture buffer (owned by the caller).
|
|
|
|
The trace starts at the stack frame that called this procedure,
|
|
you can start higher up the stack using the `skip` argument.
|
|
*/
|
|
capture_fill :: #force_no_inline proc(buf: Capture, skip := 0) -> int {
|
|
return _capture(buf, skip)
|
|
}
|
|
|
|
Resolve_Error :: enum {
|
|
None,
|
|
Allocator_Error,
|
|
Parse_Address_Failed,
|
|
Resolve_Aborted,
|
|
Resolve_Failed,
|
|
}
|
|
|
|
Location :: runtime.Source_Code_Location
|
|
|
|
/*
|
|
Resolve the back trace into source code locations, if possible.
|
|
|
|
Compile with `-debug` (or use `-define:ODIN_TRACE_INSTRUMENTATION_MODE`) for the most useful information.
|
|
|
|
The result must be destroyed using `locations_destroy`.
|
|
*/
|
|
resolve :: proc {
|
|
resolve_n,
|
|
resolve_const,
|
|
}
|
|
|
|
/*
|
|
See the procedure group `resolve`.
|
|
*/
|
|
resolve_n :: proc(bt: Capture, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (out: []Location, err: Resolve_Error) {
|
|
return _resolve(bt, allocator, temp_allocator)
|
|
}
|
|
|
|
/*
|
|
See the procedure group `resolve`.
|
|
*/
|
|
resolve_const :: proc(bt: Capture_Const, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (out: []Location, err: Resolve_Error) {
|
|
bt := bt
|
|
return _resolve(bt.trace[:bt.len], allocator, temp_allocator)
|
|
}
|
|
|
|
locations_destroy :: proc(locations: []Location, allocator := context.allocator) {
|
|
_locations_destroy(locations, allocator)
|
|
}
|
|
|
|
/*
|
|
An assertion failure procedure that prints a back trace.
|
|
|
|
Example:
|
|
context.assertion_failure_proc = trace.assertion_failure_proc
|
|
assert(false)
|
|
*/
|
|
assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
|
|
{
|
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
|
|
|
lines, err := resolve(capture(skip=1), context.temp_allocator, context.temp_allocator)
|
|
if err != nil {
|
|
fmt.eprintfln("could not get backtrace for assertion failure: %v", err)
|
|
} else {
|
|
fmt.eprintfln("[back trace]")
|
|
print(lines)
|
|
locations_destroy(lines, context.temp_allocator)
|
|
}
|
|
}
|
|
|
|
runtime.default_assertion_failure_proc(prefix, message, loc)
|
|
}
|
|
|
|
/*
|
|
Print locations to stderr.
|
|
|
|
Inputs:
|
|
- locations: the result of a `resolve` call.
|
|
- padding: padding to print before each line, defaults to a tab.
|
|
*/
|
|
print :: proc(locations: []Location, padding := "\t") {
|
|
for location, i in locations {
|
|
fmt.eprintf("%s#%v %v at %v", padding, i, location.procedure, location.file_path)
|
|
if location.line > 0 {
|
|
when ODIN_ERROR_POS_STYLE == .Default {
|
|
fmt.eprintf("(%v", location.line)
|
|
if location.column > 0 {
|
|
fmt.eprintf(":%v)", location.column)
|
|
} else {
|
|
fmt.eprint(")")
|
|
}
|
|
} else when ODIN_ERROR_POS_STYLE == .Unix {
|
|
fmt.eprintf(":%v", location.line)
|
|
if location.column > 0 {
|
|
fmt.eprintf(":%v", location.column)
|
|
}
|
|
} else {
|
|
#panic("unhandled ODIN_ERROR_POS_STYLE")
|
|
}
|
|
}
|
|
|
|
fmt.eprintln()
|
|
}
|
|
}
|
|
|
|
/*
|
|
The dbghelp library of win32 is not thread safe, this library uses this mutex to get exclusive access.
|
|
It is provided in case you want to use the dbghelp library, and want to coordinate access with this package.
|
|
*/
|
|
_win32_dbghelp_mutex: sync.Mutex
|