v / vlib / builtin
Raw file | 302 loc (263 sloc) | 8.73 KB | Latest commit hash 017ace6ea
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]
5module 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
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 u8 // 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 &u8 = unsafe { nil }
41 f_address u64
42}
43
44// returns the current options mask
45fn C.SymSetOptions(symoptions u32) u32
46
47// returns handle
48fn C.GetCurrentProcess() voidptr
49
50fn C.SymInitialize(h_process voidptr, p_user_search_path &u8, b_invade_process int) int
51
52fn C.CaptureStackBackTrace(frames_to_skip u32, frames_to_capture u32, p_backtrace voidptr, p_backtrace_hash voidptr) u16
53
54fn C.SymFromAddr(h_process voidptr, address u64, p_displacement voidptr, p_symbol voidptr) int
55
56fn 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
59const (
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)
73fn C.GetConsoleOutputCP() u32
74
75fn C.SetConsoleOutputCP(wCodePageID u32) bool
76
77fn restore_codepage() {
78 C.SetConsoleOutputCP(g_original_codepage)
79}
80
81fn 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
88fn 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
105fn 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
119fn 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
186fn 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
191fn C.tcc_backtrace(fmt &char) int
192
193fn 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`
212struct ExceptionRecord {
213pub:
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
223struct ContextRecord {
224 // TODO
225}
226
227struct ExceptionPointers {
228pub:
229 exception_record &ExceptionRecord = unsafe { nil }
230 context_record &ContextRecord = unsafe { nil }
231}
232
233type VectoredExceptionHandler = fn (&ExceptionPointers) int
234
235fn C.AddVectoredExceptionHandler(int, voidptr)
236
237fn add_vectored_exception_handler(handler VectoredExceptionHandler) {
238 C.AddVectoredExceptionHandler(1, voidptr(handler))
239}
240
241[callconv: stdcall]
242fn 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
258fn add_unhandled_exception_handler() {
259 add_vectored_exception_handler(VectoredExceptionHandler(voidptr(unhandled_exception_handler)))
260}
261
262fn C.IsDebuggerPresent() bool
263
264fn C.__debugbreak()
265
266fn 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`
281pub 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]
300pub fn panic_lasterr(base string) {
301 panic(base + winapi_lasterr_str())
302}