diff --git a/vlib/builtin/backtraces.c.v b/vlib/builtin/backtraces.c.v new file mode 100644 index 00000000000000..3e07eb702b301f --- /dev/null +++ b/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) + } + } + } + } +} diff --git a/vlib/builtin/backtraces_nix.c.v b/vlib/builtin/backtraces_nix.c.v new file mode 100644 index 00000000000000..b4e29501e3fcd5 --- /dev/null +++ b/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 +} diff --git a/vlib/builtin/backtraces_windows.c.v b/vlib/builtin/backtraces_windows.c.v new file mode 100644 index 00000000000000..94ed18921af56b --- /dev/null +++ b/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 +} diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 138d886ae342af..b217e32af764cc 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -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. diff --git a/vlib/builtin/builtin_nix.c.v b/vlib/builtin/builtin_nix.c.v index 7d3f1befe0e24b..1c7cd43abefafc 100644 --- a/vlib/builtin/builtin_nix.c.v +++ b/vlib/builtin/builtin_nix.c.v @@ -3,143 +3,19 @@ // that can be found in the LICENSE file. module builtin -// pub fn vsyscall(id int -// - -/* -pub const ( - sys_write = 1 - sys_mkdir = 83 -) -const ( - stdin_value = 0 - stdout_value = 1 - stderr_value = 2 -) -*/ - fn builtin_init() { // Do nothing } -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 -} - fn break_if_debugger_attached() { unsafe { mut ptr := &voidptr(0) *ptr = nil - //_ = ptr } } -// These functions are Windows specific - provide dummys for *nix -pub fn winapi_lasterr_str() string { - return '' +[markused; noreturn] +pub fn panic_lasterr(base string) { + // TODO: use strerror_r and errno + panic(base + ' unknown') } - -[noreturn] -pub fn panic_lasterr() {} diff --git a/vlib/builtin/builtin_windows.c.v b/vlib/builtin/builtin_windows.c.v index aaa99be6af8f8f..1b5d4686799b4b 100644 --- a/vlib/builtin/builtin_windows.c.v +++ b/vlib/builtin/builtin_windows.c.v @@ -4,68 +4,6 @@ [has_globals] 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) -) - // g_original_codepage - used to restore the original windows console code page when exiting __global g_original_codepage = u32(0) @@ -102,111 +40,6 @@ fn builtin_init() { } } -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 -} - // TODO copypaste from os // we want to be able to use this here without having to `import os` struct ExceptionRecord {