1 | // Copyright (c) 2019-2023 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 | [has_globals] |
5 | module builtin |
6 | |
7 | // dbghelp.h is already included in cheaders.v |
8 | #flag windows -l dbghelp |
9 | // SymbolInfo is used by print_backtrace_skipping_top_frames_msvc |
10 | pub struct SymbolInfo { |
11 | pub 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 u8 // must be calloc(f_max_name_len) |
27 | } |
28 | |
29 | pub struct SymbolInfoContainer { |
30 | pub mut: |
31 | syminfo SymbolInfo |
32 | f_name_rest [254]char |
33 | } |
34 | |
35 | pub struct Line64 { |
36 | pub mut: |
37 | f_size_of_struct u32 |
38 | f_key voidptr |
39 | f_line_number u32 |
40 | f_file_name &u8 = unsafe { nil } |
41 | f_address u64 |
42 | } |
43 | |
44 | // returns the current options mask |
45 | fn C.SymSetOptions(symoptions u32) u32 |
46 | |
47 | // returns handle |
48 | fn C.GetCurrentProcess() voidptr |
49 | |
50 | fn C.SymInitialize(h_process voidptr, p_user_search_path &u8, b_invade_process int) int |
51 | |
52 | fn C.CaptureStackBackTrace(frames_to_skip u32, frames_to_capture u32, p_backtrace voidptr, p_backtrace_hash voidptr) u16 |
53 | |
54 | fn C.SymFromAddr(h_process voidptr, address u64, p_displacement voidptr, p_symbol voidptr) int |
55 | |
56 | fn C.SymGetLineFromAddr64(h_process voidptr, address u64, p_displacement voidptr, p_line &Line64) int |
57 | |
58 | // Ref - https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions |
59 | const ( |
60 | symopt_undname = 0x00000002 |
61 | symopt_deferred_loads = 0x00000004 |
62 | symopt_no_cpp = 0x00000008 |
63 | symopt_load_lines = 0x00000010 |
64 | symopt_include_32bit_modules = 0x00002000 |
65 | symopt_allow_zero_address = 0x01000000 |
66 | symopt_debug = u32(0x80000000) |
67 | ) |
68 | |
69 | // g_original_codepage - used to restore the original windows console code page when exiting |
70 | __global g_original_codepage = u32(0) |
71 | |
72 | // utf8 to stdout needs C.SetConsoleOutputCP(C.CP_UTF8) |
73 | fn C.GetConsoleOutputCP() u32 |
74 | |
75 | fn C.SetConsoleOutputCP(wCodePageID u32) bool |
76 | |
77 | fn restore_codepage() { |
78 | C.SetConsoleOutputCP(g_original_codepage) |
79 | } |
80 | |
81 | fn is_terminal(fd int) int { |
82 | mut mode := u32(0) |
83 | osfh := voidptr(C._get_osfhandle(fd)) |
84 | C.GetConsoleMode(osfh, voidptr(&mode)) |
85 | return int(mode) |
86 | } |
87 | |
88 | fn builtin_init() { |
89 | g_original_codepage = C.GetConsoleOutputCP() |
90 | C.SetConsoleOutputCP(C.CP_UTF8) |
91 | C.atexit(restore_codepage) |
92 | if is_terminal(1) > 0 { |
93 | C.SetConsoleMode(C.GetStdHandle(C.STD_OUTPUT_HANDLE), C.ENABLE_PROCESSED_OUTPUT | C.ENABLE_WRAP_AT_EOL_OUTPUT | 0x0004) // enable_virtual_terminal_processing |
94 | C.SetConsoleMode(C.GetStdHandle(C.STD_ERROR_HANDLE), C.ENABLE_PROCESSED_OUTPUT | C.ENABLE_WRAP_AT_EOL_OUTPUT | 0x0004) // enable_virtual_terminal_processing |
95 | unsafe { |
96 | C.setbuf(C.stdout, 0) |
97 | C.setbuf(C.stderr, 0) |
98 | } |
99 | } |
100 | $if !no_backtrace ? { |
101 | add_unhandled_exception_handler() |
102 | } |
103 | } |
104 | |
105 | fn print_backtrace_skipping_top_frames(skipframes int) bool { |
106 | $if msvc { |
107 | return print_backtrace_skipping_top_frames_msvc(skipframes) |
108 | } |
109 | $if tinyc { |
110 | return print_backtrace_skipping_top_frames_tcc(skipframes) |
111 | } |
112 | $if mingw { |
113 | return print_backtrace_skipping_top_frames_mingw(skipframes) |
114 | } |
115 | eprintln('print_backtrace_skipping_top_frames is not implemented') |
116 | return false |
117 | } |
118 | |
119 | fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool { |
120 | $if msvc { |
121 | mut offset := u64(0) |
122 | backtraces := [100]voidptr{} |
123 | sic := SymbolInfoContainer{} |
124 | mut si := &sic.syminfo |
125 | si.f_size_of_struct = sizeof(SymbolInfo) // Note: C.SYMBOL_INFO is 88 |
126 | si.f_max_name_len = sizeof(SymbolInfoContainer) - sizeof(SymbolInfo) - 1 |
127 | fname := &char(&si.f_name) |
128 | mut sline64 := Line64{ |
129 | f_file_name: &u8(0) |
130 | } |
131 | sline64.f_size_of_struct = sizeof(Line64) |
132 | |
133 | handle := C.GetCurrentProcess() |
134 | defer { |
135 | C.SymCleanup(handle) |
136 | } |
137 | |
138 | C.SymSetOptions(symopt_debug | symopt_load_lines | symopt_undname) |
139 | |
140 | syminitok := C.SymInitialize(handle, 0, 1) |
141 | if syminitok != 1 { |
142 | eprintln('Failed getting process: Aborting backtrace.\n') |
143 | return false |
144 | } |
145 | |
146 | frames := int(C.CaptureStackBackTrace(skipframes + 1, 100, &backtraces[0], 0)) |
147 | if frames < 2 { |
148 | eprintln('C.CaptureStackBackTrace returned less than 2 frames') |
149 | return false |
150 | } |
151 | for i in 0 .. frames { |
152 | frame_addr := backtraces[i] |
153 | if C.SymFromAddr(handle, frame_addr, &offset, si) == 1 { |
154 | nframe := frames - i - 1 |
155 | mut lineinfo := '' |
156 | if C.SymGetLineFromAddr64(handle, frame_addr, &offset, &sline64) == 1 { |
157 | file_name := unsafe { tos3(sline64.f_file_name) } |
158 | lnumber := sline64.f_line_number |
159 | lineinfo = '${file_name}:${lnumber}' |
160 | } else { |
161 | // addr: |
162 | lineinfo = '?? : address = 0x${(&frame_addr):x}' |
163 | } |
164 | sfunc := unsafe { tos3(fname) } |
165 | eprintln('${nframe:-2d}: ${sfunc:-25s} ${lineinfo}') |
166 | } else { |
167 | // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes |
168 | cerr := int(C.GetLastError()) |
169 | if cerr == 87 { |
170 | eprintln('SymFromAddr failure: ${cerr} = The parameter is incorrect)') |
171 | } else if cerr == 487 { |
172 | // probably caused because the .pdb isn't in the executable folder |
173 | eprintln('SymFromAddr failure: ${cerr} = Attempt to access invalid address (Verify that you have the .pdb file in the right folder.)') |
174 | } else { |
175 | eprintln('SymFromAddr failure: ${cerr} (see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes)') |
176 | } |
177 | } |
178 | } |
179 | return true |
180 | } $else { |
181 | eprintln('print_backtrace_skipping_top_frames_msvc must be called only when the compiler is msvc') |
182 | return false |
183 | } |
184 | } |
185 | |
186 | fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool { |
187 | eprintln('print_backtrace_skipping_top_frames_mingw is not implemented') |
188 | return false |
189 | } |
190 | |
191 | fn C.tcc_backtrace(fmt &char) int |
192 | |
193 | fn print_backtrace_skipping_top_frames_tcc(skipframes int) bool { |
194 | $if tinyc { |
195 | $if no_backtrace ? { |
196 | eprintln('backtraces are disabled') |
197 | return false |
198 | } $else { |
199 | C.tcc_backtrace(c'Backtrace') |
200 | return true |
201 | } |
202 | } $else { |
203 | eprintln('print_backtrace_skipping_top_frames_tcc must be called only when the compiler is tcc') |
204 | return false |
205 | } |
206 | // Not reachable, but it looks like it's not detectable by V |
207 | return false |
208 | } |
209 | |
210 | // TODO copypaste from os |
211 | // we want to be able to use this here without having to `import os` |
212 | struct ExceptionRecord { |
213 | pub: |
214 | // status_ constants |
215 | code u32 |
216 | flags u32 |
217 | record &ExceptionRecord = unsafe { nil } |
218 | address voidptr |
219 | param_count u32 |
220 | // params []voidptr |
221 | } |
222 | |
223 | struct ContextRecord { |
224 | // TODO |
225 | } |
226 | |
227 | struct ExceptionPointers { |
228 | pub: |
229 | exception_record &ExceptionRecord = unsafe { nil } |
230 | context_record &ContextRecord = unsafe { nil } |
231 | } |
232 | |
233 | type VectoredExceptionHandler = fn (&ExceptionPointers) int |
234 | |
235 | fn C.AddVectoredExceptionHandler(int, voidptr) |
236 | |
237 | fn add_vectored_exception_handler(handler VectoredExceptionHandler) { |
238 | C.AddVectoredExceptionHandler(1, voidptr(handler)) |
239 | } |
240 | |
241 | [callconv: stdcall] |
242 | fn unhandled_exception_handler(e &ExceptionPointers) int { |
243 | match e.exception_record.code { |
244 | // These are 'used' by the backtrace printer |
245 | // so we dont want to catch them... |
246 | 0x4001000A, 0x40010006, 0xE06D7363 { |
247 | return 0 |
248 | } |
249 | else { |
250 | println('Unhandled Exception 0x${e.exception_record.code:X}') |
251 | print_backtrace_skipping_top_frames(5) |
252 | } |
253 | } |
254 | |
255 | return 0 |
256 | } |
257 | |
258 | fn add_unhandled_exception_handler() { |
259 | add_vectored_exception_handler(VectoredExceptionHandler(voidptr(unhandled_exception_handler))) |
260 | } |
261 | |
262 | fn C.IsDebuggerPresent() bool |
263 | |
264 | fn C.__debugbreak() |
265 | |
266 | fn break_if_debugger_attached() { |
267 | $if tinyc { |
268 | unsafe { |
269 | mut ptr := &voidptr(0) |
270 | *ptr = nil |
271 | _ = ptr |
272 | } |
273 | } $else { |
274 | if C.IsDebuggerPresent() { |
275 | C.__debugbreak() |
276 | } |
277 | } |
278 | } |
279 | |
280 | // return an error message generated from WinAPI's `LastError` |
281 | pub fn winapi_lasterr_str() string { |
282 | err_msg_id := C.GetLastError() |
283 | if err_msg_id == 8 { |
284 | // handle this case special since `FormatMessage()` might not work anymore |
285 | return 'insufficient memory' |
286 | } |
287 | mut msgbuf := &u16(0) |
288 | res := C.FormatMessage(C.FORMAT_MESSAGE_ALLOCATE_BUFFER | C.FORMAT_MESSAGE_FROM_SYSTEM | C.FORMAT_MESSAGE_IGNORE_INSERTS, |
289 | 0, err_msg_id, 0, voidptr(&msgbuf), 0, 0) |
290 | err_msg := if res == 0 { |
291 | 'Win-API error ${err_msg_id}' |
292 | } else { |
293 | unsafe { string_from_wide(msgbuf) } |
294 | } |
295 | return err_msg |
296 | } |
297 | |
298 | // panic with an error message generated from WinAPI's `LastError` |
299 | [noreturn] |
300 | pub fn panic_lasterr(base string) { |
301 | panic(base + winapi_lasterr_str()) |
302 | } |