#+vet explicit-allocators #+private file package back @require import "base:runtime" @require import "base:intrinsics" @require import "core:c" @require import "core:c/libc" @require import "core:os" @require import "core:strings" when !USE_FALLBACK { foreign import lib "system:c" @(private="package") _Trace_Entry :: rawptr @(private="package") _trace :: #force_no_inline proc(buf: Trace) -> (n: int) { // In order to omit this function's frame and the caller, we alloca a temp buffer with 2 extra slots. bigger_buf := ([^]Trace_Entry)(intrinsics.alloca((2 + len(buf)) * size_of(Trace_Entry), align_of(Trace_Entry)))[:len(buf)+2] _n := int(backtrace(raw_data(bigger_buf), i32(len(bigger_buf)))) if _n > 2 { copy(buf, bigger_buf[2:]) return _n-2 } return 0 } @(private="package") _lines_destroy :: proc(msgs: []Line, allocator: runtime.Allocator) { for msg in msgs { delete(msg.location, allocator) if msg.symbol != "??OOM" && msg.symbol != "??" { delete(msg.symbol, allocator) } } delete(msgs, allocator) } @(private="package") _lines :: proc(bt: Trace, allocator, temp_allocator: runtime.Allocator) -> (out: []Line, err: Lines_Error) { msgs := backtrace_symbols(raw_data(bt), i32(len(bt)))[:len(bt)] defer libc.free(raw_data(msgs)) out = make([]Line, len(bt), allocator) defer if err != nil { _lines_destroy(out, allocator) } // Debug info is needed. when !ODIN_DEBUG { for msg, i in msgs { location, mem_err := strings.clone_from(msg, allocator) if mem_err != nil { return out, .Out_Of_Memory } out[i] = Line { location = location, symbol = "??", } } return } i := 0 command := make([dynamic]string, temp_allocator) defer delete(command) if _, err := append(&command, ADDR2LINE_PATH, "--functions", "--exe", ""); err != nil { return out, .Out_Of_Memory } COMMAND_EXE_POS :: 3 COMMAND_START_LEN :: 4 for msg in msgs { exe, addr := parse_address(msg) or_return if command[COMMAND_EXE_POS] == "" { command[COMMAND_EXE_POS] = exe } else if command[COMMAND_EXE_POS] != exe { i += exec_and_fill(command[:], out[i:], msgs[i:], allocator, temp_allocator) or_return command[COMMAND_EXE_POS] = exe resize(&command, COMMAND_START_LEN) } if _, err := append(&command, addr); err != nil { return out, .Out_Of_Memory } } if len(command) > COMMAND_START_LEN { i += exec_and_fill(command[:], out[i:], msgs[i:], allocator, temp_allocator) or_return } return // Parses the exe and address out of a backtrace line. // Example: .../main(+0x20) [0x100000] -> .../main, +0x20, nil parse_address :: proc(cmsg: cstring) -> (string, string, Lines_Error) { msg := string(cmsg) close_idx := strings.last_index_byte(msg, ')') if close_idx < 1 { return "", "", .Parse_Address_Fail } open_idx := strings.last_index_byte(msg[:close_idx], '(') if open_idx < 0 { return "", "", .Parse_Address_Fail } return msg[:open_idx], msg[open_idx+1:close_idx], nil } exec_and_fill :: proc(command: []string, out: []Line, msgs: []cstring, allocator, temp_allocator: runtime.Allocator) -> (filled: int, err: Lines_Error) { state, stdout, stderr, perr := os.process_exec({command = command}, temp_allocator) defer delete(stdout, temp_allocator) defer delete(stderr, temp_allocator) if perr != nil || !state.success { return 0, .Addr2line_Process_Error } count := len(command)-COMMAND_START_LEN sstdout := string(stdout) for i in 0.. (string, Lines_Error) { if !ok { return "", .Addr2line_Unexpected_EOF } if line == "" { return "", .Addr2line_Output_Error } if line == "??" { return "??", nil } ret, err := strings.clone(strings.trim_right_space(line), allocator) return ret, err == nil ? nil : .Out_Of_Memory } } foreign lib { backtrace :: proc(buffer: [^]rawptr, size: c.int) -> c.int --- backtrace_symbols :: proc(buffer: [^]rawptr, size: c.int) -> [^]cstring --- backtrace_symbols_fd :: proc(buffer: [^]rawptr, size: c.int, fd: ^libc.FILE) --- } }