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