std\backtrace\src/dbghelp.rs
1//! A module to assist in managing dbghelp bindings on Windows
2//!
3//! Backtraces on Windows (at least for MSVC) are largely powered through
4//! `dbghelp.dll` and the various functions that it contains. These functions
5//! are currently loaded *dynamically* rather than linking to `dbghelp.dll`
6//! statically. This is currently done by the standard library (and is in theory
7//! required there), but is an effort to help reduce the static dll dependencies
8//! of a library since backtraces are typically pretty optional. That being
9//! said, `dbghelp.dll` almost always successfully loads on Windows.
10//!
11//! Note though that since we're loading all this support dynamically we can't
12//! actually use the raw definitions in `windows_sys`, but rather we need to define
13//! the function pointer types ourselves and use that. We don't really want to
14//! be in the business of duplicating auto-generated bindings, so we assert that all bindings match
15//! those in `windows_sys.rs`.
16//!
17//! Finally, you'll note here that the dll for `dbghelp.dll` is never unloaded,
18//! and that's currently intentional. The thinking is that we can globally cache
19//! it and use it between calls to the API, avoiding expensive loads/unloads. If
20//! this is a problem for leak detectors or something like that we can cross the
21//! bridge when we get there.
22
23#![allow(non_snake_case)]
24
25use alloc::vec::Vec;
26
27use super::windows_sys::*;
28use core::ffi::c_void;
29use core::mem;
30use core::ptr;
31use core::slice;
32
33// This macro is used to define a `Dbghelp` structure which internally contains
34// all the function pointers that we might load.
35macro_rules! dbghelp {
36 (extern "system" {
37 $(fn $name:ident($($arg:ident: $argty:ty),*) -> $ret: ty;)*
38 }) => (
39 pub struct Dbghelp {
40 /// The loaded DLL for `dbghelp.dll`
41 dll: HINSTANCE,
42
43 // Each function pointer for each function we might use
44 $($name: usize,)*
45 }
46
47 static mut DBGHELP: Dbghelp = Dbghelp {
48 // Initially we haven't loaded the DLL
49 dll: ptr::null_mut(),
50 // Initially all functions are set to zero to say they need to be
51 // dynamically loaded.
52 $($name: 0,)*
53 };
54
55 // Convenience typedef for each function type.
56 $(pub type $name = unsafe extern "system" fn($($argty),*) -> $ret;)*
57
58 impl Dbghelp {
59 /// Attempts to open `dbghelp.dll`. Returns success if it works or
60 /// error if `LoadLibraryW` fails.
61 fn ensure_open(&mut self) -> Result<(), ()> {
62 if !self.dll.is_null() {
63 return Ok(())
64 }
65 let lib = b"dbghelp.dll\0";
66 unsafe {
67 self.dll = LoadLibraryA(lib.as_ptr());
68 if self.dll.is_null() {
69 Err(())
70 } else {
71 Ok(())
72 }
73 }
74 }
75
76 // Function for each method we'd like to use. When called it will
77 // either read the cached function pointer or load it and return the
78 // loaded value. Loads are asserted to succeed.
79 $(pub fn $name(&mut self) -> Option<$name> {
80 unsafe {
81 if self.$name == 0 {
82 let name = concat!(stringify!($name), "\0");
83 self.$name = self.symbol(name.as_bytes())?;
84 }
85 Some(mem::transmute::<usize, $name>(self.$name))
86 }
87 })*
88
89 fn symbol(&self, symbol: &[u8]) -> Option<usize> {
90 unsafe {
91 GetProcAddress(self.dll, symbol.as_ptr()).map(|address|address as usize)
92 }
93 }
94 }
95
96 // Convenience proxy to use the cleanup locks to reference dbghelp
97 // functions.
98 #[allow(dead_code)]
99 impl Init {
100 $(pub fn $name(&self) -> $name {
101 // FIXME: https://github.com/rust-lang/backtrace-rs/issues/678
102 #[allow(static_mut_refs)]
103 unsafe {
104 DBGHELP.$name().unwrap()
105 }
106 })*
107
108 pub fn dbghelp(&self) -> *mut Dbghelp {
109 #[allow(unused_unsafe)]
110 unsafe { ptr::addr_of_mut!(DBGHELP) }
111 }
112 }
113 )
114
115}
116
117dbghelp! {
118 extern "system" {
119 fn SymGetOptions() -> u32;
120 fn SymSetOptions(options: u32) -> u32;
121 fn SymInitializeW(
122 handle: HANDLE,
123 path: PCWSTR,
124 invade: BOOL
125 ) -> BOOL;
126 fn SymGetSearchPathW(
127 hprocess: HANDLE,
128 searchpatha: PWSTR,
129 searchpathlength: u32
130 ) -> BOOL;
131 fn SymSetSearchPathW(
132 hprocess: HANDLE,
133 searchpatha: PCWSTR
134 ) -> BOOL;
135 fn EnumerateLoadedModulesW64(
136 hprocess: HANDLE,
137 enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
138 usercontext: *const c_void
139 ) -> BOOL;
140 fn StackWalk64(
141 MachineType: u32,
142 hProcess: HANDLE,
143 hThread: HANDLE,
144 StackFrame: *mut STACKFRAME64,
145 ContextRecord: *mut c_void,
146 ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64,
147 FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64,
148 GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64,
149 TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64
150 ) -> BOOL;
151 fn SymFunctionTableAccess64(
152 hProcess: HANDLE,
153 AddrBase: u64
154 ) -> *mut c_void;
155 fn SymGetModuleBase64(
156 hProcess: HANDLE,
157 AddrBase: u64
158 ) -> u64;
159 fn SymFromAddrW(
160 hProcess: HANDLE,
161 Address: u64,
162 Displacement: *mut u64,
163 Symbol: *mut SYMBOL_INFOW
164 ) -> BOOL;
165 fn SymGetLineFromAddrW64(
166 hProcess: HANDLE,
167 dwAddr: u64,
168 pdwDisplacement: *mut u32,
169 Line: *mut IMAGEHLP_LINEW64
170 ) -> BOOL;
171 fn StackWalkEx(
172 MachineType: u32,
173 hProcess: HANDLE,
174 hThread: HANDLE,
175 StackFrame: *mut STACKFRAME_EX,
176 ContextRecord: *mut c_void,
177 ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64,
178 FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64,
179 GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64,
180 TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64,
181 Flags: u32
182 ) -> BOOL;
183 fn SymFromInlineContextW(
184 hProcess: HANDLE,
185 Address: u64,
186 InlineContext: u32,
187 Displacement: *mut u64,
188 Symbol: *mut SYMBOL_INFOW
189 ) -> BOOL;
190 fn SymGetLineFromInlineContextW(
191 hProcess: HANDLE,
192 dwAddr: u64,
193 InlineContext: u32,
194 qwModuleBaseAddress: u64,
195 pdwDisplacement: *mut u32,
196 Line: *mut IMAGEHLP_LINEW64
197 ) -> BOOL;
198 fn SymAddrIncludeInlineTrace(
199 hProcess: HANDLE,
200 Address: u64
201 ) -> u32;
202 fn SymQueryInlineTrace(
203 hProcess: HANDLE,
204 StartAddress: u64,
205 StartContext: u32,
206 StartRetAddress: u64,
207 CurAddress: u64,
208 CurContext: *mut u32,
209 CurFrameIndex: *mut u32
210 ) -> BOOL;
211 }
212}
213
214pub struct Init {
215 lock: HANDLE,
216}
217
218/// Initialize all support necessary to access `dbghelp` API functions from this
219/// crate.
220///
221/// Note that this function is **safe**, it internally has its own
222/// synchronization. Also note that it is safe to call this function multiple
223/// times recursively.
224pub fn init() -> Result<Init, ()> {
225 use core::sync::atomic::{AtomicPtr, Ordering::SeqCst};
226
227 // Helper function for generating a name that's unique to the process.
228 fn mutex_name() -> [u8; 33] {
229 let mut name: [u8; 33] = *b"Local\\RustBacktraceMutex00000000\0";
230 let mut id = unsafe { GetCurrentProcessId() };
231 // Quick and dirty no alloc u32 to hex.
232 let mut index = name.len() - 1;
233 while id > 0 {
234 name[index - 1] = match (id & 0xF) as u8 {
235 h @ 0..=9 => b'0' + h,
236 h => b'A' + (h - 10),
237 };
238 id >>= 4;
239 index -= 1;
240 }
241 name
242 }
243
244 unsafe {
245 // First thing we need to do is to synchronize this function. This can
246 // be called concurrently from other threads or recursively within one
247 // thread. Note that it's trickier than that though because what we're
248 // using here, `dbghelp`, *also* needs to be synchronized with all other
249 // callers to `dbghelp` in this process.
250 //
251 // Typically there aren't really that many calls to `dbghelp` within the
252 // same process and we can probably safely assume that we're the only
253 // ones accessing it. There is, however, one primary other user we have
254 // to worry about which is ironically ourselves, but in the standard
255 // library. The Rust standard library depends on this crate for
256 // backtrace support, and this crate also exists on crates.io. This
257 // means that if the standard library is printing a panic backtrace it
258 // may race with this crate coming from crates.io, causing segfaults.
259 //
260 // To help solve this synchronization problem we employ a
261 // Windows-specific trick here (it is, after all, a Windows-specific
262 // restriction about synchronization). We create a *session-local* named
263 // mutex to protect this call. The intention here is that the standard
264 // library and this crate don't have to share Rust-level APIs to
265 // synchronize here but can instead work behind the scenes to make sure
266 // they're synchronizing with one another. That way when this function
267 // is called through the standard library or through crates.io we can be
268 // sure that the same mutex is being acquired.
269 //
270 // So all of that is to say that the first thing we do here is we
271 // atomically create a `HANDLE` which is a named mutex on Windows. We
272 // synchronize a bit with other threads sharing this function
273 // specifically and ensure that only one handle is created per instance
274 // of this function. Note that the handle is never closed once it's
275 // stored in the global.
276 //
277 // After we've actually go the lock we simply acquire it, and our `Init`
278 // handle we hand out will be responsible for dropping it eventually.
279 static LOCK: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
280 let mut lock = LOCK.load(SeqCst);
281 if lock.is_null() {
282 let name = mutex_name();
283 lock = CreateMutexA(ptr::null_mut(), FALSE, name.as_ptr());
284 if lock.is_null() {
285 return Err(());
286 }
287 if let Err(other) = LOCK.compare_exchange(ptr::null_mut(), lock, SeqCst, SeqCst) {
288 debug_assert!(!other.is_null());
289 CloseHandle(lock);
290 lock = other;
291 }
292 }
293 debug_assert!(!lock.is_null());
294 let r = WaitForSingleObjectEx(lock, INFINITE, FALSE);
295 debug_assert_eq!(r, 0);
296 let ret = Init { lock };
297
298 // Ok, phew! Now that we're all safely synchronized, let's actually
299 // start processing everything. First up we need to ensure that
300 // `dbghelp.dll` is actually loaded in this process. We do this
301 // dynamically to avoid a static dependency. This has historically been
302 // done to work around weird linking issues and is intended at making
303 // binaries a bit more portable since this is largely just a debugging
304 // utility.
305 //
306 // Once we've opened `dbghelp.dll` we need to call some initialization
307 // functions in it, and that's detailed more below. We only do this
308 // once, though, so we've got a global boolean indicating whether we're
309 // done yet or not.
310 // FIXME: https://github.com/rust-lang/backtrace-rs/issues/678
311 #[allow(static_mut_refs)]
312 DBGHELP.ensure_open()?;
313
314 static mut INITIALIZED: bool = false;
315 if !INITIALIZED {
316 set_optional_options(ret.dbghelp());
317 INITIALIZED = true;
318 }
319 Ok(ret)
320 }
321}
322unsafe fn set_optional_options(dbghelp: *mut Dbghelp) -> Option<()> {
323 unsafe {
324 let orig = (*dbghelp).SymGetOptions()?();
325
326 // Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
327 // according to MSVC's own docs about this: "This is the fastest, most
328 // efficient way to use the symbol handler.", so let's do that!
329 (*dbghelp).SymSetOptions()?(orig | SYMOPT_DEFERRED_LOADS);
330
331 // Actually initialize symbols with MSVC. Note that this can fail, but we
332 // ignore it. There's not a ton of prior art for this per se, but LLVM
333 // internally seems to ignore the return value here and one of the
334 // sanitizer libraries in LLVM prints a scary warning if this fails but
335 // basically ignores it in the long run.
336 //
337 // One case this comes up a lot for Rust is that the standard library and
338 // this crate on crates.io both want to compete for `SymInitializeW`. The
339 // standard library historically wanted to initialize then cleanup most of
340 // the time, but now that it's using this crate it means that someone will
341 // get to initialization first and the other will pick up that
342 // initialization.
343 (*dbghelp).SymInitializeW()?(GetCurrentProcess(), ptr::null_mut(), TRUE);
344
345 // The default search path for dbghelp will only look in the current working
346 // directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`.
347 // However, we also want to look in the directory of the executable
348 // and each DLL that is loaded. To do this, we need to update the search path
349 // to include these directories.
350 //
351 // See https://learn.microsoft.com/cpp/build/reference/pdbpath for an
352 // example of where symbols are usually searched for.
353 let mut search_path_buf = Vec::new();
354 search_path_buf.resize(1024, 0);
355
356 // Prefill the buffer with the current search path.
357 if (*dbghelp).SymGetSearchPathW()?(
358 GetCurrentProcess(),
359 search_path_buf.as_mut_ptr(),
360 search_path_buf.len() as _,
361 ) == TRUE
362 {
363 // Trim the buffer to the actual length of the string.
364 let len = lstrlenW(search_path_buf.as_mut_ptr());
365 assert!(len >= 0);
366 search_path_buf.truncate(len as usize);
367 } else {
368 // If getting the search path fails, at least include the current directory.
369 search_path_buf.clear();
370 search_path_buf.push(utf16_char('.'));
371 search_path_buf.push(utf16_char(';'));
372 }
373
374 let mut search_path = SearchPath::new(search_path_buf);
375
376 // Update the search path to include the directory of the executable and each DLL.
377 (*dbghelp).EnumerateLoadedModulesW64()?(
378 GetCurrentProcess(),
379 Some(enum_loaded_modules_callback),
380 ((&mut search_path) as *mut SearchPath) as *mut c_void,
381 );
382
383 let new_search_path = search_path.finalize();
384
385 // Set the new search path.
386 (*dbghelp).SymSetSearchPathW()?(GetCurrentProcess(), new_search_path.as_ptr());
387 }
388 Some(())
389}
390
391struct SearchPath {
392 search_path_utf16: Vec<u16>,
393}
394
395fn utf16_char(c: char) -> u16 {
396 let buf = &mut [0u16; 2];
397 let buf = c.encode_utf16(buf);
398 assert!(buf.len() == 1);
399 buf[0]
400}
401
402impl SearchPath {
403 fn new(initial_search_path: Vec<u16>) -> Self {
404 Self {
405 search_path_utf16: initial_search_path,
406 }
407 }
408
409 /// Add a path to the search path if it is not already present.
410 fn add(&mut self, path: &[u16]) {
411 let sep = utf16_char(';');
412
413 // We could deduplicate in a case-insensitive way, but case-sensitivity
414 // can be configured by directory on Windows, so let's not do that.
415 // https://learn.microsoft.com/windows/wsl/case-sensitivity
416 if !self
417 .search_path_utf16
418 .split(|&c| c == sep)
419 .any(|p| p == path)
420 {
421 if self.search_path_utf16.last() != Some(&sep) {
422 self.search_path_utf16.push(sep);
423 }
424 self.search_path_utf16.extend_from_slice(path);
425 }
426 }
427
428 fn finalize(mut self) -> Vec<u16> {
429 // Add a null terminator.
430 self.search_path_utf16.push(0);
431 self.search_path_utf16
432 }
433}
434
435extern "system" fn enum_loaded_modules_callback(
436 module_name: PCWSTR,
437 _: u64,
438 _: u32,
439 user_context: *const c_void,
440) -> BOOL {
441 // `module_name` is an absolute path like `C:\path\to\module.dll`
442 // or `C:\path\to\module.exe`
443 let len: usize = unsafe { lstrlenW(module_name).try_into().unwrap() };
444
445 if len == 0 {
446 // This should not happen, but if it does, we can just ignore it.
447 return TRUE;
448 }
449
450 let module_name = unsafe { slice::from_raw_parts(module_name, len) };
451 let path_sep = utf16_char('\\');
452 let alt_path_sep = utf16_char('/');
453
454 let Some(end_of_directory) = module_name
455 .iter()
456 .rposition(|&c| c == path_sep || c == alt_path_sep)
457 else {
458 // `module_name` being an absolute path, it should always contain at least one
459 // path separator. If not, there is nothing we can do.
460 return TRUE;
461 };
462
463 let search_path = unsafe { &mut *(user_context as *mut SearchPath) };
464 search_path.add(&module_name[..end_of_directory]);
465
466 TRUE
467}
468
469impl Drop for Init {
470 fn drop(&mut self) {
471 unsafe {
472 let r = ReleaseMutex(self.lock);
473 debug_assert!(r != 0);
474 }
475 }
476}