Skip to content

Commit

Permalink
builtin: extract backtraces.c.v, backtraces_nix.c.v, backtraces_windo…
Browse files Browse the repository at this point in the history
…ws.c.v (#19480)
  • Loading branch information
spytheman committed Sep 30, 2023
1 parent 80339c8 commit c030c5e
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 320 deletions.
26 changes: 26 additions & 0 deletions vlib/builtin/backtraces.c.v
@@ -0,0 +1,26 @@
module builtin

// print_backtrace shows a backtrace of the current call stack on stdout
pub fn print_backtrace() {
// At the time of backtrace_symbols_fd call, the C stack would look something like this:
// * print_backtrace_skipping_top_frames
// * print_backtrace itself
// * the rest of the backtrace frames
// => top 2 frames should be skipped, since they will not be informative to the developer
$if !no_backtrace ? {
$if freestanding {
println(bare_backtrace())
} $else {
$if tinyc {
C.tcc_backtrace(c'Backtrace')
} $else {
// NOTE: TCC doesn't have the unwind library
$if use_libbacktrace ? {
print_libbacktrace(1)
} $else {
print_backtrace_skipping_top_frames(2)
}
}
}
}
}
107 changes: 107 additions & 0 deletions vlib/builtin/backtraces_nix.c.v
@@ -0,0 +1,107 @@
module builtin

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
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
}
C.backtrace_symbols_fd(&buffer[skipframes], nr_ptrs - skipframes, 2)
}
return true
}
}

fn C.tcc_backtrace(fmt &char) int
fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
$if android {
eprintln('On Android no backtrace is available.')
return false
}
$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
}
$if no_backtrace ? {
return false
} $else {
$if linux && !freestanding {
$if tinyc {
C.tcc_backtrace(c'Backtrace')
return false
} $else {
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
mut sframes := []string{}
//////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 {
sframes << unsafe { tos2(&u8(csymbols[i])) }
}
for sframe in sframes {
executable := sframe.all_before('(')
addr := sframe.all_after('[').all_before(']')
beforeaddr := sframe.all_before('[')
cmd := 'addr2line -e ${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 := &buf[0]
for C.fgets(&char(bp), 1000, f) != 0 {
output += tos(bp, vstrlen(bp))
}
}
output = output.trim_space() + ':'
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.')
eprintln('${output:-55s} | ${addr:14s} | ${beforeaddr}')
}
}
}
}
return true
}
169 changes: 169 additions & 0 deletions vlib/builtin/backtraces_windows.c.v
@@ -0,0 +1,169 @@
module builtin

// dbghelp.h is already included in cheaders.v
#flag windows -l dbghelp

// SymbolInfo is used by print_backtrace_skipping_top_frames_msvc
pub struct SymbolInfo {
pub mut:
f_size_of_struct u32 // must be 88 to be recognised by SymFromAddr
f_type_index u32 // Type Index of symbol
f_reserved [2]u64
f_index u32
f_size u32
f_mod_base u64 // Base Address of module comtaining this symbol
f_flags u32
f_value u64 // Value of symbol, ValuePresent should be 1
f_address u64 // Address of symbol including base address of module
f_register u32 // register holding value or pointer to value
f_scope u32 // scope of the symbol
f_tag u32 // pdb classification
f_name_len u32 // Actual length of name
f_max_name_len u32 // must be manually set
f_name u8 // must be calloc(f_max_name_len)
}

pub struct SymbolInfoContainer {
pub mut:
syminfo SymbolInfo
f_name_rest [254]char
}

pub struct Line64 {
pub mut:
f_size_of_struct u32
f_key voidptr
f_line_number u32
f_file_name &u8 = unsafe { nil }
f_address u64
}

// returns the current options mask
fn C.SymSetOptions(symoptions u32) u32

// returns handle
fn C.GetCurrentProcess() voidptr

fn C.SymInitialize(h_process voidptr, p_user_search_path &u8, b_invade_process int) int

fn C.CaptureStackBackTrace(frames_to_skip u32, frames_to_capture u32, p_backtrace voidptr, p_backtrace_hash voidptr) u16

fn C.SymFromAddr(h_process voidptr, address u64, p_displacement voidptr, p_symbol voidptr) int

fn C.SymGetLineFromAddr64(h_process voidptr, address u64, p_displacement voidptr, p_line &Line64) int

// Ref - https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions
const (
symopt_undname = 0x00000002
symopt_deferred_loads = 0x00000004
symopt_no_cpp = 0x00000008
symopt_load_lines = 0x00000010
symopt_include_32bit_modules = 0x00002000
symopt_allow_zero_address = 0x01000000
symopt_debug = u32(0x80000000)
)

