summaryrefslogtreecommitdiff
path: root/vendor/backtrace/src/dbghelp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/backtrace/src/dbghelp.rs')
-rw-r--r--vendor/backtrace/src/dbghelp.rs487
1 files changed, 487 insertions, 0 deletions
diff --git a/vendor/backtrace/src/dbghelp.rs b/vendor/backtrace/src/dbghelp.rs
new file mode 100644
index 00000000..df31fefb
--- /dev/null
+++ b/vendor/backtrace/src/dbghelp.rs
@@ -0,0 +1,487 @@
+//! A module to assist in managing dbghelp bindings on Windows
+//!
+//! Backtraces on Windows (at least for MSVC) are largely powered through
+//! `dbghelp.dll` and the various functions that it contains. These functions
+//! are currently loaded *dynamically* rather than linking to `dbghelp.dll`
+//! statically. This is currently done by the standard library (and is in theory
+//! required there), but is an effort to help reduce the static dll dependencies
+//! of a library since backtraces are typically pretty optional. That being
+//! said, `dbghelp.dll` almost always successfully loads on Windows.
+//!
+//! Note though that since we're loading all this support dynamically we can't
+//! actually use the raw definitions in `windows_sys`, but rather we need to define
+//! the function pointer types ourselves and use that. We don't really want to
+//! be in the business of duplicating auto-generated bindings, so we assert that all bindings match
+//! those in `windows_sys.rs`.
+//!
+//! Finally, you'll note here that the dll for `dbghelp.dll` is never unloaded,
+//! and that's currently intentional. The thinking is that we can globally cache
+//! it and use it between calls to the API, avoiding expensive loads/unloads. If
+//! this is a problem for leak detectors or something like that we can cross the
+//! bridge when we get there.
+
+#![allow(non_snake_case)]
+
+use alloc::vec::Vec;
+
+use super::windows_sys::*;
+use core::ffi::c_void;
+use core::mem;
+use core::ptr;
+use core::slice;
+
+// This macro is used to define a `Dbghelp` structure which internally contains
+// all the function pointers that we might load.
+macro_rules! dbghelp {
+ (extern "system" {
+ $(fn $name:ident($($arg:ident: $argty:ty),*) -> $ret: ty;)*
+ }) => (
+ pub struct Dbghelp {
+ /// The loaded DLL for `dbghelp.dll`
+ dll: HINSTANCE,
+
+ // Each function pointer for each function we might use
+ $($name: usize,)*
+ }
+
+ static mut DBGHELP: Dbghelp = Dbghelp {
+ // Initially we haven't loaded the DLL
+ dll: ptr::null_mut(),
+ // Initially all functions are set to zero to say they need to be
+ // dynamically loaded.
+ $($name: 0,)*
+ };
+
+ // Convenience typedef for each function type.
+ $(pub type $name = unsafe extern "system" fn($($argty),*) -> $ret;)*
+
+ impl Dbghelp {
+ /// Attempts to open `dbghelp.dll`. Returns success if it works or
+ /// error if `LoadLibraryW` fails.
+ fn ensure_open(&mut self) -> Result<(), ()> {
+ if !self.dll.is_null() {
+ return Ok(())
+ }
+ let lib = b"dbghelp.dll\0";
+ unsafe {
+ self.dll = LoadLibraryA(lib.as_ptr());
+ if self.dll.is_null() {
+ Err(())
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ // Function for each method we'd like to use. When called it will
+ // either read the cached function pointer or load it and return the
+ // loaded value. Loads are asserted to succeed.
+ $(pub fn $name(&mut self) -> Option<$name> {
+ // Assert that windows_sys::$name is declared to have the same
+ // argument types and return type as our declaration, although
+ // it might be either extern "C" or extern "system".
+ cfg_if::cfg_if! {
+ if #[cfg(any(target_arch = "x86", not(windows_raw_dylib)))] {
+ let _: unsafe extern "system" fn($($argty),*) -> $ret = super::windows_sys::$name;
+ } else {
+ let _: unsafe extern "C" fn($($argty),*) -> $ret = super::windows_sys::$name;
+ }
+ }
+
+ unsafe {
+ if self.$name == 0 {
+ let name = concat!(stringify!($name), "\0");
+ self.$name = self.symbol(name.as_bytes())?;
+ }
+ Some(mem::transmute::<usize, $name>(self.$name))
+ }
+ })*
+
+ fn symbol(&self, symbol: &[u8]) -> Option<usize> {
+ unsafe {
+ GetProcAddress(self.dll, symbol.as_ptr()).map(|address|address as usize)
+ }
+ }
+ }
+
+ // Convenience proxy to use the cleanup locks to reference dbghelp
+ // functions.
+ #[allow(dead_code)]
+ impl Init {
+ $(pub fn $name(&self) -> $name {
+ // FIXME: https://github.com/rust-lang/backtrace-rs/issues/678
+ #[allow(static_mut_refs)]
+ unsafe {
+ DBGHELP.$name().unwrap()
+ }
+ })*
+
+ pub fn dbghelp(&self) -> *mut Dbghelp {
+ #[allow(unused_unsafe)]
+ unsafe { ptr::addr_of_mut!(DBGHELP) }
+ }
+ }
+ )
+
+}
+
+dbghelp! {
+ extern "system" {
+ fn SymGetOptions() -> u32;
+ fn SymSetOptions(options: u32) -> u32;
+ fn SymInitializeW(
+ handle: HANDLE,
+ path: PCWSTR,
+ invade: BOOL
+ ) -> BOOL;
+ fn SymGetSearchPathW(
+ hprocess: HANDLE,
+ searchpatha: PWSTR,
+ searchpathlength: u32
+ ) -> BOOL;
+ fn SymSetSearchPathW(
+ hprocess: HANDLE,
+ searchpatha: PCWSTR
+ ) -> BOOL;
+ fn EnumerateLoadedModulesW64(
+ hprocess: HANDLE,
+ enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
+ usercontext: *const c_void
+ ) -> BOOL;
+ fn StackWalk64(
+ MachineType: u32,
+ hProcess: HANDLE,
+ hThread: HANDLE,
+ StackFrame: *mut STACKFRAME64,
+ ContextRecord: *mut c_void,
+ ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64,
+ FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64,
+ GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64,
+ TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64
+ ) -> BOOL;
+ fn SymFunctionTableAccess64(
+ hProcess: HANDLE,
+ AddrBase: u64
+ ) -> *mut c_void;
+ fn SymGetModuleBase64(
+ hProcess: HANDLE,
+ AddrBase: u64
+ ) -> u64;
+ fn SymFromAddrW(
+ hProcess: HANDLE,
+ Address: u64,
+ Displacement: *mut u64,
+ Symbol: *mut SYMBOL_INFOW
+ ) -> BOOL;
+ fn SymGetLineFromAddrW64(
+ hProcess: HANDLE,
+ dwAddr: u64,
+ pdwDisplacement: *mut u32,
+ Line: *mut IMAGEHLP_LINEW64
+ ) -> BOOL;
+ fn StackWalkEx(
+ MachineType: u32,
+ hProcess: HANDLE,
+ hThread: HANDLE,
+ StackFrame: *mut STACKFRAME_EX,
+ ContextRecord: *mut c_void,
+ ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64,
+ FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64,
+ GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64,
+ TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64,
+ Flags: u32
+ ) -> BOOL;
+ fn SymFromInlineContextW(
+ hProcess: HANDLE,
+ Address: u64,
+ InlineContext: u32,
+ Displacement: *mut u64,
+ Symbol: *mut SYMBOL_INFOW
+ ) -> BOOL;
+ fn SymGetLineFromInlineContextW(
+ hProcess: HANDLE,
+ dwAddr: u64,
+ InlineContext: u32,
+ qwModuleBaseAddress: u64,
+ pdwDisplacement: *mut u32,
+ Line: *mut IMAGEHLP_LINEW64
+ ) -> BOOL;
+ fn SymAddrIncludeInlineTrace(
+ hProcess: HANDLE,
+ Address: u64
+ ) -> u32;
+ fn SymQueryInlineTrace(
+ hProcess: HANDLE,
+ StartAddress: u64,
+ StartContext: u32,
+ StartRetAddress: u64,
+ CurAddress: u64,
+ CurContext: *mut u32,
+ CurFrameIndex: *mut u32
+ ) -> BOOL;
+ }
+}
+
+pub struct Init {
+ lock: HANDLE,
+}
+
+/// Initialize all support necessary to access `dbghelp` API functions from this
+/// crate.
+///
+/// Note that this function is **safe**, it internally has its own
+/// synchronization. Also note that it is safe to call this function multiple
+/// times recursively.
+pub fn init() -> Result<Init, ()> {
+ use core::sync::atomic::{AtomicPtr, Ordering::SeqCst};
+
+ // Helper function for generating a name that's unique to the process.
+ fn mutex_name() -> [u8; 33] {
+ let mut name: [u8; 33] = *b"Local\\RustBacktraceMutex00000000\0";
+ let mut id = unsafe { GetCurrentProcessId() };
+ // Quick and dirty no alloc u32 to hex.
+ let mut index = name.len() - 1;
+ while id > 0 {
+ name[index - 1] = match (id & 0xF) as u8 {
+ h @ 0..=9 => b'0' + h,
+ h => b'A' + (h - 10),
+ };
+ id >>= 4;
+ index -= 1;
+ }
+ name
+ }
+
+ unsafe {
+ // First thing we need to do is to synchronize this function. This can
+ // be called concurrently from other threads or recursively within one
+ // thread. Note that it's trickier than that though because what we're
+ // using here, `dbghelp`, *also* needs to be synchronized with all other
+ // callers to `dbghelp` in this process.
+ //
+ // Typically there aren't really that many calls to `dbghelp` within the
+ // same process and we can probably safely assume that we're the only
+ // ones accessing it. There is, however, one primary other user we have
+ // to worry about which is ironically ourselves, but in the standard
+ // library. The Rust standard library depends on this crate for
+ // backtrace support, and this crate also exists on crates.io. This
+ // means that if the standard library is printing a panic backtrace it
+ // may race with this crate coming from crates.io, causing segfaults.
+ //
+ // To help solve this synchronization problem we employ a
+ // Windows-specific trick here (it is, after all, a Windows-specific
+ // restriction about synchronization). We create a *session-local* named
+ // mutex to protect this call. The intention here is that the standard
+ // library and this crate don't have to share Rust-level APIs to
+ // synchronize here but can instead work behind the scenes to make sure
+ // they're synchronizing with one another. That way when this function
+ // is called through the standard library or through crates.io we can be
+ // sure that the same mutex is being acquired.
+ //
+ // So all of that is to say that the first thing we do here is we
+ // atomically create a `HANDLE` which is a named mutex on Windows. We
+ // synchronize a bit with other threads sharing this function
+ // specifically and ensure that only one handle is created per instance
+ // of this function. Note that the handle is never closed once it's
+ // stored in the global.
+ //
+ // After we've actually go the lock we simply acquire it, and our `Init`
+ // handle we hand out will be responsible for dropping it eventually.
+ static LOCK: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
+ let mut lock = LOCK.load(SeqCst);
+ if lock.is_null() {
+ let name = mutex_name();
+ lock = CreateMutexA(ptr::null_mut(), FALSE, name.as_ptr());
+ if lock.is_null() {
+ return Err(());
+ }
+ if let Err(other) = LOCK.compare_exchange(ptr::null_mut(), lock, SeqCst, SeqCst) {
+ debug_assert!(!other.is_null());
+ CloseHandle(lock);
+ lock = other;
+ }
+ }
+ debug_assert!(!lock.is_null());
+ let r = WaitForSingleObjectEx(lock, INFINITE, FALSE);
+ debug_assert_eq!(r, 0);
+ let ret = Init { lock };
+
+ // Ok, phew! Now that we're all safely synchronized, let's actually
+ // start processing everything. First up we need to ensure that
+ // `dbghelp.dll` is actually loaded in this process. We do this
+ // dynamically to avoid a static dependency. This has historically been
+ // done to work around weird linking issues and is intended at making
+ // binaries a bit more portable since this is largely just a debugging
+ // utility.
+ //
+ // Once we've opened `dbghelp.dll` we need to call some initialization
+ // functions in it, and that's detailed more below. We only do this
+ // once, though, so we've got a global boolean indicating whether we're
+ // done yet or not.
+ // FIXME: https://github.com/rust-lang/backtrace-rs/issues/678
+ #[allow(static_mut_refs)]
+ DBGHELP.ensure_open()?;
+
+ static mut INITIALIZED: bool = false;
+ if !INITIALIZED {
+ set_optional_options(ret.dbghelp());
+ INITIALIZED = true;
+ }
+ Ok(ret)
+ }
+}
+unsafe fn set_optional_options(dbghelp: *mut Dbghelp) -> Option<()> {
+ unsafe {
+ let orig = (*dbghelp).SymGetOptions()?();
+
+ // Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
+ // according to MSVC's own docs about this: "This is the fastest, most
+ // efficient way to use the symbol handler.", so let's do that!
+ (*dbghelp).SymSetOptions()?(orig | SYMOPT_DEFERRED_LOADS);
+
+ // Actually initialize symbols with MSVC. Note that this can fail, but we
+ // ignore it. There's not a ton of prior art for this per se, but LLVM
+ // internally seems to ignore the return value here and one of the
+ // sanitizer libraries in LLVM prints a scary warning if this fails but
+ // basically ignores it in the long run.
+ //
+ // One case this comes up a lot for Rust is that the standard library and
+ // this crate on crates.io both want to compete for `SymInitializeW`. The
+ // standard library historically wanted to initialize then cleanup most of
+ // the time, but now that it's using this crate it means that someone will
+ // get to initialization first and the other will pick up that
+ // initialization.
+ (*dbghelp).SymInitializeW()?(GetCurrentProcess(), ptr::null_mut(), TRUE);
+
+ // The default search path for dbghelp will only look in the current working
+ // directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`.
+ // However, we also want to look in the directory of the executable
+ // and each DLL that is loaded. To do this, we need to update the search path
+ // to include these directories.
+ //
+ // See https://learn.microsoft.com/cpp/build/reference/pdbpath for an
+ // example of where symbols are usually searched for.
+ let mut search_path_buf = Vec::new();
+ search_path_buf.resize(1024, 0);
+
+ // Prefill the buffer with the current search path.
+ if (*dbghelp).SymGetSearchPathW()?(
+ GetCurrentProcess(),
+ search_path_buf.as_mut_ptr(),
+ search_path_buf.len() as _,
+ ) == TRUE
+ {
+ // Trim the buffer to the actual length of the string.
+ let len = lstrlenW(search_path_buf.as_mut_ptr());
+ assert!(len >= 0);
+ search_path_buf.truncate(len as usize);
+ } else {
+ // If getting the search path fails, at least include the current directory.
+ search_path_buf.clear();
+ search_path_buf.push(utf16_char('.'));
+ search_path_buf.push(utf16_char(';'));
+ }
+
+ let mut search_path = SearchPath::new(search_path_buf);
+
+ // Update the search path to include the directory of the executable and each DLL.
+ (*dbghelp).EnumerateLoadedModulesW64()?(
+ GetCurrentProcess(),
+ Some(enum_loaded_modules_callback),
+ ((&mut search_path) as *mut SearchPath) as *mut c_void,
+ );
+
+ let new_search_path = search_path.finalize();
+
+ // Set the new search path.
+ (*dbghelp).SymSetSearchPathW()?(GetCurrentProcess(), new_search_path.as_ptr());
+ }
+ Some(())
+}
+
+struct SearchPath {
+ search_path_utf16: Vec<u16>,
+}
+
+fn utf16_char(c: char) -> u16 {
+ let buf = &mut [0u16; 2];
+ let buf = c.encode_utf16(buf);
+ assert!(buf.len() == 1);
+ buf[0]
+}
+
+impl SearchPath {
+ fn new(initial_search_path: Vec<u16>) -> Self {
+ Self {
+ search_path_utf16: initial_search_path,
+ }
+ }
+
+ /// Add a path to the search path if it is not already present.
+ fn add(&mut self, path: &[u16]) {
+ let sep = utf16_char(';');
+
+ // We could deduplicate in a case-insensitive way, but case-sensitivity
+ // can be configured by directory on Windows, so let's not do that.
+ // https://learn.microsoft.com/windows/wsl/case-sensitivity
+ if !self
+ .search_path_utf16
+ .split(|&c| c == sep)
+ .any(|p| p == path)
+ {
+ if self.search_path_utf16.last() != Some(&sep) {
+ self.search_path_utf16.push(sep);
+ }
+ self.search_path_utf16.extend_from_slice(path);
+ }
+ }
+
+ fn finalize(mut self) -> Vec<u16> {
+ // Add a null terminator.
+ self.search_path_utf16.push(0);
+ self.search_path_utf16
+ }
+}
+
+extern "system" fn enum_loaded_modules_callback(
+ module_name: PCWSTR,
+ _: u64,
+ _: u32,
+ user_context: *const c_void,
+) -> BOOL {
+ // `module_name` is an absolute path like `C:\path\to\module.dll`
+ // or `C:\path\to\module.exe`
+ let len: usize = unsafe { lstrlenW(module_name).try_into().unwrap() };
+
+ if len == 0 {
+ // This should not happen, but if it does, we can just ignore it.
+ return TRUE;
+ }
+
+ let module_name = unsafe { slice::from_raw_parts(module_name, len) };
+ let path_sep = utf16_char('\\');
+ let alt_path_sep = utf16_char('/');
+
+ let Some(end_of_directory) = module_name
+ .iter()
+ .rposition(|&c| c == path_sep || c == alt_path_sep)
+ else {
+ // `module_name` being an absolute path, it should always contain at least one
+ // path separator. If not, there is nothing we can do.
+ return TRUE;
+ };
+
+ let search_path = unsafe { &mut *(user_context as *mut SearchPath) };
+ search_path.add(&module_name[..end_of_directory]);
+
+ TRUE
+}
+
+impl Drop for Init {
+ fn drop(&mut self) {
+ unsafe {
+ let r = ReleaseMutex(self.lock);
+ debug_assert!(r != 0);
+ }
+ }
+}