245 lines
7.2 KiB
V
245 lines
7.2 KiB
V
module builtin
|
|
|
|
// print_backtrace_skipping_top_frames prints the backtrace skipping N top frames.
|
|
pub fn print_backtrace_skipping_top_frames(xskipframes int) bool {
|
|
$if no_backtrace ? {
|
|
return false
|
|
} $else {
|
|
skipframes := xskipframes + 2
|
|
$if macos || freebsd || openbsd || netbsd {
|
|
return print_backtrace_skipping_top_frames_bsd(skipframes)
|
|
} $else $if linux {
|
|
return print_backtrace_skipping_top_frames_linux(skipframes)
|
|
} $else {
|
|
println('print_backtrace_skipping_top_frames is not implemented. skipframes: ${skipframes}')
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// the functions below are not called outside this file,
|
|
// so there is no need to have their twins in builtin_windows.v
|
|
@[direct_array_access]
|
|
fn print_backtrace_skipping_top_frames_bsd(skipframes int) bool {
|
|
$if no_backtrace ? {
|
|
return false
|
|
} $else {
|
|
$if macos || freebsd || netbsd {
|
|
buffer := [100]voidptr{}
|
|
nr_ptrs := C.backtrace(&buffer[0], 100)
|
|
if nr_ptrs < 2 {
|
|
eprintln('C.backtrace returned less than 2 frames')
|
|
return false
|
|
}
|
|
$if detailed_backtraces ? {
|
|
nr_actual_frames := nr_ptrs - skipframes
|
|
csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
|
|
atos_lines := bsd_backtrace_resolve_atos(&buffer[skipframes], nr_actual_frames)
|
|
for i in 0 .. nr_actual_frames {
|
|
sframe := unsafe { tos2(&u8(csymbols[i])) }
|
|
mut file_line := ''
|
|
if i < atos_lines.len {
|
|
file_line = atos_lines[i]
|
|
}
|
|
// macOS format: `0 main 0x00000001047232f8 veb__run_T_main__App_main__Context + 356`
|
|
symbol_start := sframe.index('0x') or { -1 }
|
|
if symbol_start < 0 {
|
|
continue
|
|
}
|
|
rest := sframe[symbol_start..]
|
|
space_after_addr := rest.index(' ') or { -1 }
|
|
if space_after_addr < 0 {
|
|
continue
|
|
}
|
|
symbol_and_offset := rest[space_after_addr + 1..]
|
|
plus_pos := symbol_and_offset.index(' + ') or { -1 }
|
|
mut raw_symbol := symbol_and_offset
|
|
if plus_pos >= 0 {
|
|
raw_symbol = symbol_and_offset[..plus_pos]
|
|
}
|
|
// Skip C runtime frames that are not V functions:
|
|
if raw_symbol in ['main', 'start', '_main'] {
|
|
continue
|
|
}
|
|
mut demangled := ''
|
|
if plus_pos >= 0 {
|
|
demangled = demangle_v_symbol(raw_symbol) + symbol_and_offset[plus_pos..]
|
|
} else {
|
|
demangled = demangle_v_symbol(raw_symbol)
|
|
}
|
|
if file_line.len > 0 {
|
|
eprint(file_line)
|
|
eprint_space_padding(file_line, 45)
|
|
eprint(' | ')
|
|
eprintln(demangled)
|
|
} else {
|
|
eprintln(demangled)
|
|
}
|
|
}
|
|
if nr_actual_frames > 0 {
|
|
unsafe { C.free(csymbols) }
|
|
}
|
|
} $else {
|
|
C.backtrace_symbols_fd(&buffer[skipframes], nr_ptrs - skipframes, 2)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
// bsd_backtrace_resolve_atos uses macOS `atos` to resolve addresses to file:line info.
|
|
// Returns an array of file:line strings (one per frame). Empty string if unresolved.
|
|
@[direct_array_access]
|
|
fn bsd_backtrace_resolve_atos(buffer &voidptr, nr_frames int) []string {
|
|
$if macos {
|
|
exe_name := backtrace_current_executable_name()
|
|
if exe_name.len == 0 {
|
|
return []string{}
|
|
}
|
|
base_addr := voidptr(C._dyld_get_image_header(0))
|
|
if base_addr == unsafe { nil } {
|
|
return []string{}
|
|
}
|
|
// Build single atos command with all addresses for efficiency:
|
|
mut cmd := 'atos --fullPath -o "' + exe_name + '" -l ' + ptr_str(base_addr)
|
|
for i in 0 .. nr_frames {
|
|
cmd += ' ' + ptr_str(unsafe { buffer[i] })
|
|
}
|
|
f := C.popen(&char(cmd.str), c'r')
|
|
if f == unsafe { nil } {
|
|
return []string{}
|
|
}
|
|
buf := [4096]u8{}
|
|
mut lines := []string{cap: nr_frames}
|
|
unsafe {
|
|
bp := &u8(&buf[0])
|
|
for C.fgets(&char(bp), 4096, f) != 0 {
|
|
line := tos(bp, vstrlen(bp)).trim_chars(' \t\n\r', .trim_both)
|
|
// atos output format: `func_name (in binary) (file.v:42)`
|
|
// Extract the last parenthesized (file:line) part:
|
|
paren_pos := line.index_last_('(')
|
|
if paren_pos >= 0 {
|
|
file_part := line[paren_pos + 1..]
|
|
end_paren := file_part.index_last_(')')
|
|
if end_paren >= 0 {
|
|
file_line := file_part[..end_paren]
|
|
if file_line.contains(':') && !file_line.starts_with('in ')
|
|
&& !file_line.contains('.tmp.c:') {
|
|
lines << file_line
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
lines << ''
|
|
}
|
|
}
|
|
C.pclose(f)
|
|
return lines
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
fn C.tcc_backtrace(fmt &char) i32
|
|
|
|
fn backtrace_current_executable_name() string {
|
|
args := arguments()
|
|
if args.len == 0 {
|
|
return ''
|
|
}
|
|
return args[0]
|
|
}
|
|
|
|
fn backtrace_addr2line_executable(executable string, current_executable_name string) string {
|
|
if executable.len == 0 {
|
|
return '/proc/self/exe'
|
|
}
|
|
if executable.contains('/') {
|
|
return executable
|
|
}
|
|
if current_executable_name.len > 0
|
|
&& executable.all_after_last('/') == current_executable_name.all_after_last('/') {
|
|
return '/proc/self/exe'
|
|
}
|
|
return executable
|
|
}
|
|
|
|
@[direct_array_access]
|
|
fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
|
|
$if android {
|
|
eprintln('On Android no backtrace is available.')
|
|
return false
|
|
}
|
|
$if no_backtrace ? {
|
|
return false
|
|
} $else {
|
|
$if linux && !freestanding {
|
|
$if tinyc {
|
|
C.tcc_backtrace(c'Backtrace')
|
|
return false
|
|
} $else {
|
|
$if !glibc {
|
|
eprintln('backtrace_symbols is missing => printing backtraces is not available.')
|
|
eprintln('Some libc implementations like musl simply do not provide it.')
|
|
return false
|
|
} $else {
|
|
current_executable_name := backtrace_current_executable_name()
|
|
buffer := [100]voidptr{}
|
|
nr_ptrs := C.backtrace(&buffer[0], 100)
|
|
if nr_ptrs < 2 {
|
|
eprintln('C.backtrace returned less than 2 frames')
|
|
return false
|
|
}
|
|
nr_actual_frames := nr_ptrs - skipframes
|
|
//////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)
|
|
csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
|
|
for i in 0 .. nr_actual_frames {
|
|
sframe := unsafe { tos2(&u8(csymbols[i])) }
|
|
executable := sframe.all_before('(')
|
|
addr2line_executable := backtrace_addr2line_executable(executable,
|
|
current_executable_name)
|
|
addr := sframe.all_after('[').all_before(']')
|
|
beforeaddr := sframe.all_before('[')
|
|
cmd := 'addr2line -e ' + addr2line_executable + ' ' + addr
|
|
// taken from os, to avoid depending on the os module inside builtin.v
|
|
f := C.popen(&char(cmd.str), c'r')
|
|
if f == unsafe { nil } {
|
|
eprintln(sframe)
|
|
continue
|
|
}
|
|
buf := [1000]u8{}
|
|
mut output := ''
|
|
unsafe {
|
|
bp := &u8(&buf[0])
|
|
for C.fgets(&char(bp), 1000, f) != 0 {
|
|
output += tos(bp, vstrlen(bp))
|
|
}
|
|
}
|
|
output = output.trim_chars(' \t\n', .trim_both) + ':'
|
|
if C.pclose(f) != 0 {
|
|
eprintln(sframe)
|
|
continue
|
|
}
|
|
if output in ['??:0:', '??:?:'] {
|
|
output = ''
|
|
}
|
|
// See http://wiki.dwarfstd.org/index.php?title=Path_Discriminators
|
|
// Note: it is shortened here to just d. , just so that it fits, and so
|
|
// that the common error file:lineno: line format is enforced.
|
|
output = output.replace(' (discriminator', ': (d.')
|
|
eprint(output)
|
|
eprint_space_padding(output, 55)
|
|
eprint(' | ')
|
|
eprint(addr)
|
|
eprint(' | ')
|
|
eprintln(demangle_backtrace_sym(beforeaddr))
|
|
}
|
|
if nr_actual_frames > 0 {
|
|
unsafe { C.free(csymbols) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|