v2 / vlib / builtin / backtraces_nix.c.v
240 lines · 233 sloc · 7.08 KB · a7912e4258c4c5888503bda40e924a89ac7c21c3
Raw
1module builtin
2
3// print_backtrace_skipping_top_frames prints the backtrace skipping N top frames.
4pub fn print_backtrace_skipping_top_frames(xskipframes int) bool {
5 $if no_backtrace ? {
6 return false
7 } $else {
8 skipframes := xskipframes + 2
9 $if macos || freebsd || openbsd || netbsd {
10 return print_backtrace_skipping_top_frames_bsd(skipframes)
11 } $else $if linux {
12 return print_backtrace_skipping_top_frames_linux(skipframes)
13 } $else {
14 println('print_backtrace_skipping_top_frames is not implemented. skipframes: ${skipframes}')
15 }
16 }
17 return false
18}
19
20// the functions below are not called outside this file,
21// so there is no need to have their twins in builtin_windows.v
22@[direct_array_access]
23fn print_backtrace_skipping_top_frames_bsd(skipframes int) bool {
24 $if no_backtrace ? {
25 return false
26 } $else {
27 $if macos || freebsd || netbsd {
28 buffer := [100]voidptr{}
29 nr_ptrs := C.backtrace(&buffer[0], 100)
30 if nr_ptrs < 2 {
31 eprintln('C.backtrace returned less than 2 frames')
32 return false
33 }
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 }
81 }
82 return true
83 }
84}
85
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]
89fn 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 := voidptr(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 := &u8(&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
138fn C.tcc_backtrace(fmt &char) i32
139
140fn backtrace_current_executable_name() string {
141 args := arguments()
142 if args.len == 0 {
143 return ''
144 }
145 return args[0]
146}
147
148fn backtrace_addr2line_executable(executable string, current_executable_name string) string {
149 if executable.len == 0 {
150 return '/proc/self/exe'
151 }
152 if executable.contains('/') {
153 return executable
154 }
155 if current_executable_name.len > 0
156 && executable.all_after_last('/') == current_executable_name.all_after_last('/') {
157 return '/proc/self/exe'
158 }
159 return executable
160}
161
162@[direct_array_access]
163fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
164 $if android {
165 eprintln('On Android no backtrace is available.')
166 return false
167 }
168 $if no_backtrace ? {
169 return false
170 } $else {
171 $if linux && !freestanding {
172 $if tinyc {
173 C.tcc_backtrace(c'Backtrace')
174 return false
175 } $else {
176 $if !glibc {
177 eprintln('backtrace_symbols is missing => printing backtraces is not available.')
178 eprintln('Some libc implementations like musl simply do not provide it.')
179 return false
180 } $else {
181 current_executable_name := backtrace_current_executable_name()
182 buffer := [100]voidptr{}
183 nr_ptrs := C.backtrace(&buffer[0], 100)
184 if nr_ptrs < 2 {
185 eprintln('C.backtrace returned less than 2 frames')
186 return false
187 }
188 nr_actual_frames := nr_ptrs - skipframes
189 //////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)
190 csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
191 for i in 0 .. nr_actual_frames {
192 sframe := unsafe { tos2(&u8(csymbols[i])) }
193 executable := sframe.all_before('(')
194 addr2line_executable := backtrace_addr2line_executable(executable,
195 current_executable_name)
196 addr := sframe.all_after('[').all_before(']')
197 beforeaddr := sframe.all_before('[')
198 cmd := 'addr2line -e ' + addr2line_executable + ' ' + addr
199 // taken from os, to avoid depending on the os module inside builtin.v
200 f := C.popen(&char(cmd.str), c'r')
201 if f == unsafe { nil } {
202 eprintln(sframe)
203 continue
204 }
205 buf := [1000]u8{}
206 mut output := ''
207 unsafe {
208 bp := &u8(&buf[0])
209 for C.fgets(&char(bp), 1000, f) != 0 {
210 output += tos(bp, vstrlen(bp))
211 }
212 }
213 output = output.trim_chars(' \t\n', .trim_both) + ':'
214 if C.pclose(f) != 0 {
215 eprintln(sframe)
216 continue
217 }
218 if output in ['??:0:', '??:?:'] {
219 output = ''
220 }
221 // See http://wiki.dwarfstd.org/index.php?title=Path_Discriminators
222 // Note: it is shortened here to just d. , just so that it fits, and so
223 // that the common error file:lineno: line format is enforced.
224 output = output.replace(' (discriminator', ': (d.')
225 eprint(output)
226 eprint_space_padding(output, 55)
227 eprint(' | ')
228 eprint(addr)
229 eprint(' | ')
230 eprintln(demangle_backtrace_sym(beforeaddr))
231 }
232 if nr_actual_frames > 0 {
233 unsafe { C.free(csymbols) }
234 }
235 }
236 }
237 }
238 }
239 return true
240}
241