From 897c99a9a388ff7ef71a3fc083cf785610ae498a Mon Sep 17 00:00:00 2001 From: Laytan Date: Wed, 26 Aug 2026 20:19:38 +0200 Subject: [PATCH] [core:debug/trace] remove `os`, `fmt`, `slice`, and `strconv` imports (#7408) * [core:debug/trace] remove `os`, `fmt`, `slice`, and `strconv` imports * [core:debug/trace]: print memory nicely A bit of a modified, minimal core:fmt `%m` (to not need `core:strconv`) --- core/debug/trace/allocator.odin | 63 ++++++-- core/debug/trace/trace.odin | 42 ++++-- core/debug/trace/trace_instrumentation.odin | 7 +- core/debug/trace/trace_linux.odin | 150 +++++++++++++++++++- 4 files changed, 229 insertions(+), 33 deletions(-) diff --git a/core/debug/trace/allocator.odin b/core/debug/trace/allocator.odin index febbcc4e1..5535b376f 100644 --- a/core/debug/trace/allocator.odin +++ b/core/debug/trace/allocator.odin @@ -3,7 +3,6 @@ package debug_trace import "base:runtime" -import "core:fmt" import "core:mem" import "core:sync" @@ -290,43 +289,49 @@ tracking_allocator_print_results :: proc(t: ^Tracking_Allocator, temp_allocator ALLOCATOR_MAX_BACKTRACES :: 16 for _, leak in t.allocation_map { - fmt.eprintfln("%v leaked %m", leak.location, leak.size) + runtime.print_caller_location(leak.location) + runtime.print_string(" leaked ") + _print_memory(leak.size) + runtime.print_byte('\n') defer i += 1 if i > ALLOCATOR_MAX_BACKTRACES { continue } - fmt.eprintln("[back trace]") + runtime.print_string("[back trace]\n") trace, err := resolve(leak.backtrace, temp_allocator, temp_allocator) if err != nil { - fmt.eprintfln("\tbacktrace error: %v", err) + runtime.print_string("\tbacktrace error: ") + runtime.print_string(resolve_err_string(err)) + runtime.print_string("\n") continue } defer locations_destroy(trace, temp_allocator) print(trace) - fmt.eprintln() + runtime.print_string("\n") } for bad_free, _ in t.bad_free_array { - fmt.eprintfln( - "%v allocation %p was freed badly", - bad_free.location, - bad_free.memory, - ) + runtime.print_caller_location(bad_free.location) + runtime.print_string(" allocation ") + runtime.print_u64(u64(uintptr(bad_free.memory))) + runtime.print_string(" was freed badly\n") defer i += 1 if i > ALLOCATOR_MAX_BACKTRACES { continue } - fmt.eprintln("[back trace]") + runtime.print_string("[back trace]\n") trace, err := resolve(bad_free.backtrace, temp_allocator, temp_allocator) if err != nil { - fmt.eprintfln("\tbacktrace error: %v", err) + runtime.print_string("\tbacktrace error: ") + runtime.print_string(resolve_err_string(err)) + runtime.print_string("\n") continue } defer locations_destroy(trace, temp_allocator) @@ -334,3 +339,37 @@ tracking_allocator_print_results :: proc(t: ^Tracking_Allocator, temp_allocator print(trace) } } + +@(rodata) +_MEMORY_UNITS := [?]string{"b", "kib", "mib", "gib", "tib", "pib", "eib"} + +_print_memory :: proc(size: int) { + assert(size >= 0) + + u := u64(size) + unit_idx := 0 + div: u64 = 1 + for u / div >= mem.Kilobyte && unit_idx < len(_MEMORY_UNITS) - 1 { + div *= mem.Kilobyte + unit_idx += 1 + } + + whole := u / div + rem := u % div + frac := (rem * 10 + div / 2) / div + if frac == 10 { + frac = 0 + whole += 1 + if whole == mem.Kilobyte && unit_idx < len(_MEMORY_UNITS) - 1 { + whole = 1 + unit_idx += 1 + } + } + + runtime.print_u64(whole) + if frac != 0 { + runtime.print_byte('.') + runtime.print_byte(byte('0' + frac)) + } + runtime.print_string(_MEMORY_UNITS[unit_idx]) +} diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin index 1f2a0b0de..42f1ac71c 100644 --- a/core/debug/trace/trace.odin +++ b/core/debug/trace/trace.odin @@ -3,7 +3,6 @@ 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. @@ -87,6 +86,16 @@ Resolve_Error :: enum { Resolve_Aborted, } +resolve_err_string :: proc(err: Resolve_Error) -> string { + switch err { + case .None: return "none" + case .Allocator_Error: return "allocator error" + case .Parse_Address_Failed: return "parse address failed" + case .Resolve_Aborted: return "resolve aborted" + case: return "unknown" + } +} + Location :: runtime.Source_Code_Location /* @@ -133,9 +142,11 @@ assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code 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) + runtime.print_string("could not get backtrace for assertion failure: ") + runtime.print_string(resolve_err_string(err)) + runtime.print_string("\n") } else { - fmt.eprintfln("[back trace]") + runtime.print_string("[back trace]\n") print(lines) locations_destroy(lines, context.temp_allocator) } @@ -153,26 +164,35 @@ Inputs: */ 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) + runtime.print_string(padding) + runtime.print_string("#") + runtime.print_int(i) + runtime.print_string(" ") + runtime.print_string(location.procedure) + runtime.print_string(" at ") + runtime.print_string(location.file_path) if location.line > 0 { when ODIN_ERROR_POS_STYLE == .Default { - fmt.eprintf("(%v", location.line) + runtime.print_string("(") + runtime.print_i64(i64(location.line)) if location.column > 0 { - fmt.eprintf(":%v)", location.column) - } else { - fmt.eprint(")") + runtime.print_string(":") + runtime.print_i64(i64(location.column)) } + runtime.print_string(")") } else when ODIN_ERROR_POS_STYLE == .Unix { - fmt.eprintf(":%v", location.line) + runtime.print_string(":") + runtime.print_i64(i64(location.line)) if location.column > 0 { - fmt.eprintf(":%v", location.column) + runtime.print_string(":") + runtime.print_i64(i64(location.column)) } } else { #panic("unhandled ODIN_ERROR_POS_STYLE") } } - fmt.eprintln() + runtime.print_string("\n") } } diff --git a/core/debug/trace/trace_instrumentation.odin b/core/debug/trace/trace_instrumentation.odin index fcc49ef77..dbadc4228 100644 --- a/core/debug/trace/trace_instrumentation.odin +++ b/core/debug/trace/trace_instrumentation.odin @@ -4,8 +4,6 @@ package debug_trace @require import "base:runtime" -@require import "core:slice" - when INSTRUMENTATION_MODE { when ODIN_OPTIMIZATION_MODE == .None { @@ -55,8 +53,11 @@ _locations_destroy :: proc(locations: []Location, allocator: runtime.Allocator) @(private="package") _resolve :: proc(bt: Capture, allocator, temp_allocator: runtime.Allocator) -> (out: []Location, err: Resolve_Error) { - clone, mem_err := slice.clone(bt, allocator) + clone, mem_err := make(Capture, len(bt), allocator) if mem_err != nil { return nil, .Allocator_Error } + for entry, i in bt { + clone[i] = entry + } return transmute([]Location)clone, nil } diff --git a/core/debug/trace/trace_linux.odin b/core/debug/trace/trace_linux.odin index 4e818f2ac..11b37952c 100644 --- a/core/debug/trace/trace_linux.odin +++ b/core/debug/trace/trace_linux.odin @@ -6,9 +6,8 @@ package debug_trace @require import "base:runtime" @require import "core:c" -@require import "core:os" -@require import "core:strconv" @require import "core:strings" +@require import "core:sys/posix" when !INSTRUMENTATION_MODE { @@ -153,19 +152,18 @@ _resolve :: proc(bt: Capture, allocator, temp_allocator: runtime.Allocator) -> ( } exec_and_fill :: proc(command: []string, locations: []Location, msgs: []cstring, allocator, temp_allocator: runtime.Allocator) -> (filled: int, err: Resolve_Error) { - state, stdout, stderr, perr := os.process_exec({command = command}, temp_allocator) + stdout, exec_errno, success := exec_symbolizer(command, temp_allocator) defer delete(stdout, temp_allocator) - defer delete(stderr, temp_allocator) count := len(command)-COMMAND_START_LEN // `SYMBOLIZER_PROGRAM` does not exist, lets fall back to unresolved info. - if perr == .Not_Exist { + if exec_errno == .ENOENT { fill_unresolved(locations, msgs, allocator) return count, nil } - if perr != nil || !state.success { + if !success { return 0, .Resolve_Aborted } @@ -201,18 +199,156 @@ _resolve :: proc(bt: Capture, allocator, temp_allocator: runtime.Allocator) -> ( colon_idx := strings.last_index_byte(location, ':') if colon_idx > 0 { line_str := location[colon_idx+1:] - if line_int, ok := strconv.parse_i64_of_base(line_str, 10); ok { + if line_int, ok := parse_line_number(line_str); ok { file_path = location[:colon_idx] line = i32(line_int) } } return + + parse_line_number :: proc(s: string) -> (n: i64, ok: bool) { + if len(s) == 0 { return } + for c in s { + if c < '0' || c > '9' { return } + n = n*10 + i64(c-'0') + } + return n, true + } } process_line :: proc(line: string, ok: bool) -> (string, Resolve_Error) { if !ok || line == "" { return "", .Resolve_Aborted } return strings.trim_right_space(line), nil } + + exec_symbolizer :: proc(command: []string, allocator: runtime.Allocator) -> (stdout: []byte, exec_errno: posix.Errno, ok: bool) { + cargs, cargs_err := make([]cstring, len(command)+1, allocator) + if cargs_err != nil { + return + } + defer delete(cargs, allocator) + + args_size := 0 + for arg in command { + args_size += len(arg)+1 + } + args, args_err := make([]byte, args_size, allocator) + if args_err != nil { + return + } + defer delete(args, allocator) + + offset := 0 + for arg, i in command { + cargs[i] = cstring(&args[offset]) + copy(args[offset:], arg) + offset += len(arg)+1 + } + + READ, WRITE :: 0, 1 + stdout_pipe, exec_pipe: [2]posix.FD + if posix.pipe(&stdout_pipe) != .OK { + return + } + if posix.pipe(&exec_pipe) != .OK { + posix.close(stdout_pipe[READ]) + posix.close(stdout_pipe[WRITE]) + return + } + if posix.fcntl(exec_pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(stdout_pipe[READ]) + posix.close(stdout_pipe[WRITE]) + posix.close(exec_pipe[READ]) + posix.close(exec_pipe[WRITE]) + return + } + + pid := posix.fork() + if pid == -1 { + posix.close(stdout_pipe[READ]) + posix.close(stdout_pipe[WRITE]) + posix.close(exec_pipe[READ]) + posix.close(exec_pipe[WRITE]) + return + } + + if pid == 0 { + abort :: proc(exec_fd: posix.FD) -> ! { + errno := posix.errno() + posix.write(exec_fd, ([^]byte)(&errno), size_of(errno)) + posix._exit(126) + } + + posix.close(stdout_pipe[READ]) + posix.close(exec_pipe[READ]) + if posix.dup2(stdout_pipe[WRITE], posix.STDOUT_FILENO) == -1 { + abort(exec_pipe[WRITE]) + } + + dev_null := posix.open("/dev/null", {.WRONLY}) + if dev_null == -1 || posix.dup2(dev_null, posix.STDERR_FILENO) == -1 { + abort(exec_pipe[WRITE]) + } + if dev_null != posix.STDERR_FILENO { + posix.close(dev_null) + } + if stdout_pipe[WRITE] != posix.STDOUT_FILENO { + posix.close(stdout_pipe[WRITE]) + } + + posix.execvp(cargs[0], raw_data(cargs)) + abort(exec_pipe[WRITE]) + } + + posix.close(stdout_pipe[WRITE]) + posix.close(exec_pipe[WRITE]) + + output: [dynamic]byte + output.allocator = allocator + read_ok := true + buf: [1024]byte + for { + n := posix.read(stdout_pipe[READ], raw_data(buf[:]), len(buf)) + if n > 0 { + if _, err := append(&output, ..buf[:n]); err != nil { + read_ok = false + } + } else if n == -1 && posix.errno() == .EINTR { + continue + } else { + read_ok = read_ok && n == 0 + break + } + } + posix.close(stdout_pipe[READ]) + + exec_errno = .NONE + for { + n := posix.read(exec_pipe[READ], ([^]byte)(&exec_errno), size_of(exec_errno)) + if n == -1 && posix.errno() == .EINTR { + continue + } + read_ok = read_ok && (n == 0 || n == size_of(exec_errno)) + break + } + posix.close(exec_pipe[READ]) + + status: c.int + for { + if posix.waitpid(pid, &status, {}) == -1 { + if posix.errno() == .EINTR { + continue + } + read_ok = false + } + break + } + + stdout = output[:] + ok = read_ok && exec_errno == nil && posix.WIFEXITED(status) && posix.WEXITSTATUS(status) == 0 + return + } + } } // INSTRUMENTATION_MODE