From 51663520c8a74aa132cc5ab7dfd1521119ae2442 Mon Sep 17 00:00:00 2001 From: Steven Gay Date: Wed, 13 Nov 2019 19:05:06 +1100 Subject: [PATCH] MSVC backtrace --- vlib/builtin/builtin.v | 53 ++--------- vlib/builtin/builtin_nix.v | 77 +++++++++++++++ vlib/builtin/builtin_windows.v | 135 +++++++++++++++++++++++++++ vlib/compiler/cheaders.v | 3 + vlib/compiler/msvc.v | 2 +- vlib/compiler/tests/backtrace_test.v | 11 +++ vlib/os/os_windows.v | 1 - 7 files changed, 236 insertions(+), 46 deletions(-) create mode 100644 vlib/builtin/builtin_nix.v create mode 100644 vlib/builtin/builtin_windows.v create mode 100644 vlib/compiler/tests/backtrace_test.v diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index 3cac11d0bd..aef052fb13 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -31,55 +31,20 @@ fn on_panic(f fn (int) int) { } pub fn print_backtrace_skipping_top_frames(skipframes int) { - $if mac { - buffer := [100]byteptr - nr_ptrs := C.backtrace(*voidptr(buffer), 100) - C.backtrace_symbols_fd(*voidptr(&buffer[skipframes]), nr_ptrs-skipframes, 1) - return - } - $if linux { - $if !android { - $if glibc { - // backtrace is not available on Android. - //if C.backtrace_symbols_fd != 0 { - buffer := [100]byteptr - nr_ptrs := C.backtrace(*voidptr(buffer), 100) - nr_actual_frames := nr_ptrs-skipframes - mut sframes := []string - csymbols := *byteptr(C.backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)) - for i in 0..nr_actual_frames { sframes << tos2(csymbols[i]) } - for sframe in sframes { - executable := sframe.all_before('(') - addr := sframe.all_after('[').all_before(']') - cmd := 'addr2line -e $executable $addr' - - // taken from os, to avoid depending on the os module inside builtin.v - f := C.popen(cmd.str, 'r') - if isnil(f) { - println(sframe) continue - } - buf := [1000]byte - mut output := '' - for C.fgets(voidptr(buf), 1000, f) != 0 { - output += tos(buf, vstrlen(buf)) - } - output = output.trim_space()+':' - if 0 != int(C.pclose(f)) { - println(sframe) continue - } - println( '${output:-45s} | $sframe') - } - //C.backtrace_symbols_fd(*voidptr(&buffer[skipframes]), nr_actual_frames, 1) - return - }$else{ - C.printf('backtrace_symbols_fd is missing, so printing backtraces is not available.\n') - C.printf('Some libc implementations like musl simply do not provide it.\n') - } + $if windows { + $if msvc { + if print_backtrace_skipping_top_frames_msvc(skipframes) { return } } + $if mingw { + if print_backtrace_skipping_top_frames_mingw(skipframes) { return } + } + }$else{ + if print_backtrace_skipping_top_frames_nix(skipframes) { return } } println('print_backtrace_skipping_top_frames is not implemented on this platform for now...\n') } + pub fn print_backtrace(){ // at the time of backtrace_symbols_fd call, the C stack would look something like this: // 1 frame for print_backtrace_skipping_top_frames diff --git a/vlib/builtin/builtin_nix.v b/vlib/builtin/builtin_nix.v new file mode 100644 index 0000000000..5105f390ba --- /dev/null +++ b/vlib/builtin/builtin_nix.v @@ -0,0 +1,77 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool { + println('not implemented, see builtin_windows.v') + return false +} + +fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool { + println('not implemented, see builtin_windows.v') + return false +} + +fn print_backtrace_skipping_top_frames_nix(xskipframes int) bool { + skipframes := xskipframes + 2 + $if mac { return print_backtrace_skipping_top_frames_mac(skipframes) } + $if linux { return print_backtrace_skipping_top_frames_linux(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_mac(skipframes int) bool { + buffer := [100]byteptr + nr_ptrs := C.backtrace(*voidptr(buffer), 100) + C.backtrace_symbols_fd(*voidptr(&buffer[skipframes]), nr_ptrs-skipframes, 1) + return true +} + +fn print_backtrace_skipping_top_frames_linux(skipframes int) bool { + $if tinyc { + println('TODO: print_backtrace_skipping_top_frames_linux $skipframes with tcc fails tests with "stack smashing detected" .') + return false + } + $if !android { + $if glibc { + // backtrace is not available on Android. + //if C.backtrace_symbols_fd != 0 { + buffer := [100]byteptr + nr_ptrs := C.backtrace(*voidptr(buffer), 100) + nr_actual_frames := nr_ptrs-skipframes + mut sframes := []string + csymbols := *byteptr(C.backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)) + for i in 0..nr_actual_frames { sframes << tos2(csymbols[i]) } + for sframe in sframes { + executable := sframe.all_before('(') + addr := sframe.all_after('[').all_before(']') + cmd := 'addr2line -e $executable $addr' + + // taken from os, to avoid depending on the os module inside builtin.v + f := C.popen(cmd.str, 'r') + if isnil(f) { + println(sframe) continue + } + buf := [1000]byte + mut output := '' + for C.fgets(voidptr(buf), 1000, f) != 0 { + output += tos(buf, vstrlen(buf)) + } + output = output.trim_space()+':' + if 0 != int(C.pclose(f)) { + println(sframe) continue + } + println( '${output:-45s} | $sframe') + } + //C.backtrace_symbols_fd(*voidptr(&buffer[skipframes]), nr_actual_frames, 1) + return true + }$else{ + C.printf('backtrace_symbols_fd is missing, so printing backtraces is not available.\n') + C.printf('Some libc implementations like musl simply do not provide it.\n') + } + } + return false +} diff --git a/vlib/builtin/builtin_windows.v b/vlib/builtin/builtin_windows.v new file mode 100644 index 0000000000..c27f7c4139 --- /dev/null +++ b/vlib/builtin/builtin_windows.v @@ -0,0 +1,135 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +#include +#flag windows -l dbghelp + +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 byte // must be calloc(f_max_name_len) +} + +pub struct SymbolInfoContainer { +pub mut: + syminfo SymbolInfo + f_name_rest [254]char +} + +pub struct Line64 { + f_size_of_struct u32 + f_key voidptr + f_line_number u32 + f_file_name byteptr + f_address u64 +} + +fn C.SymSetOptions(symoptions u32) u32 // returns the current options mask +fn C.GetCurrentProcess() voidptr // returns handle +fn C.SymInitialize(h_process voidptr, p_user_search_path byteptr, 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 = 0x80000000 +) + +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{} + sline64.f_size_of_struct = sizeof(Line64) + + handle := C.GetCurrentProcess() + defer { C.SymCleanup(handle) } + + options := C.SymSetOptions(SYMOPT_DEBUG | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME) + syminitok := C.SymInitialize( handle, 0, 1) + if syminitok != 1 { + println('Failed getting process: Aborting backtrace.\n') + return true + } + + frames := int( C.CaptureStackBackTrace(skipframes + 1, 100, backtraces, 0) ) + for i:=0; i < frames; i++ { + // fugly pointer arithmetics follows ... + s := *voidptr( u64(backtraces) + u64(i*sizeof(voidptr)) ) + symfa_ok := C.SymFromAddr( handle, *s, &offset, si ) + if symfa_ok == 1 { + nframe := frames - i - 1 + mut lineinfo := '' + symglfa_ok := C.SymGetLineFromAddr64(handle, *s, &offset, &sline64) + if symglfa_ok == 1 { + lineinfo = ' ${sline64.f_file_name}:${sline64.f_line_number}' + } + else { + //cerr := int(C.GetLastError()) println('SymGetLineFromAddr64 failure: $cerr ') + lineinfo = ' ?? : address= $s' + } + sfunc := tos3(fname) + println('${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) { + println('SymFromAddr failure: $cerr = The parameter is incorrect)') + } + else if (cerr == 487) { + // probably caused because the .pdb isn't in the executable folder + println('SymFromAddr failure: $cerr = Attempt to access invalid address (Verify that you have the .pdb file in the right folder.)') + } + else { + println('SymFromAddr failure: $cerr (see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes)') + } + } + } + return true +} +$else { + println('TODO: Not implemented on Windows without msvc.') + return false +} +} + + +fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool { + println('TODO: print_backtrace_skipping_top_frames_mingw($skipframes)') + return false +} + +fn print_backtrace_skipping_top_frames_nix(skipframes int) bool { + println('not implemented, see builtin_nix.v') + return false +} diff --git a/vlib/compiler/cheaders.v b/vlib/compiler/cheaders.v index e6920c7a0c..3aee3abf38 100644 --- a/vlib/compiler/cheaders.v +++ b/vlib/compiler/cheaders.v @@ -19,6 +19,9 @@ CommonCHeaders = ' #include #include // sleep #else +#if defined(_MSC_VER) +#pragma comment(lib, "Dbghelp.lib") +#endif #if defined(__MSVCRT_VERSION__) && __MSVCRT_VERSION__ < __MSVCR90_DLL #error Please upgrade your MinGW distribution to use msvcr90.dll or later. #endif diff --git a/vlib/compiler/msvc.v b/vlib/compiler/msvc.v index 512f06873a..ef29b66d1d 100644 --- a/vlib/compiler/msvc.v +++ b/vlib/compiler/msvc.v @@ -3,7 +3,7 @@ module compiler import os #flag windows -l shell32 - +#flag windows -l dbghelp // RegOpenKeyExA etc #flag windows -l advapi32 diff --git a/vlib/compiler/tests/backtrace_test.v b/vlib/compiler/tests/backtrace_test.v new file mode 100644 index 0000000000..32509445c1 --- /dev/null +++ b/vlib/compiler/tests/backtrace_test.v @@ -0,0 +1,11 @@ +/* + Test for backtrace capability +*/ +fn a_method() { + print_backtrace() +} + +fn test_backtrace() { + a_method() + //panic('hi') +} diff --git a/vlib/os/os_windows.v b/vlib/os/os_windows.v index aaf7738b82..9fb5219270 100644 --- a/vlib/os/os_windows.v +++ b/vlib/os/os_windows.v @@ -86,7 +86,6 @@ fn init_os_args(argc int, argv &byteptr) []string { return args } - pub fn ls(path string) ?[]string { mut find_file_data := Win32finddata{} mut dir_files := []string