v / vlib / builtin / backtraces_nix.c.v
244 lines · 237 sloc · 7.25 KB · 4b7955afe528e233a43f8586b923529e6d28c391
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
166@[direct_array_access]
167fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
168 $if android {
169 eprintln('On Android no backtrace is available.')
170 return false
171 }
172 $if no_backtrace ? {
173 return false
174 } $else {
175 $if linux && !freestanding {
176 $if tinyc {
177 C.tcc_backtrace(c'Backtrace')
178 return false
179 } $else {
180 $if !glibc {
181 eprintln('backtrace_symbols is missing => printing backtraces is not available.')
182 eprintln('Some libc implementations like musl simply do not provide it.')
183 return false
184 } $else {
185 current_executable_name := backtrace_current_executable_name()
186 buffer := [100]voidptr{}
187 nr_ptrs := C.backtrace(&buffer[0], 100)
188 if nr_ptrs < 2 {
189 eprintln('C.backtrace returned less than 2 frames')
190 return false
191 }
192 nr_actual_frames := nr_ptrs - skipframes
193 //////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)
194 csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
195 for i in 0 .. nr_actual_frames {
196 sframe := unsafe { tos2(&u8(csymbols[i])) }
197 executable := sframe.all_before('(')
198 addr2line_executable := backtrace_addr2line_executable(executable,
199 current_executable_name)
200 addr := sframe.all_after('[').all_before(']')
201 beforeaddr := sframe.all_before('[')
202 cmd := 'addr2line -e ' + addr2line_executable + ' ' + addr
203 // taken from os, to avoid depending on the os module inside builtin.v
204 f := C.popen(&char(cmd.str), c'r')
205 if f == unsafe { nil } {
206 eprintln(sframe)
207 continue
208 }
209 buf := [1000]u8{}
210 mut output := ''
211 unsafe {
212 bp := &u8(&buf[0])
213 for C.fgets(&char(bp), 1000, f) != 0 {
214 output += tos(bp, vstrlen(bp))
215 }
216 }
217 output = output.trim_chars(' \t\n', .trim_both) + ':'
218 if C.pclose(f) != 0 {
219 eprintln(sframe)
220 continue
221 }
222 if output in ['??:0:', '??:?:'] {
223 output = ''
224 }
225 // See http://wiki.dwarfstd.org/index.php?title=Path_Discriminators
226 // Note: it is shortened here to just d. , just so that it fits, and so
227 // that the common error file:lineno: line format is enforced.
228 output = output.replace(' (discriminator', ': (d.')
229 eprint(output)
230 eprint_space_padding(output, 55)
231 eprint(' | ')
232 eprint(addr)
233 eprint(' | ')
234 eprintln(demangle_backtrace_sym(beforeaddr))
235 }
236 if nr_actual_frames > 0 {
237 unsafe { C.free(csymbols) }
238 }
239 }
240 }
241 }
242 }
243 return true
244}
245