1// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module builtin
6
7// dbghelp.h is already included in cheaders.v
8#flag windows -l dbghelp
9
10pub struct SymbolInfo {
11pub mut:
12	f_size_of_struct u32 // must be 88 to be recognised by SymFromAddr
13	f_type_index u32 // Type Index of symbol
14	f_reserved [2]u64
15	f_index u32
16	f_size u32
17	f_mod_base u64 // Base Address of module comtaining this symbol
18	f_flags u32
19	f_value u64 // Value of symbol, ValuePresent should be 1
20	f_address u64 // Address of symbol including base address of module
21	f_register u32 // register holding value or pointer to value
22	f_scope u32 // scope of the symbol
23	f_tag u32 // pdb classification
24	f_name_len u32 // Actual length of name
25	f_max_name_len u32 // must be manually set
26	f_name byte // must be calloc(f_max_name_len)
27}
28
29pub struct SymbolInfoContainer {
30pub mut:
31	syminfo SymbolInfo
32	f_name_rest [254]char
33}
34
35pub struct Line64 {
36pub mut:
37	f_size_of_struct u32
38	f_key voidptr
39	f_line_number u32
40	f_file_name byteptr
41	f_address u64
42}
43
44fn C.SymSetOptions(symoptions u32) u32 // returns the current options mask
45fn C.GetCurrentProcess() voidptr // returns handle
46fn C.SymInitialize(h_process voidptr, p_user_search_path byteptr, b_invade_process int) int
47fn C.CaptureStackBackTrace(frames_to_skip u32, frames_to_capture u32, p_backtrace voidptr, p_backtrace_hash voidptr) u16
48fn C.SymFromAddr(h_process voidptr, address u64, p_displacement voidptr, p_symbol voidptr) int
49fn C.SymGetLineFromAddr64(h_process voidptr, address u64, p_displacement voidptr, p_line &Line64) int
50
51// Ref - https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions
52const (
53	symopt_undname = 0x00000002
54	symopt_deferred_loads = 0x00000004
55	symopt_no_cpp = 0x00000008
56	symopt_load_lines = 0x00000010
57	symopt_include_32bit_modules = 0x00002000
58	symopt_allow_zero_address = 0x01000000
59	symopt_debug = 0x80000000
60)
61
62fn builtin_init() {
63	if is_atty(1) > 0 {
64		C.SetConsoleMode(C.GetStdHandle(C.STD_OUTPUT_HANDLE), C.ENABLE_PROCESSED_OUTPUT | 0x0004) // enable_virtual_terminal_processing
65		unsafe {
66			C.setbuf(C.stdout, 0)
67		}
68	}
69	add_unhandled_exception_handler()
70}
71
72fn print_backtrace_skipping_top_frames(skipframes int) bool {
73	$if msvc {
74		return print_backtrace_skipping_top_frames_msvc(skipframes)
75	}
76	$if tinyc {
77		return print_backtrace_skipping_top_frames_tcc(skipframes)
78	}
79	$if mingw {
80		return print_backtrace_skipping_top_frames_mingw(skipframes)
81	}
82	eprintln('print_backtrace_skipping_top_frames is not implemented')
83	return false
84}
85
86fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool {
87$if msvc {
88	mut offset := u64(0)
89	backtraces := [100]voidptr
90	sic := SymbolInfoContainer{}
91	mut si := &sic.syminfo
92	si.f_size_of_struct = sizeof(SymbolInfo) // Note: C.SYMBOL_INFO is 88
93	si.f_max_name_len = sizeof(SymbolInfoContainer) - sizeof(SymbolInfo) - 1
94	fname := charptr( &si.f_name )
95	mut sline64 := Line64{}
96	sline64.f_size_of_struct = sizeof(Line64)
97
98	handle := C.GetCurrentProcess()
99	defer { C.SymCleanup(handle) }
100
101	C.SymSetOptions(symopt_debug | symopt_load_lines | symopt_undname)
102
103	syminitok := C.SymInitialize( handle, 0, 1)
104	if syminitok != 1 {
105		eprintln('Failed getting process: Aborting backtrace.\n')
106		return false
107	}
108
109	frames := int(C.CaptureStackBackTrace(skipframes + 1, 100, backtraces, 0))
110	if frames < 2 {
111		eprintln('C.CaptureStackBackTrace returned less than 2 frames')
112		return false
113	}
114	for i in 0..frames {
115		frame_addr := backtraces[i]
116		if C.SymFromAddr(handle, frame_addr, &offset, si) == 1 {
117			nframe := frames - i - 1
118			mut lineinfo := ''
119			if C.SymGetLineFromAddr64(handle, frame_addr, &offset, &sline64) == 1 {
120				file_name := tos3(sline64.f_file_name)
121				lineinfo = '${file_name}:${sline64.f_line_number}'
122			} else {
123				addr :
124				lineinfo = '?? : address = 0x${(&frame_addr):x}'
125			}
126			sfunc := tos3(fname)
127			eprintln('${nframe:-2d}: ${sfunc:-25s}  $lineinfo')
128		} else {
129			// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
130			cerr := int(C.GetLastError())
131			if cerr == 87 {
132				eprintln('SymFromAddr failure: $cerr = The parameter is incorrect)')
133			} else if cerr == 487 {
134				// probably caused because the .pdb isn't in the executable folder
135				eprintln('SymFromAddr failure: $cerr = Attempt to access invalid address (Verify that you have the .pdb file in the right folder.)')
136			} else {
137				eprintln('SymFromAddr failure: $cerr (see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes)')
138			}
139		}
140	}
141	return true
142} $else {
143	eprintln('print_backtrace_skipping_top_frames_msvc must be called only when the compiler is msvc')
144	return false
145}
146}
147
148fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool {
149	eprintln('print_backtrace_skipping_top_frames_mingw is not implemented')
150	return false
151}
152
153fn C.tcc_backtrace(fmt charptr, other ...charptr) int
154fn print_backtrace_skipping_top_frames_tcc(skipframes int) bool {
155	$if tinyc {
156		C.tcc_backtrace("Backtrace")
157		return false
158	} $else {
159		eprintln('print_backtrace_skipping_top_frames_tcc must be called only when the compiler is tcc')
160		return false
161	}
162}
163
164//TODO copypaste from os
165// we want to be able to use this here without having to `import os`
166struct ExceptionRecord {
167pub:
168	// status_ constants
169	code u32
170	flags u32
171
172	record &ExceptionRecord
173	address voidptr
174	param_count u32
175	// params []voidptr
176}
177
178 struct ContextRecord {
179	// TODO
180}
181
182struct ExceptionPointers {
183pub:
184	exception_record &ExceptionRecord
185	context_record &ContextRecord
186}
187
188type VectoredExceptionHandler fn(&ExceptionPointers)u32
189
190fn C.AddVectoredExceptionHandler(u32, VectoredExceptionHandler)
191fn add_vectored_exception_handler(handler VectoredExceptionHandler) {
192	C.AddVectoredExceptionHandler(1, handler)
193}
194
195[windows_stdcall]
196fn unhandled_exception_handler(e &ExceptionPointers) u32 {
197	match e.exception_record.code {
198		// These are 'used' by the backtrace printer
199		// so we dont want to catch them...
200		0x4001000A, 0x40010006 {
201			return 0
202		}
203		else {
204			println('Unhandled Exception 0x${e.exception_record.code:X}')
205			print_backtrace_skipping_top_frames(5)
206		}
207	}
208
209	return 0
210}
211
212fn add_unhandled_exception_handler() {
213	add_vectored_exception_handler(unhandled_exception_handler)
214}
215
216fn C.IsDebuggerPresent() bool
217fn C.__debugbreak()
218
219fn break_if_debugger_attached() {
220	$if tinyc {
221		unsafe {
222			mut ptr := &voidptr(0)
223			*ptr = 0
224		}
225	} $else {
226		if C.IsDebuggerPresent() {
227			C.__debugbreak()
228		}
229	}
230}
231