fn print_backtrace_skipping_top_frames(skipframes int) bool {
$if msvc {
return print_backtrace_skipping_top_frames_msvc(skipframes)
}
$if tinyc {
return print_backtrace_skipping_top_frames_tcc(skipframes)
}
$if mingw {
return print_backtrace_skipping_top_frames_mingw(skipframes)
}
eprintln('print_backtrace_skipping_top_frames is not implemented')
return false
}

fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool {
$if msvc {
mut offset := u64(0)
backtraces := [100]voidptr{}
sic := SymbolInfoContainer{}
mut si := &sic.syminfo
si.f_size_of_struct = sizeof(SymbolInfo) // Note: C.SYMBOL_INFO is 88
si.f_max_name_len = sizeof(SymbolInfoContainer) - sizeof(SymbolInfo) - 1
fname := &char(&si.f_name)
mut sline64 := Line64{
f_file_name: &u8(0)
}
sline64.f_size_of_struct = sizeof(Line64)

handle := C.GetCurrentProcess()
defer {
C.SymCleanup(handle)
}

C.SymSetOptions(symopt_debug | symopt_load_lines | symopt_undname)

syminitok := C.SymInitialize(handle, 0, 1)
if syminitok != 1 {
eprintln('Failed getting process: Aborting backtrace.\n')
return false
}

frames := int(C.CaptureStackBackTrace(skipframes + 1, 100, &backtraces[0], 0))
if frames < 2 {
eprintln('C.CaptureStackBackTrace returned less than 2 frames')
return false
}
for i in 0 .. frames {
frame_addr := backtraces[i]
if C.SymFromAddr(handle, frame_addr, &offset, si) == 1 {
nframe := frames - i - 1
mut lineinfo := ''
if C.SymGetLineFromAddr64(handle, frame_addr, &offset, &sline64) == 1 {
file_name := unsafe { tos3(sline64.f_file_name) }
lnumber := sline64.f_line_number
lineinfo = '${file_name}:${lnumber}'
} else {
// addr:
lineinfo = '?? : address = 0x${(&frame_addr):x}'
}
sfunc := unsafe { tos3(fname) }
eprintln('${nframe:-2d}: ${sfunc:-25s} ${lineinfo}')
} else {
// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
cerr := int(C.GetLastError())
if cerr == 87 {
eprintln('SymFromAddr failure: ${cerr} = The parameter is incorrect)')
} else if cerr == 487 {
// probably caused because the .pdb isn't in the executable folder
eprintln('SymFromAddr failure: ${cerr} = Attempt to access invalid address (Verify that you have the .pdb file in the right folder.)')
} else {
eprintln('SymFromAddr failure: ${cerr} (see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes)')
}
}
}
return true
} $else {
eprintln('print_backtrace_skipping_top_frames_msvc must be called only when the compiler is msvc')
return false
}
}

fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool {
eprintln('print_backtrace_skipping_top_frames_mingw is not implemented')
return false
}

fn C.tcc_backtrace(fmt &char) int

fn print_backtrace_skipping_top_frames_tcc(skipframes int) bool {
$if tinyc {
$if no_backtrace ? {
eprintln('backtraces are disabled')
return false
} $else {
C.tcc_backtrace(c'Backtrace')
return true
}
} $else {
eprintln('print_backtrace_skipping_top_frames_tcc must be called only when the compiler is tcc')
return false
}
// Not reachable, but it looks like it's not detectable by V
return false
}
25 changes: 0 additions & 25 deletions vlib/builtin/builtin.c.v
Expand Up @@ -690,31 +690,6 @@ fn v_fixed_index(i int, len int) int {
return i
}

// print_backtrace shows a backtrace of the current call stack on stdout
pub fn print_backtrace() {
// At the time of backtrace_symbols_fd call, the C stack would look something like this:
// * print_backtrace_skipping_top_frames
// * print_backtrace itself
// * the rest of the backtrace frames
// => top 2 frames should be skipped, since they will not be informative to the developer
$if !no_backtrace ? {
$if freestanding {
println(bare_backtrace())
} $else {
$if tinyc {
C.tcc_backtrace(c'Backtrace')
} $else {
// NOTE: TCC doesn't have the unwind library
$if use_libbacktrace ? {
print_libbacktrace(1)
} $else {
print_backtrace_skipping_top_frames(2)
}
}
}
}
}

// NOTE: g_main_argc and g_main_argv are filled in right after C's main start.
// They are used internally by V's builtin; for user code, it is much
// more convenient to just use `os.args` instead.
Expand Down

0 comments on commit c030c5e

Please sign in to comment.