@@ -31,12 +31,110 @@ fn print_backtrace_skipping_top_frames_bsd(skipframes int) bool {
3131 eprintln ('C.backtrace returned less than 2 frames' )
3232 return false
3333 }
34- C.backtrace_symbols_fd (& buffer[skipframes], nr_ptrs - skipframes, 2 )
34+ nr_actual_frames := nr_ptrs - skipframes
35+ csymbols := C.backtrace_symbols (voidptr (& buffer[skipframes]), nr_actual_frames)
36+ atos_lines := bsd_backtrace_resolve_atos (& buffer[skipframes], nr_actual_frames)
37+ for i in 0 .. nr_actual_frames {
38+ sframe := unsafe { tos2 (& u8 (csymbols[i])) }
39+ mut file_line := ''
40+ if i < atos_lines.len {
41+ file_line = atos_lines[i]
42+ }
43+ // macOS format: `0 main 0x00000001047232f8 veb__run_T_main__App_main__Context + 356`
44+ symbol_start := sframe.index ('0x' ) or { - 1 }
45+ if symbol_start < 0 {
46+ continue
47+ }
48+ rest := sframe[symbol_start..]
49+ space_after_addr := rest.index (' ' ) or { - 1 }
50+ if space_after_addr < 0 {
51+ continue
52+ }
53+ symbol_and_offset := rest[space_after_addr + 1 ..]
54+ plus_pos := symbol_and_offset.index (' + ' ) or { - 1 }
55+ mut raw_symbol := symbol_and_offset
56+ if plus_pos > = 0 {
57+ raw_symbol = symbol_and_offset[..plus_pos]
58+ }
59+ // Skip C runtime frames that are not V functions:
60+ if raw_symbol in ['main' , 'start' , '_main' ] {
61+ continue
62+ }
63+ mut demangled := ''
64+ if plus_pos > = 0 {
65+ demangled = demangle_v_symbol (raw_symbol) + symbol_and_offset[plus_pos..]
66+ } else {
67+ demangled = demangle_v_symbol (raw_symbol)
68+ }
69+ if file_line.len > 0 {
70+ eprint (file_line)
71+ eprint_space_padding (file_line, 45 )
72+ eprint (' | ' )
73+ eprintln (demangled)
74+ } else {
75+ eprintln (demangled)
76+ }
77+ }
78+ if nr_actual_frames > 0 {
79+ unsafe { C.free (csymbols) }
80+ }
3581 }
3682 return true
3783 }
3884}
3985
86+ // bsd_backtrace_resolve_atos uses macOS `atos` to resolve addresses to file:line info.
87+ // Returns an array of file:line strings (one per frame). Empty string if unresolved.
88+ @[direct_array_access]
89+ fn bsd_backtrace_resolve_atos (buffer & voidptr , nr_frames int ) []string {
90+ $if macos {
91+ exe_name := backtrace_current_executable_name ()
92+ if exe_name.len == 0 {
93+ return []string {}
94+ }
95+ base_addr := C._dyld_get_image_header (0 )
96+ if base_addr == unsafe { nil } {
97+ return []string {}
98+ }
99+ // Build single atos command with all addresses for efficiency:
100+ mut cmd := 'atos --fullPath -o "' + exe_name + '" -l ' + ptr_str (base_addr)
101+ for i in 0 .. nr_frames {
102+ cmd + = ' ' + ptr_str (unsafe { buffer[i] })
103+ }
104+ f := C.popen (& char (cmd.str), c 'r' )
105+ if f == unsafe { nil } {
106+ return []string {}
107+ }
108+ buf := [4096 ]u8 {}
109+ mut lines := []string {cap: nr_frames}
110+ unsafe {
111+ bp := & buf[0 ]
112+ for C.fgets (& char (bp), 4096 , f) != 0 {
113+ line := tos (bp, vstrlen (bp)).trim_chars (' \t\n\r ' , .trim_both)
114+ // atos output format: `func_name (in binary) (file.v:42)`
115+ // Extract the last parenthesized (file:line) part:
116+ paren_pos := line.index_last_ ('(' )
117+ if paren_pos > = 0 {
118+ file_part := line[paren_pos + 1 ..]
119+ end_paren := file_part.index_last_ (')' )
120+ if end_paren > = 0 {
121+ file_line := file_part[..end_paren]
122+ if file_line.contains (':' ) && ! file_line.starts_with ('in ' )
123+ && ! file_line.contains ('.tmp.c:' ) {
124+ lines << file_line
125+ continue
126+ }
127+ }
128+ }
129+ lines << ''
130+ }
131+ }
132+ C.pclose (f)
133+ return lines
134+ }
135+ return []string {}
136+ }
137+
40138fn C.tcc_backtrace (fmt & char) i32
41139
42140fn backtrace_current_executable_name () string {
@@ -132,7 +230,7 @@ fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
132230 eprint (' | ' )
133231 eprint (addr)
134232 eprint (' | ' )
135- eprintln (beforeaddr)
233+ eprintln (demangle_backtrace_sym ( beforeaddr) )
136234 }
137235 if nr_actual_frames > 0 {
138236 unsafe { C.free (csymbols) }
0 commit comments