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}