147 lines
4.5 KiB
Odin
147 lines
4.5 KiB
Odin
#+vet explicit-allocators
|
|
package back
|
|
|
|
import "base:runtime"
|
|
|
|
import "core:fmt"
|
|
import "core:io"
|
|
import "core:os"
|
|
import "core:sync"
|
|
import "core:text/table"
|
|
|
|
// Size of a constant backtrace, as used by the tracking allocator for example.
|
|
BACKTRACE_SIZE :: #config(BACKTRACE_SIZE, 16)
|
|
|
|
// For targets that do not have native support (using debug info),
|
|
// backtraces are done through instrumentation, Odin only allows one enter/exit instrumentation
|
|
// procedure though, so you can set this to true, add your own instrumentation procs, and have
|
|
// them call `back.other_instrumentation_enter` and `back.other_instrumentation_exit` to hook
|
|
// up the backtraces.
|
|
// The custom proc must have `#force_inline`.
|
|
OTHER_CUSTOM_INSTRUMENTATION :: #config(BACK_OTHER_CUSTOM_INSTRUMENTATION, false)
|
|
|
|
// Force the fallback instrumentation based implementation instead of debug info based.
|
|
FORCE_FALLBACK :: #config(BACK_FORCE_FALLBACK, false)
|
|
|
|
// Fallback requires a single module (subtle bugs with multiple modules and instrumentation in Odin),
|
|
// and at least -o:minimal (#force_inline has to actually inline).
|
|
_COULD_USE_FALLBACK_WITHOUT_ERROR :: !ODIN_USE_SEPARATE_MODULES && ODIN_OPTIMIZATION_MODE >= .Minimal
|
|
|
|
// Use the fallback (instrumentation based) implementation:
|
|
// if it is forced, or it can be used without error and debug info is off, or if the target has no debug info based support.
|
|
USE_FALLBACK :: FORCE_FALLBACK || (_COULD_USE_FALLBACK_WITHOUT_ERROR && !ODIN_DEBUG) || (ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .Windows)
|
|
|
|
ADDR2LINE_PATH :: #config(TRACE_ADDR2LINE_PATH, "addr2line")
|
|
|
|
Trace :: []Trace_Entry
|
|
|
|
Trace_Const :: struct {
|
|
trace: [BACKTRACE_SIZE]Trace_Entry,
|
|
len: int,
|
|
}
|
|
|
|
// Platform specific.
|
|
Trace_Entry :: _Trace_Entry
|
|
|
|
Line :: struct {
|
|
location: string,
|
|
symbol: string,
|
|
}
|
|
|
|
// TODO: improve errors.
|
|
Lines_Error :: enum {
|
|
None,
|
|
Parse_Address_Fail,
|
|
Addr2line_Unexpected_EOF,
|
|
Addr2line_Output_Error,
|
|
Addr2line_Unresolved,
|
|
Addr2line_Process_Error,
|
|
Out_Of_Memory,
|
|
Info_Not_Found,
|
|
}
|
|
|
|
// TODO: arbitrary skip (argument).
|
|
|
|
trace :: #force_no_inline proc() -> (bt: Trace_Const) {
|
|
bt.len = _trace(bt.trace[:])
|
|
return
|
|
}
|
|
|
|
trace_n :: #force_no_inline proc(max_len: i32, allocator := context.allocator) -> Trace {
|
|
bt := make([]Trace_Entry, max_len, allocator)
|
|
n := _trace(bt[:])
|
|
return bt[:n]
|
|
}
|
|
|
|
trace_fill :: #force_no_inline proc(buf: Trace) -> int {
|
|
return _trace(buf)
|
|
}
|
|
|
|
trace_n_destroy :: proc(b: Trace, allocator := context.allocator) {
|
|
delete(b, allocator)
|
|
}
|
|
|
|
// Processes the message trying to get more/useful information.
|
|
// This adds file and line information if the program is running in debug mode.
|
|
//
|
|
// If an error is returned the original message will be the result and is save to use.
|
|
lines :: proc {
|
|
lines_n,
|
|
lines_const,
|
|
}
|
|
|
|
lines_n :: proc(bt: Trace, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (out: []Line, err: Lines_Error) {
|
|
return _lines(bt, allocator, temp_allocator)
|
|
}
|
|
|
|
lines_const :: proc(bt: Trace_Const, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (out: []Line, err: Lines_Error) {
|
|
bt := bt
|
|
return _lines(bt.trace[:bt.len], allocator, temp_allocator)
|
|
}
|
|
|
|
lines_destroy :: proc(lines: []Line, allocator := context.allocator) {
|
|
_lines_destroy(lines, allocator)
|
|
}
|
|
|
|
assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
|
|
{
|
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
|
|
|
lines, err := lines(trace(), context.temp_allocator, context.temp_allocator)
|
|
if err != nil {
|
|
fmt.eprintf("could not get backtrace for assertion failure: %v\n", err)
|
|
} else {
|
|
fmt.eprintln("[back trace]")
|
|
print(lines, temp_allocator=context.temp_allocator)
|
|
}
|
|
}
|
|
|
|
runtime.default_assertion_failure_proc(prefix, message, loc)
|
|
}
|
|
|
|
register_segfault_handler :: proc() {
|
|
_register_segfault_handler()
|
|
}
|
|
|
|
print :: proc(lines: []Line, padding := " ", w: Maybe(io.Writer) = nil, temp_allocator := context.temp_allocator) {
|
|
w := w.? or_else os.to_writer(os.stderr)
|
|
|
|
tbl := table.init(&table.Table{}, temp_allocator, temp_allocator)
|
|
|
|
for line in lines {
|
|
table.row(tbl, padding, line.symbol, " - ", line.location)
|
|
}
|
|
|
|
table.build(tbl, table.unicode_width_proc)
|
|
|
|
for row in 0..<tbl.nr_rows {
|
|
for col in 0..<tbl.nr_cols {
|
|
table.write_table_cell(w, tbl, row, col)
|
|
}
|
|
io.write_byte(w, '\n')
|
|
}
|
|
}
|
|
|
|
// 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
|