vxx2 / vlib / builtin / backtraces_nix.c.v
256 lines · 248 sloc · 7.42 KB · 099040eb391e8186e1bafd1906cb9dc01e20f07c
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 $if detailed_backtraces ? {
35 nr_actual_frames := nr_ptrs - skipframes
36 csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
37 atos_lines := bsd_backtrace_resolve_atos(&buffer[skipframes], nr_actual_frames)
38 for i in 0 .. nr_actual_frames {
39 sframe := unsafe { tos2(&u8(csymbols[i])) }
40 mut file_line := ''
41 if i < atos_lines.len {
42 file_line = atos_lines[i]
43 }
44 // macOS format: `0 main 0x00000001047232f8 veb__run_T_main__App_main__Context + 356`
45 symbol_start := sframe.index('0x') or { -1 }
46 if symbol_start < 0 {
47 continue
48 }
49 rest := sframe[symbol_start..]
50 space_after_addr := rest.index(' ') or { -1 }
51 if space_after_addr < 0 {
52 continue
53 }
54 symbol_and_offset := rest[space_after_addr + 1..]
55 plus_pos := symbol_and_offset.index(' + ') or { -1 }
56 mut raw_symbol := symbol_and_offset
57 if plus_pos >= 0 {
58 raw_symbol = symbol_and_offset[..plus_pos]
59 }
60 // Skip C runtime frames that are not V functions:
61 if raw_symbol in ['main', 'start', '_main'] {
62 continue
63 }
64 mut demangled := ''
65 if plus_pos >= 0 {
66 demangled = demangle_v_symbol(raw_symbol) + symbol_and_offset[plus_pos..]
67 } else {
68 demangled = demangle_v_symbol(raw_symbol)
69 }
70 if file_line.len > 0 {
71 eprint(file_line)
72 eprint_space_padding(file_line, 45)
73 eprint(' | ')
74 eprintln(demangled)
75 } else {
76 eprintln(demangled)
77 }
78 }
79 if nr_actual_frames > 0 {
80 unsafe { C.free(csymbols) }
81 }
82 } $else {
83 C.backtrace_symbols_fd(&buffer[skipframes], nr_ptrs - skipframes, 2)
84 }
85 }
86 return true
87 }
88}
89
90// bsd_backtrace_resolve_atos uses macOS `atos` to resolve addresses to file:line info.
91// Returns an array of file:line strings (one per frame). Empty string if unresolved.
92@[direct_array_access]
93fn bsd_backtrace_resolve_atos(buffer &voidptr, nr_frames int) []string {
94 $if macos {
95 exe_name := backtrace_current_executable_name()
96 if exe_name.len == 0 {
97 return []string{}
98 }
99 base_addr := voidptr(C._dyld_get_image_header(0))
100 if base_addr == unsafe { nil } {
101 return []string{}
102 }
103 // Build single atos command with all addresses for efficiency:
104 mut cmd := 'atos --fullPath -o "' + exe_name + '" -l ' + ptr_str(base_addr)
105 for i in 0 .. nr_frames {
106 cmd += ' ' + ptr_str(unsafe { buffer[i] })
107 }
108 f := C.popen(&char(cmd.str), c'r')
109 if f == unsafe { nil } {
110 return []string{}
111 }
112 buf := [4096]u8{}
113 mut lines := []string{cap: nr_frames}
114 unsafe {
115 bp := &u8(&buf[0])
116 for C.fgets(&char(bp), 4096, f) != 0 {
117 line := tos(bp, vstrlen(bp)).trim_chars(' \t\n\r', .trim_both)
118 // atos output format: `func_name (in binary) (file.v:42)`
119 // Extract the last parenthesized (file:line) part:
120 paren_pos := line.index_last_('(')
121 if paren_pos >= 0 {
122 file_part := line[paren_pos + 1..]
123 end_paren := file_part.index_last_(')')
124 if end_paren >= 0 {
125 file_line := file_part[..end_paren]
126 if file_line.contains(':') && !file_line.starts_with('in ')
127 && !file_line.contains('.tmp.c:') {
128 lines << file_line
129 continue
130 }
131 }
132 }
133 lines << ''
134 }
135 }
136 C.pclose(f)
137 return lines
138 }
139 return []string{}
140}
141
142fn C.tcc_backtrace(fmt &char) i32
143
144fn backtrace_current_executable_name() string {
145 args := arguments()
146 if args.len == 0 {
147 return ''
148 }
149 return args[0]
150}
151
152fn backtrace_addr2line_executable(executable string, current_executable_name string) string {
153 if executable.len == 0 {
154 return '/proc/self/exe'
155 }
156 if executable.contains('/') {
157 return executable
158 }
159 if current_executable_name.len > 0
160 && executable.all_after_last('/') == current_executable_name.all_after_last('/') {
161 return '/proc/self/exe'
162 }
163 return executable
164}
165
166fn backtrace_shell_quote(s string) string {
167 mut quoted := "'"
168 for i in 0 .. s.len {
169 if s[i] == `'` {
170 quoted += "'\\''"
171 } else {
172 quoted += s[i].ascii_str()
173 }
174 }
175 return quoted + "'"
176}
177
178@[direct_array_access]
179fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
180 $if android {
181 eprintln('On Android no backtrace is available.')
182 return false
183 }
184 $if no_backtrace ? {
185 return false
186 } $else {
187 $if linux && !freestanding {
188 $if !glibc {
189 $if tinyc {
190 C.tcc_backtrace(c'Backtrace')
191 } $else {
192 eprintln('backtrace_symbols is missing => printing backtraces is not available.')
193 eprintln('Some libc implementations like musl simply do not provide it.')
194 }
195 return false
196 } $else {
197 current_executable_name := backtrace_current_executable_name()
198 buffer := [100]voidptr{}
199 nr_ptrs := C.backtrace(&buffer[0], 100)
200 if nr_ptrs < 2 {
201 eprintln('C.backtrace returned less than 2 frames')
202 return false
203 }
204 nr_actual_frames := nr_ptrs - skipframes
205 //////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)
206 csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
207 for i in 0 .. nr_actual_frames {
208 sframe := unsafe { tos2(&u8(csymbols[i])) }
209 executable := sframe.all_before('(')
210 addr2line_executable := backtrace_addr2line_executable(executable,
211 current_executable_name)
212 addr := sframe.all_after('[').all_before(']')
213 beforeaddr := sframe.all_before('[')
214 cmd := 'addr2line -e ' + backtrace_shell_quote(addr2line_executable) + ' ' +
215 backtrace_shell_quote(addr)
216 // taken from os, to avoid depending on the os module inside builtin.v
217 f := C.popen(&char(cmd.str), c'r')
218 if f == unsafe { nil } {
219 eprintln(sframe)
220 continue
221 }
222 buf := [1000]u8{}
223 mut output := ''
224 unsafe {
225 bp := &u8(&buf[0])
226 for C.fgets(&char(bp), 1000, f) != 0 {
227 output += tos(bp, vstrlen(bp))
228 }
229 }
230 output = output.trim_chars(' \t\n', .trim_both) + ':'
231 if C.pclose(f) != 0 {
232 eprintln(sframe)
233 continue
234 }
235 if output in ['??:0:', '??:?:'] {
236 output = ''
237 }
238 // See http://wiki.dwarfstd.org/index.php?title=Path_Discriminators
239 // Note: it is shortened here to just d. , just so that it fits, and so
240 // that the common error file:lineno: line format is enforced.
241 output = output.replace(' (discriminator', ': (d.')
242 eprint(output)
243 eprint_space_padding(output, 55)
244 eprint(' | ')
245 eprint(addr)
246 eprint(' | ')
247 eprintln(demangle_backtrace_sym(beforeaddr))
248 }
249 if nr_actual_frames > 0 {
250 unsafe { C.free(csymbols) }
251 }
252 }
253 }
254 }
255 return true
256}
257