304 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			V
		
	
	
| // Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
 | |
| // Use of this source code is governed by an MIT license
 | |
| // that can be found in the LICENSE file.
 | |
| [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           byte // 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      &byte
 | |
| 	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 &byte, 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
 | |
| )
 | |
| 
 | |
| // g_original_codepage - used to restore the original windows console code page when exiting
 | |
| __global g_original_codepage = u32(0)
 | |
| 
 | |
| // utf8 to stdout needs C.SetConsoleOutputCP(C.CP_UTF8)
 | |
| fn C.GetConsoleOutputCP() u32
 | |
| 
 | |
| fn C.SetConsoleOutputCP(wCodePageID u32) bool
 | |
| 
 | |
| fn restore_codepage() {
 | |
| 	C.SetConsoleOutputCP(g_original_codepage)
 | |
| }
 | |
| 
 | |
| fn is_terminal(fd int) int {
 | |
| 	mut mode := u32(0)
 | |
| 	osfh := voidptr(C._get_osfhandle(fd))
 | |
| 	C.GetConsoleMode(osfh, voidptr(&mode))
 | |
| 	return int(mode)
 | |
| }
 | |
| 
 | |
| fn builtin_init() {
 | |
| 	g_original_codepage = C.GetConsoleOutputCP()
 | |
| 	C.SetConsoleOutputCP(C.CP_UTF8)
 | |
| 	C.atexit(restore_codepage)
 | |
| 	if is_terminal(1) > 0 {
 | |
| 		C.SetConsoleMode(C.GetStdHandle(C.STD_OUTPUT_HANDLE), C.ENABLE_PROCESSED_OUTPUT | C.ENABLE_WRAP_AT_EOL_OUTPUT | 0x0004) // enable_virtual_terminal_processing
 | |
| 		C.SetConsoleMode(C.GetStdHandle(C.STD_ERROR_HANDLE), C.ENABLE_PROCESSED_OUTPUT | C.ENABLE_WRAP_AT_EOL_OUTPUT | 0x0004) // enable_virtual_terminal_processing
 | |
| 		unsafe {
 | |
| 			C.setbuf(C.stdout, 0)
 | |
| 			C.setbuf(C.stderr, 0)
 | |
| 		}
 | |
| 	}
 | |
| 	$if !no_backtrace ? {
 | |
| 		add_unhandled_exception_handler()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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: &byte(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 {
 | |
| pub:
 | |
| 	// status_ constants
 | |
| 	code        u32
 | |
| 	flags       u32
 | |
| 	record      &ExceptionRecord
 | |
| 	address     voidptr
 | |
| 	param_count u32
 | |
| 	// params []voidptr
 | |
| }
 | |
| 
 | |
| struct ContextRecord {
 | |
| 	// TODO
 | |
| }
 | |
| 
 | |
| struct ExceptionPointers {
 | |
| pub:
 | |
| 	exception_record &ExceptionRecord
 | |
| 	context_record   &ContextRecord
 | |
| }
 | |
| 
 | |
| type VectoredExceptionHandler = fn (&ExceptionPointers) int
 | |
| 
 | |
| fn C.AddVectoredExceptionHandler(int, C.PVECTORED_EXCEPTION_HANDLER)
 | |
| 
 | |
| fn add_vectored_exception_handler(handler VectoredExceptionHandler) {
 | |
| 	C.AddVectoredExceptionHandler(1, C.PVECTORED_EXCEPTION_HANDLER(handler))
 | |
| }
 | |
| 
 | |
| [windows_stdcall]
 | |
| fn unhandled_exception_handler(e &ExceptionPointers) int {
 | |
| 	match e.exception_record.code {
 | |
| 		// These are 'used' by the backtrace printer
 | |
| 		// so we dont want to catch them...
 | |
| 		0x4001000A, 0x40010006 {
 | |
| 			return 0
 | |
| 		}
 | |
| 		else {
 | |
| 			println('Unhandled Exception 0x${e.exception_record.code:X}')
 | |
| 			print_backtrace_skipping_top_frames(5)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| fn add_unhandled_exception_handler() {
 | |
| 	add_vectored_exception_handler(VectoredExceptionHandler(voidptr(unhandled_exception_handler)))
 | |
| }
 | |
| 
 | |
| fn C.IsDebuggerPresent() bool
 | |
| 
 | |
| fn C.__debugbreak()
 | |
| 
 | |
| fn break_if_debugger_attached() {
 | |
| 	$if tinyc {
 | |
| 		unsafe {
 | |
| 			mut ptr := &voidptr(0)
 | |
| 			*ptr = voidptr(0)
 | |
| 			_ = ptr
 | |
| 		}
 | |
| 	} $else {
 | |
| 		if C.IsDebuggerPresent() {
 | |
| 			C.__debugbreak()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // return an error message generated from WinAPI's `LastError`
 | |
| pub fn winapi_lasterr_str() string {
 | |
| 	err_msg_id := C.GetLastError()
 | |
| 	if err_msg_id == 8 {
 | |
| 		// handle this case special since `FormatMessage()` might not work anymore
 | |
| 		return 'insufficient memory'
 | |
| 	}
 | |
| 	mut msgbuf := &u16(0)
 | |
| 	res := C.FormatMessage(C.FORMAT_MESSAGE_ALLOCATE_BUFFER | C.FORMAT_MESSAGE_FROM_SYSTEM | C.FORMAT_MESSAGE_IGNORE_INSERTS,
 | |
| 		C.NULL, err_msg_id, C.MAKELANGID(C.LANG_NEUTRAL, C.SUBLANG_DEFAULT), &msgbuf,
 | |
| 		0, C.NULL)
 | |
| 	err_msg := if res == 0 {
 | |
| 		'Win-API error $err_msg_id'
 | |
| 	} else {
 | |
| 		unsafe { string_from_wide(msgbuf) }
 | |
| 	}
 | |
| 	return err_msg
 | |
| }
 | |
| 
 | |
| // panic with an error message generated from WinAPI's `LastError`
 | |
| [noreturn]
 | |
| pub fn panic_lasterr(base string) {
 | |
| 	panic(base + winapi_lasterr_str())
 | |
| }
 |