From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- library/backtrace/src/dbghelp.rs | 351 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 library/backtrace/src/dbghelp.rs (limited to 'library/backtrace/src/dbghelp.rs') diff --git a/library/backtrace/src/dbghelp.rs b/library/backtrace/src/dbghelp.rs new file mode 100644 index 000000000..e01002beb --- /dev/null +++ b/library/backtrace/src/dbghelp.rs @@ -0,0 +1,351 @@ +//! 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 `winapi`, 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 winapi, so we have a Cargo feature +//! `verify-winapi` which asserts that all bindings match those in winapi and +//! this feature is enabled on CI. +//! +//! 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 super::windows::*; +use core::mem; +use core::ptr; + +// Work around `SymGetOptions` and `SymSetOptions` not being present in winapi +// itself. Otherwise this is only used when we're double-checking types against +// winapi. +#[cfg(feature = "verify-winapi")] +mod dbghelp { + use crate::windows::*; + pub use winapi::um::dbghelp::{ + StackWalk64, StackWalkEx, SymCleanup, SymFromAddrW, SymFunctionTableAccess64, + SymGetLineFromAddrW64, SymGetModuleBase64, SymGetOptions, SymInitializeW, SymSetOptions, + }; + + extern "system" { + // Not defined in winapi yet + pub fn SymFromInlineContextW( + hProcess: HANDLE, + Address: DWORD64, + InlineContext: ULONG, + Displacement: PDWORD64, + Symbol: PSYMBOL_INFOW, + ) -> BOOL; + pub fn SymGetLineFromInlineContextW( + hProcess: HANDLE, + dwAddr: DWORD64, + InlineContext: ULONG, + qwModuleBaseAddress: DWORD64, + pdwDisplacement: PDWORD, + Line: PIMAGEHLP_LINEW64, + ) -> BOOL; + } + + pub fn assert_equal_types(a: T, _b: T) -> T { + a + } +} + +// 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: HMODULE, + + // Each function pointer for each function we might use + $($name: usize,)* + } + + static mut DBGHELP: Dbghelp = Dbghelp { + // Initially we haven't loaded the DLL + dll: 0 as *mut _, + // Initiall 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. + /// + /// Panics if library is already loaded. + 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() as *const i8); + 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> { + unsafe { + if self.$name == 0 { + let name = concat!(stringify!($name), "\0"); + self.$name = self.symbol(name.as_bytes())?; + } + let ret = mem::transmute::(self.$name); + #[cfg(feature = "verify-winapi")] + dbghelp::assert_equal_types(ret, dbghelp::$name); + Some(ret) + } + })* + + fn symbol(&self, symbol: &[u8]) -> Option { + unsafe { + match GetProcAddress(self.dll, symbol.as_ptr() as *const _) as usize { + 0 => None, + n => Some(n), + } + } + } + } + + // Convenience proxy to use the cleanup locks to reference dbghelp + // functions. + #[allow(dead_code)] + impl Init { + $(pub fn $name(&self) -> $name { + unsafe { + DBGHELP.$name().unwrap() + } + })* + + pub fn dbghelp(&self) -> *mut Dbghelp { + unsafe { + &mut DBGHELP + } + } + } + ) + +} + +const SYMOPT_DEFERRED_LOADS: DWORD = 0x00000004; + +dbghelp! { + extern "system" { + fn SymGetOptions() -> DWORD; + fn SymSetOptions(options: DWORD) -> DWORD; + fn SymInitializeW( + handle: HANDLE, + path: PCWSTR, + invade: BOOL + ) -> BOOL; + fn SymCleanup(handle: HANDLE) -> BOOL; + fn StackWalk64( + MachineType: DWORD, + hProcess: HANDLE, + hThread: HANDLE, + StackFrame: LPSTACKFRAME64, + ContextRecord: PVOID, + 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: DWORD64 + ) -> PVOID; + fn SymGetModuleBase64( + hProcess: HANDLE, + AddrBase: DWORD64 + ) -> DWORD64; + fn SymFromAddrW( + hProcess: HANDLE, + Address: DWORD64, + Displacement: PDWORD64, + Symbol: PSYMBOL_INFOW + ) -> BOOL; + fn SymGetLineFromAddrW64( + hProcess: HANDLE, + dwAddr: DWORD64, + pdwDisplacement: PDWORD, + Line: PIMAGEHLP_LINEW64 + ) -> BOOL; + fn StackWalkEx( + MachineType: DWORD, + hProcess: HANDLE, + hThread: HANDLE, + StackFrame: LPSTACKFRAME_EX, + ContextRecord: PVOID, + ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64, + FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64, + GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64, + TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64, + Flags: DWORD + ) -> BOOL; + fn SymFromInlineContextW( + hProcess: HANDLE, + Address: DWORD64, + InlineContext: ULONG, + Displacement: PDWORD64, + Symbol: PSYMBOL_INFOW + ) -> BOOL; + fn SymGetLineFromInlineContextW( + hProcess: HANDLE, + dwAddr: DWORD64, + InlineContext: ULONG, + qwModuleBaseAddress: DWORD64, + pdwDisplacement: PDWORD, + Line: PIMAGEHLP_LINEW64 + ) -> 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 { + use core::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + + 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: AtomicUsize = AtomicUsize::new(0); + let mut lock = LOCK.load(SeqCst); + if lock == 0 { + lock = CreateMutexA( + ptr::null_mut(), + 0, + "Local\\RustBacktraceMutex\0".as_ptr() as _, + ) as usize; + if lock == 0 { + return Err(()); + } + if let Err(other) = LOCK.compare_exchange(0, lock, SeqCst, SeqCst) { + debug_assert!(other != 0); + CloseHandle(lock as HANDLE); + lock = other; + } + } + debug_assert!(lock != 0); + let lock = lock as HANDLE; + 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. + DBGHELP.ensure_open()?; + + static mut INITIALIZED: bool = false; + if INITIALIZED { + return Ok(ret); + } + + let orig = DBGHELP.SymGetOptions().unwrap()(); + + // 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().unwrap()(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().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE); + INITIALIZED = true; + Ok(ret) + } +} + +impl Drop for Init { + fn drop(&mut self) { + unsafe { + let r = ReleaseMutex(self.lock); + debug_assert!(r != 0); + } + } +} -- cgit v1.2.3