| 1 | module builtin |
| 2 | |
| 3 | // print_backtrace_skipping_top_frames prints the backtrace skipping N top frames. |
| 4 | pub 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] |
| 23 | fn 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] |
| 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 := 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 | |
| 138 | fn C.tcc_backtrace(fmt &char) i32 |
| 139 | |
| 140 | fn backtrace_current_executable_name() string { |
| 141 | args := arguments() |
| 142 | if args.len == 0 { |
| 143 | return '' |
| 144 | } |
| 145 | return args[0] |
| 146 | } |
| 147 | |
| 148 | fn 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] |
| 163 | fn 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 | |