#![allow(non_snake_case)]
use alloc::vec::Vec;
use super::windows::*;
use core::mem;
use core::ptr;
use core::slice;
#[cfg(feature = "verify-winapi")]
mod dbghelp {
use crate::windows::*;
pub use winapi::um::dbghelp::{
StackWalk64, StackWalkEx, SymFromAddrW, SymFunctionTableAccess64, SymGetLineFromAddrW64,
SymGetModuleBase64, SymGetOptions, SymInitializeW, SymSetOptions,
};
extern "system" {
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 SymAddrIncludeInlineTrace(hProcess: HANDLE, Address: DWORD64) -> DWORD;
pub fn SymQueryInlineTrace(
hProcess: HANDLE,
StartAddress: DWORD64,
StartContext: DWORD,
StartRetAddress: DWORD64,
CurAddress: DWORD64,
CurContext: LPDWORD,
CurFrameIndex: LPDWORD,
) -> BOOL;
pub fn SymGetSearchPathW(
hprocess: HANDLE,
searchpatha: PWSTR,
searchpathlength: DWORD,
) -> BOOL;
pub fn SymSetSearchPathW(hprocess: HANDLE, searchpatha: PCWSTR) -> BOOL;
pub fn EnumerateLoadedModulesW64(
hprocess: HANDLE,
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
usercontext: PVOID,
) -> BOOL;
}
pub fn assert_equal_types<T>(a: T, _b: T) -> T {
a
}
}
macro_rules! dbghelp {
(extern "system" {
$(fn $name:ident($($arg:ident: $argty:ty),*) -> $ret: ty;)*
}) => (
pub struct Dbghelp {
dll: HMODULE,
$($name: usize,)*
}
static mut DBGHELP: Dbghelp = Dbghelp {
dll: ptr::null_mut(),
$($name: 0,)*
};
$(pub type $name = unsafe extern "system" fn($($argty),*) -> $ret;)*
impl Dbghelp {
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().cast::<i8>());
if self.dll.is_null() {
Err(())
} else {
Ok(())
}
}
}
$(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::<usize, $name>(self.$name);
#[cfg(feature = "verify-winapi")]
dbghelp::assert_equal_types(ret, dbghelp::$name);
Some(ret)
}
})*
fn symbol(&self, symbol: &[u8]) -> Option<usize> {
unsafe {
match GetProcAddress(self.dll, symbol.as_ptr().cast()) as usize {
0 => None,
n => Some(n),
}
}
}
}
#[allow(dead_code)]
impl Init {
$(pub fn $name(&self) -> $name {
unsafe {
DBGHELP.$name().unwrap()
}
})*
pub fn dbghelp(&self) -> *mut Dbghelp {
unsafe {
ptr::addr_of_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 SymGetSearchPathW(
hprocess: HANDLE,
searchpatha: PWSTR,
searchpathlength: DWORD
) -> BOOL;
fn SymSetSearchPathW(
hprocess: HANDLE,
searchpatha: PCWSTR
) -> BOOL;
fn EnumerateLoadedModulesW64(
hprocess: HANDLE,
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
usercontext: PVOID
) -> 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 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;
fn SymAddrIncludeInlineTrace(
hProcess: HANDLE,
Address: DWORD64
) -> DWORD;
fn SymQueryInlineTrace(
hProcess: HANDLE,
StartAddress: DWORD64,
StartContext: DWORD,
StartRetAddress: DWORD64,
CurAddress: DWORD64,
CurContext: LPDWORD,
CurFrameIndex: LPDWORD
) -> BOOL;
fn SymFromAddrW(
hProcess: HANDLE,
Address: DWORD64,
Displacement: PDWORD64,
Symbol: PSYMBOL_INFOW
) -> BOOL;
fn SymGetLineFromAddrW64(
hProcess: HANDLE,
dwAddr: DWORD64,
pdwDisplacement: PDWORD,
Line: PIMAGEHLP_LINEW64
) -> BOOL;
}
}
pub struct Init {
lock: HANDLE,
}
pub fn init() -> Result<Init, ()> {
use core::sync::atomic::{AtomicUsize, Ordering::SeqCst};
fn mutex_name() -> [u8; 33] {
let mut name: [u8; 33] = *b"Local\\RustBacktraceMutex00000000\0";
let mut id = unsafe { GetCurrentProcessId() };
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 {
static LOCK: AtomicUsize = AtomicUsize::new(0);
let mut lock = LOCK.load(SeqCst);
if lock == 0 {
let name = mutex_name();
lock = CreateMutexA(ptr::null_mut(), 0, name.as_ptr().cast::<i8>()) 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 };
DBGHELP.ensure_open()?;
static mut INITIALIZED: bool = false;
if INITIALIZED {
return Ok(ret);
}
let orig = DBGHELP.SymGetOptions().unwrap()();
DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS);
DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
let mut search_path_buf = Vec::new();
search_path_buf.resize(1024, 0);
if DBGHELP.SymGetSearchPathW().unwrap()(
GetCurrentProcess(),
search_path_buf.as_mut_ptr(),
search_path_buf.len() as _,
) == TRUE
{
let len = lstrlenW(search_path_buf.as_mut_ptr());
assert!(len >= 0);
search_path_buf.truncate(len as usize);
} else {
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);
DBGHELP.EnumerateLoadedModulesW64().unwrap()(
GetCurrentProcess(),
Some(enum_loaded_modules_callback),
((&mut search_path) as *mut SearchPath) as *mut c_void,
);
let new_search_path = search_path.finalize();
DBGHELP.SymSetSearchPathW().unwrap()(GetCurrentProcess(), new_search_path.as_ptr());
INITIALIZED = true;
Ok(ret)
}
}
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,
}
}
fn add(&mut self, path: &[u16]) {
let sep = utf16_char(';');
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> {
self.search_path_utf16.push(0);
self.search_path_utf16
}
}
extern "system" fn enum_loaded_modules_callback(
module_name: PCWSTR,
_: DWORD64,
_: ULONG,
user_context: PVOID,
) -> BOOL {
let len: usize = unsafe { lstrlenW(module_name).try_into().unwrap() };
if len == 0 {
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 {
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);
}
}
}