[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`)
This commit is contained in:
Laytan
2026-08-26 20:19:38 +02:00
committed by GitHub
parent 9909676141
commit 897c99a9a3
4 changed files with 229 additions and 33 deletions

View File

@@ -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])
}

View File

@@ -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")
}
}

View File

@@ -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
}

View File

@@ -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