From 48ee03cb5650a9ae57964e481f67731af634e8bf Mon Sep 17 00:00:00 2001 From: Dennis Duda Date: Thu, 1 Jan 2026 18:52:40 +0100 Subject: [PATCH 1/4] Manually implement RtlCaptureContext for x86 # Conflicts: # src/lib.rs --- src/backtrace/win32.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/backtrace/win32.rs b/src/backtrace/win32.rs index 4029609f..6af34a0a 100644 --- a/src/backtrace/win32.rs +++ b/src/backtrace/win32.rs @@ -218,3 +218,44 @@ fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> u16 { frame.addr_frame_mut().Mode = AddrModeFlat; IMAGE_FILE_MACHINE_ARMNT } + +#[cfg(all(target_arch = "x86", target_vendor = "rust9x"))] +#[naked] +unsafe extern "C" fn RtlCaptureContext(context: &mut CONTEXT) { + core::arch::naked_asm!( + " + push ebx + mov ebx, [esp+8] + + mov [ebx+0xB0], eax + mov [ebx+0xAC], ecx + mov [ebx+0xA8], edx + mov eax, [esp] + mov [ebx+0xA4], eax + mov [ebx+0xA0], esi + mov [ebx+0x9C], edi + + mov [ebx+0xBC], cs + mov [ebx+0x98], ds + mov [ebx+0x94], es + mov [ebx+0x90], fs + mov [ebx+0x8C], gs + mov [ebx+0xC8], ss + + pushfd + pop dword ptr [ebx] + + mov eax, [ebp+4] + mov [ebx+0xB8], eax + + mov eax, [ebp+0] + mov [ebx+0xB4], eax + + lea eax, [ebp+8] + mov [ebx+0xC4], eax + + pop ebx + ret 4 + ", + ) +} From 280d6fc655fe384a87c084e3c321ddec5b43eb14 Mon Sep 17 00:00:00 2001 From: Dennis Duda Date: Sat, 30 Nov 2024 16:32:13 +0100 Subject: [PATCH 2/4] Add more fallbacks for older dbghelp.dll Dbghelp 5.1.2600.5512 now works, albeit with slightly sketchy name handling. --- bindings.txt | 8 +- src/backtrace/win32.rs | 4 +- src/dbghelp.rs | 42 ++++---- src/symbolize/dbghelp.rs | 204 +++++++++++++++++++++++++++++++++++++-- src/windows_sys.rs | 62 +++++++----- 5 files changed, 264 insertions(+), 56 deletions(-) diff --git a/bindings.txt b/bindings.txt index 166224b9..8b1637aa 100644 --- a/bindings.txt +++ b/bindings.txt @@ -7,7 +7,9 @@ Windows.Win32.Foundation.HINSTANCE Windows.Win32.Foundation.INVALID_HANDLE_VALUE Windows.Win32.Foundation.TRUE Windows.Win32.Globalization.CP_UTF8 +Windows.Win32.Globalization.CP_THREAD_ACP Windows.Win32.Globalization.lstrlenW +Windows.Win32.Globalization.MultiByteToWideChar Windows.Win32.Globalization.WideCharToMultiByte Windows.Win32.System.Diagnostics.Debug.AddrModeFlat Windows.Win32.System.Diagnostics.Debug.CONTEXT @@ -28,15 +30,17 @@ Windows.Win32.System.Diagnostics.Debug.StackWalk64 Windows.Win32.System.Diagnostics.Debug.StackWalkEx Windows.Win32.System.Diagnostics.Debug.SymAddrIncludeInlineTrace Windows.Win32.System.Diagnostics.Debug.SYMBOL_INFOW +Windows.Win32.System.Diagnostics.Debug.SymFromAddr Windows.Win32.System.Diagnostics.Debug.SymFromAddrW Windows.Win32.System.Diagnostics.Debug.SymFromInlineContextW Windows.Win32.System.Diagnostics.Debug.SymFunctionTableAccess64 +Windows.Win32.System.Diagnostics.Debug.SymGetLineFromAddr64 Windows.Win32.System.Diagnostics.Debug.SymGetLineFromAddrW64 Windows.Win32.System.Diagnostics.Debug.SymGetLineFromInlineContextW Windows.Win32.System.Diagnostics.Debug.SymGetModuleBase64 Windows.Win32.System.Diagnostics.Debug.SymGetOptions Windows.Win32.System.Diagnostics.Debug.SymGetSearchPathW -Windows.Win32.System.Diagnostics.Debug.SymInitializeW +Windows.Win32.System.Diagnostics.Debug.SymInitialize Windows.Win32.System.Diagnostics.Debug.SYMOPT_DEFERRED_LOADS Windows.Win32.System.Diagnostics.Debug.SymQueryInlineTrace Windows.Win32.System.Diagnostics.Debug.SymSetOptions @@ -60,4 +64,4 @@ Windows.Win32.System.Threading.GetCurrentProcessId Windows.Win32.System.Threading.GetCurrentThread Windows.Win32.System.Threading.INFINITE Windows.Win32.System.Threading.ReleaseMutex -Windows.Win32.System.Threading.WaitForSingleObjectEx \ No newline at end of file +Windows.Win32.System.Threading.WaitForSingleObjectEx diff --git a/src/backtrace/win32.rs b/src/backtrace/win32.rs index 6af34a0a..1cc1ef7a 100644 --- a/src/backtrace/win32.rs +++ b/src/backtrace/win32.rs @@ -219,8 +219,8 @@ fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> u16 { IMAGE_FILE_MACHINE_ARMNT } -#[cfg(all(target_arch = "x86", target_vendor = "rust9x"))] -#[naked] +#[cfg(all(target_arch = "x86", target_family = "rust9x"))] +#[unsafe(naked)] unsafe extern "C" fn RtlCaptureContext(context: &mut CONTEXT) { core::arch::naked_asm!( " diff --git a/src/dbghelp.rs b/src/dbghelp.rs index c3bec8d1..3c644b3a 100644 --- a/src/dbghelp.rs +++ b/src/dbghelp.rs @@ -23,12 +23,10 @@ #![allow(non_snake_case)] use alloc::vec::Vec; +use core::ffi::c_void; +use core::{mem, ptr, slice}; 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. @@ -118,9 +116,9 @@ dbghelp! { extern "system" { fn SymGetOptions() -> u32; fn SymSetOptions(options: u32) -> u32; - fn SymInitializeW( + fn SymInitialize( handle: HANDLE, - path: PCWSTR, + path: PCSTR, invade: BOOL ) -> BOOL; fn SymGetSearchPathW( @@ -168,6 +166,18 @@ dbghelp! { pdwDisplacement: *mut u32, Line: *mut IMAGEHLP_LINEW64 ) -> BOOL; + fn SymFromAddr( + hProcess: HANDLE, + Address: u64, + Displacement: *mut u64, + Symbol: *mut SYMBOL_INFO + ) -> BOOL; + fn SymGetLineFromAddr64( + hProcess: HANDLE, + dwAddr: u64, + pdwDisplacement: *mut u32, + Line: *mut IMAGEHLP_LINE64 + ) -> BOOL; fn StackWalkEx( MachineType: u32, hProcess: HANDLE, @@ -222,7 +232,8 @@ pub struct Init { /// synchronization. Also note that it is safe to call this function multiple /// times recursively. pub fn init() -> Result { - use core::sync::atomic::{AtomicPtr, Ordering::SeqCst}; + use core::sync::atomic::AtomicPtr; + use core::sync::atomic::Ordering::SeqCst; // Helper function for generating a name that's unique to the process. fn mutex_name() -> [u8; 33] { @@ -340,7 +351,7 @@ unsafe fn set_optional_options(dbghelp: *mut Dbghelp) -> Option<()> { // 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); + (*dbghelp).SymInitialize()?(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`. @@ -401,9 +412,7 @@ fn utf16_char(c: char) -> u16 { impl SearchPath { fn new(initial_search_path: Vec) -> Self { - Self { - search_path_utf16: initial_search_path, - } + Self { search_path_utf16: initial_search_path } } /// Add a path to the search path if it is not already present. @@ -413,11 +422,7 @@ impl SearchPath { // 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.split(|&c| c == sep).any(|p| p == path) { if self.search_path_utf16.last() != Some(&sep) { self.search_path_utf16.push(sep); } @@ -451,9 +456,8 @@ extern "system" fn enum_loaded_modules_callback( 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) + 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. diff --git a/src/symbolize/dbghelp.rs b/src/symbolize/dbghelp.rs index 62ce30c8..6a2c5aa8 100644 --- a/src/symbolize/dbghelp.rs +++ b/src/symbolize/dbghelp.rs @@ -17,14 +17,13 @@ #![allow(bad_style)] -use super::super::{dbghelp, windows_sys::*}; -use super::{BytesOrWideString, ResolveWhat, SymbolName}; -use core::cmp; use core::ffi::c_void; -use core::marker; use core::mem::{self, MaybeUninit}; -use core::ptr; -use core::slice; +use core::{cmp, marker, ptr, slice}; + +use super::super::dbghelp; +use super::super::windows_sys::*; +use super::{BytesOrWideString, ResolveWhat, SymbolName}; // FIXME: replace with ptr::from_ref once MSRV is high enough #[inline(always)] @@ -56,8 +55,7 @@ impl Symbol<'_> { } pub fn filename_raw(&self) -> Option> { - self.filename - .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) }) + self.filename.map(|slice| unsafe { BytesOrWideString::Wide(&*slice) }) } pub fn colno(&self) -> Option { @@ -79,7 +77,7 @@ impl Symbol<'_> { #[repr(C, align(8))] struct Aligned8(T); -#[cfg(not(target_vendor = "win7"))] +#[cfg(not(any(target_vendor = "win7", target_family = "rust9x")))] pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) { // Ensure this process's symbols are initialized let dbghelp = match dbghelp::init() { @@ -147,6 +145,74 @@ unsafe fn resolve_legacy( Some(()) } +#[cfg(target_family = "rust9x")] +pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) { + // Ensure this process's symbols are initialized + let dbghelp = match dbghelp::init() { + Ok(dbghelp) => dbghelp, + Err(()) => return, // oh well... + }; + + let resolve_inner = if (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace().is_some() { + // We are on a version of dbghelp 6.2+, which contains the more modern + // Inline APIs. + resolve_with_inline + } else if (*dbghelp.dbghelp()).SymFromAddrW().is_some() + && (*dbghelp.dbghelp()).SymGetLineFromAddrW64().is_some() + { + // We are on an older version of dbghelp which doesn't contain the Inline + // APIs. + resolve_legacy + } else { + resolve_legacy_multibyte + }; + match what { + ResolveWhat::Address(_) => resolve_inner(&dbghelp, what.address_or_ip(), None, cb), + ResolveWhat::Frame(frame) => { + resolve_inner(&dbghelp, frame.ip(), frame.inner.inline_context(), cb) + } + }; +} + +/// Resolve the address using the legacy dbghelp API. +/// +/// This should work all the way down to Windows XP. The inline context is +/// ignored, since this concept was only introduced in dbghelp 6.2+. +#[cfg(target_family = "rust9x")] +unsafe fn resolve_legacy( + dbghelp: &dbghelp::Init, + addr: *mut c_void, + _inline_context: Option, + cb: &mut dyn FnMut(&super::Symbol), +) -> Option<()> { + let addr = super::adjust_ip(addr) as u64; + do_resolve( + |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr, &mut 0, info), + |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr, &mut 0, line), + cb, + ); + Some(()) +} + +// dbghelp 5.1 (Version included with Windows XP) doesn't have some of the wide APIs, so we have to +// use the ANSI ones. +#[cfg(target_family = "rust9x")] +#[inline(never)] +unsafe fn resolve_legacy_multibyte( + dbghelp: &dbghelp::Init, + addr: *mut c_void, + _inline_context: Option, + cb: &mut dyn FnMut(&super::Symbol), +) -> Option<()> { + let addr = super::adjust_ip(addr) as u64; + do_resolve_multibyte( + |info| dbghelp.SymFromAddr()(GetCurrentProcess(), addr, &mut 0, info), + |line| dbghelp.SymGetLineFromAddr64()(GetCurrentProcess(), addr, &mut 0, line), + cb, + ); + Some(()) +} + /// Resolve the address using the modern dbghelp APIs. /// /// Note that calling this function requires having dbghelp 6.2+ loaded - and @@ -313,6 +379,126 @@ unsafe fn do_resolve( }) } +#[cfg(target_family = "rust9x")] +#[inline(never)] +unsafe fn do_resolve_multibyte( + sym_from_addr: impl FnOnce(*mut SYMBOL_INFO) -> BOOL, + get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINE64) -> BOOL, + cb: &mut dyn FnMut(&super::Symbol), +) { + const SIZE: usize = MAX_SYM_NAME as usize + mem::size_of::(); + let mut data = Aligned8([0u8; SIZE]); + let data = &mut data.0; + let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFO); + info.MaxNameLen = MAX_SYM_NAME; + // the struct size in C. the value is different to + // `size_of::() - MAX_SYM_NAME + 1` (== 81) + // due to struct alignment. + info.SizeOfStruct = 88; + + if sym_from_addr(info) != TRUE { + return; + } + + // If the symbol name is greater than MaxNameLen, SymFromAddrW will + // give a buffer of (MaxNameLen - 1) characters and set NameLen to + // the real value. + + let multibyte_len = if info.NameLen == 0 { + // NameLen is not set correctly on some versions of dbghelp, but the name is still provided. + // It seems that older versions of dbghelp don't support whatever #[track_caller] uses, so + // some of the symbol names are incorrect/missing/mangled. Still, better than nothing. + let ptr = info.Name.as_ptr(); + 'len: { + for i in 0..(info.MaxNameLen - 1) as isize { + if *ptr.offset(i) == 0 { + break 'len i as usize; + } + } + + 0 + } + } else { + ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1) + }; + + let multibyte_ptr = info.Name.as_ptr().cast(); + let multibyte_str = slice::from_raw_parts(multibyte_ptr, multibyte_len); + + // Reencode from local charset to utf-16 + let mut name_buffer_utf16 = [0; 260 * 2]; + let utf16_len = convert_multibyte(&mut name_buffer_utf16, multibyte_str); + + // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like + // all other platforms + let mut name_len = 0; + let mut name_buffer = [0; 256]; + { + let mut remaining = &mut name_buffer[..]; + for c in char::decode_utf16(name_buffer_utf16[..utf16_len].iter().cloned()) { + let c = c.unwrap_or(char::REPLACEMENT_CHARACTER); + let len = c.len_utf8(); + if len < remaining.len() { + c.encode_utf8(remaining); + let tmp = remaining; + remaining = &mut tmp[len..]; + name_len += len; + } else { + break; + } + } + } + let name = &name_buffer[..name_len] as *const [u8]; + + let mut line = mem::zeroed::(); + line.SizeOfStruct = mem::size_of::() as u32; + + let mut filename = None; + let mut lineno = None; + let mut filename_buffer_utf16 = [0; 260 * 2]; + if get_line_from_addr(&mut line) == TRUE { + lineno = Some(line.LineNumber as u32); + + let base = line.FileName; + let mut len = 0; + while *base.offset(len) != 0 { + len += 1; + } + + let len = len as usize; + let filename_multibyte = slice::from_raw_parts(base, len); + // Reencode from local charset to utf-16 + let len_in_wchars = convert_multibyte(&mut filename_buffer_utf16, filename_multibyte); + + filename = Some(&filename_buffer_utf16[..len_in_wchars] as *const [u16]); + } + + cb(&super::Symbol { + inner: Symbol { + name, + addr: info.Address as *mut _, + line: lineno, + filename, + _filename_cache: cache(filename), + _marker: marker::PhantomData, + }, + }) +} + +#[cfg(target_family = "rust9x")] +unsafe fn convert_multibyte(output: &mut [u16; 260 * 2], input: &[u8]) -> usize { + let input_len = input.len().min(260) as i32; + let size = + MultiByteToWideChar(CP_THREAD_ACP, 0, input.as_ptr(), input_len, output.as_mut_ptr(), 0); + + if size >= 260 * 2 - 1 { + return 0; + } + + MultiByteToWideChar(CP_THREAD_ACP, 0, input.as_ptr(), input_len, output.as_mut_ptr(), size) + as usize +} + #[cfg(feature = "std")] unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> { use std::os::windows::ffi::OsStringExt; diff --git a/src/windows_sys.rs b/src/windows_sys.rs index ddd5393c..030cd47e 100644 --- a/src/windows_sys.rs +++ b/src/windows_sys.rs @@ -1,25 +1,21 @@ // Bindings generated by `windows-bindgen` 0.58.0 -#![allow( - non_snake_case, - non_upper_case_globals, - non_camel_case_types, - dead_code, - clippy::all -)] +#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] windows_link::link!("dbghelp.dll" "system" fn EnumerateLoadedModulesW64(hprocess : HANDLE, enumloadedmodulescallback : PENUMLOADED_MODULES_CALLBACKW64, usercontext : *const core::ffi::c_void) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn StackWalk64(machinetype : u32, hprocess : HANDLE, hthread : HANDLE, stackframe : *mut STACKFRAME64, contextrecord : *mut core::ffi::c_void, readmemoryroutine : PREAD_PROCESS_MEMORY_ROUTINE64, functiontableaccessroutine : PFUNCTION_TABLE_ACCESS_ROUTINE64, getmodulebaseroutine : PGET_MODULE_BASE_ROUTINE64, translateaddress : PTRANSLATE_ADDRESS_ROUTINE64) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn StackWalkEx(machinetype : u32, hprocess : HANDLE, hthread : HANDLE, stackframe : *mut STACKFRAME_EX, contextrecord : *mut core::ffi::c_void, readmemoryroutine : PREAD_PROCESS_MEMORY_ROUTINE64, functiontableaccessroutine : PFUNCTION_TABLE_ACCESS_ROUTINE64, getmodulebaseroutine : PGET_MODULE_BASE_ROUTINE64, translateaddress : PTRANSLATE_ADDRESS_ROUTINE64, flags : u32) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymAddrIncludeInlineTrace(hprocess : HANDLE, address : u64) -> u32); +windows_link::link!("dbghelp.dll" "system" fn SymFromAddr(hprocess : HANDLE, address : u64, displacement : *mut u64, symbol : *mut SYMBOL_INFO) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymFromAddrW(hprocess : HANDLE, address : u64, displacement : *mut u64, symbol : *mut SYMBOL_INFOW) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymFromInlineContextW(hprocess : HANDLE, address : u64, inlinecontext : u32, displacement : *mut u64, symbol : *mut SYMBOL_INFOW) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymFunctionTableAccess64(hprocess : HANDLE, addrbase : u64) -> *mut core::ffi::c_void); +windows_link::link!("dbghelp.dll" "system" fn SymGetLineFromAddr64(hprocess : HANDLE, qwaddr : u64, pdwdisplacement : *mut u32, line64 : *mut IMAGEHLP_LINE64) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymGetLineFromAddrW64(hprocess : HANDLE, dwaddr : u64, pdwdisplacement : *mut u32, line : *mut IMAGEHLP_LINEW64) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymGetLineFromInlineContextW(hprocess : HANDLE, dwaddr : u64, inlinecontext : u32, qwmodulebaseaddress : u64, pdwdisplacement : *mut u32, line : *mut IMAGEHLP_LINEW64) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymGetModuleBase64(hprocess : HANDLE, qwaddr : u64) -> u64); windows_link::link!("dbghelp.dll" "system" fn SymGetOptions() -> u32); windows_link::link!("dbghelp.dll" "system" fn SymGetSearchPathW(hprocess : HANDLE, searchpatha : PWSTR, searchpathlength : u32) -> BOOL); -windows_link::link!("dbghelp.dll" "system" fn SymInitializeW(hprocess : HANDLE, usersearchpath : PCWSTR, finvadeprocess : BOOL) -> BOOL); +windows_link::link!("dbghelp.dll" "system" fn SymInitialize(hprocess : HANDLE, usersearchpath : PCSTR, finvadeprocess : BOOL) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymQueryInlineTrace(hprocess : HANDLE, startaddress : u64, startcontext : u32, startretaddress : u64, curaddress : u64, curcontext : *mut u32, curframeindex : *mut u32) -> BOOL); windows_link::link!("dbghelp.dll" "system" fn SymSetOptions(symoptions : u32) -> u32); windows_link::link!("dbghelp.dll" "system" fn SymSetSearchPathW(hprocess : HANDLE, searchpatha : PCWSTR) -> BOOL); @@ -35,6 +31,7 @@ windows_link::link!("kernel32.dll" "system" fn LoadLibraryA(lplibfilename : PCST windows_link::link!("kernel32.dll" "system" fn MapViewOfFile(hfilemappingobject : HANDLE, dwdesiredaccess : FILE_MAP, dwfileoffsethigh : u32, dwfileoffsetlow : u32, dwnumberofbytestomap : usize) -> MEMORY_MAPPED_VIEW_ADDRESS); windows_link::link!("kernel32.dll" "system" fn Module32FirstW(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32W) -> BOOL); windows_link::link!("kernel32.dll" "system" fn Module32NextW(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32W) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn MultiByteToWideChar(codepage : u32, dwflags : MULTI_BYTE_TO_WIDE_CHAR_FLAGS, lpmultibytestr : PCSTR, cbmultibyte : i32, lpwidecharstr : PWSTR, cchwidechar : i32) -> i32); windows_link::link!("kernel32.dll" "system" fn ReleaseMutex(hmutex : HANDLE) -> BOOL); windows_link::link!("kernel32.dll" "system" fn RtlCaptureContext(contextrecord : *mut CONTEXT)); #[cfg(target_arch = "aarch64")] @@ -245,8 +242,8 @@ pub struct CONTEXT { pub SegSs: u32, pub ExtendedRegisters: [u8; 512], } - pub type CONTEXT_FLAGS = u32; +pub const CP_THREAD_ACP: u32 = 3u32; pub const CP_UTF8: u32 = 65001u32; pub type CREATE_TOOLHELP_SNAPSHOT_FLAGS = u32; pub type EXCEPTION_DISPOSITION = i32; @@ -273,11 +270,7 @@ pub type FARPROC = Option isize>; pub type FILE_MAP = u32; pub const FILE_MAP_READ: FILE_MAP = 4u32; #[repr(C)] -#[cfg(any( - target_arch = "aarch64", - target_arch = "arm64ec", - target_arch = "x86_64" -))] +#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec", target_arch = "x86_64"))] #[derive(Clone, Copy)] pub struct FLOATING_SAVE_AREA { pub ControlWord: u32, @@ -309,6 +302,15 @@ pub type HINSTANCE = *mut core::ffi::c_void; pub type HMODULE = *mut core::ffi::c_void; #[repr(C)] #[derive(Clone, Copy)] +pub struct IMAGEHLP_LINE64 { + pub SizeOfStruct: u32, + pub Key: *mut core::ffi::c_void, + pub LineNumber: u32, + pub FileName: PSTR, + pub Address: u64, +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct IMAGEHLP_LINEW64 { pub SizeOfStruct: u32, pub Key: *mut core::ffi::c_void, @@ -491,6 +493,7 @@ pub struct MODULEENTRY32W { pub szModule: [u16; 256], pub szExePath: [u16; 260], } +pub type MULTI_BYTE_TO_WIDE_CHAR_FLAGS = u32; pub type NTSTATUS = i32; pub type PAGE_PROTECTION_FLAGS = u32; pub const PAGE_READONLY: PAGE_PROTECTION_FLAGS = 2u32; @@ -564,6 +567,25 @@ pub struct STACKFRAME_EX { } #[repr(C)] #[derive(Clone, Copy)] +pub struct SYMBOL_INFO { + pub SizeOfStruct: u32, + pub TypeIndex: u32, + pub Reserved: [u64; 2], + pub Index: u32, + pub Size: u32, + pub ModBase: u64, + pub Flags: SYMBOL_INFO_FLAGS, + pub Value: u64, + pub Address: u64, + pub Register: u32, + pub Scope: u32, + pub Tag: u32, + pub NameLen: u32, + pub MaxNameLen: u32, + pub Name: [i8; 1], +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct SYMBOL_INFOW { pub SizeOfStruct: u32, pub TypeIndex: u32, @@ -586,11 +608,7 @@ pub const SYMOPT_DEFERRED_LOADS: u32 = 4u32; pub const TH32CS_SNAPMODULE: CREATE_TOOLHELP_SNAPSHOT_FLAGS = 8u32; pub const TRUE: BOOL = 1i32; #[repr(C)] -#[cfg(any( - target_arch = "aarch64", - target_arch = "arm64ec", - target_arch = "x86_64" -))] +#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec", target_arch = "x86_64"))] #[derive(Clone, Copy)] pub struct UNWIND_HISTORY_TABLE { pub Count: u32, @@ -618,11 +636,7 @@ pub struct UNWIND_HISTORY_TABLE_ENTRY { } pub type WAIT_EVENT = u32; #[repr(C)] -#[cfg(any( - target_arch = "aarch64", - target_arch = "arm64ec", - target_arch = "x86_64" -))] +#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec", target_arch = "x86_64"))] #[derive(Clone, Copy)] pub struct XSAVE_FORMAT { pub ControlWord: u16, From 1f23107e2cc01f8d66327055b4dcac7749d1fd9b Mon Sep 17 00:00:00 2001 From: Jookia Date: Thu, 2 Jul 2026 03:39:57 +1000 Subject: [PATCH 3/4] win32: Mark RtlCaptureContext as stdcall RtlCaptureContext is a Windows NT API and uses the stdcall calling convention. Failing to adhere to this can cause stack overflow and corruption later on in the program. --- src/backtrace/win32.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backtrace/win32.rs b/src/backtrace/win32.rs index 1cc1ef7a..2285084d 100644 --- a/src/backtrace/win32.rs +++ b/src/backtrace/win32.rs @@ -221,7 +221,7 @@ fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> u16 { #[cfg(all(target_arch = "x86", target_family = "rust9x"))] #[unsafe(naked)] -unsafe extern "C" fn RtlCaptureContext(context: &mut CONTEXT) { +unsafe extern "stdcall" fn RtlCaptureContext(context: &mut CONTEXT) { core::arch::naked_asm!( " push ebx From fdb085c07cd0f2dda2e8d3a3aa21359f420b7281 Mon Sep 17 00:00:00 2001 From: Jookia Date: Thu, 2 Jul 2026 05:04:33 +1000 Subject: [PATCH 4/4] TODO --- bindings.txt | 4 ++ src/symbolize/gimli/libs_windows.rs | 63 +++++++++++++++++++++++++++++ src/windows_sys.rs | 18 +++++++++ 3 files changed, 85 insertions(+) diff --git a/bindings.txt b/bindings.txt index 8b1637aa..692fc58b 100644 --- a/bindings.txt +++ b/bindings.txt @@ -7,6 +7,7 @@ Windows.Win32.Foundation.HINSTANCE Windows.Win32.Foundation.INVALID_HANDLE_VALUE Windows.Win32.Foundation.TRUE Windows.Win32.Globalization.CP_UTF8 +Windows.Win32.Globalization.CP_ACP Windows.Win32.Globalization.CP_THREAD_ACP Windows.Win32.Globalization.lstrlenW Windows.Win32.Globalization.MultiByteToWideChar @@ -46,10 +47,13 @@ Windows.Win32.System.Diagnostics.Debug.SymQueryInlineTrace Windows.Win32.System.Diagnostics.Debug.SymSetOptions Windows.Win32.System.Diagnostics.Debug.SymSetSearchPathW Windows.Win32.System.Diagnostics.ToolHelp.CreateToolhelp32Snapshot +Windows.Win32.System.Diagnostics.ToolHelp.Module32First Windows.Win32.System.Diagnostics.ToolHelp.Module32FirstW +Windows.Win32.System.Diagnostics.ToolHelp.Module32Next Windows.Win32.System.Diagnostics.ToolHelp.Module32NextW Windows.Win32.System.Diagnostics.ToolHelp.MODULEENTRY32W Windows.Win32.System.Diagnostics.ToolHelp.TH32CS_SNAPMODULE +Windows.Win32.System.LibraryLoader.GetModuleHandleA Windows.Win32.System.LibraryLoader.GetProcAddress Windows.Win32.System.LibraryLoader.LoadLibraryA Windows.Win32.System.Memory.CreateFileMappingA diff --git a/src/symbolize/gimli/libs_windows.rs b/src/symbolize/gimli/libs_windows.rs index 7dca7f29..a74907f8 100644 --- a/src/symbolize/gimli/libs_windows.rs +++ b/src/symbolize/gimli/libs_windows.rs @@ -5,6 +5,7 @@ use alloc::vec; use alloc::vec::Vec; use core::mem; use core::mem::MaybeUninit; +use core::ptr; // For loading native libraries on Windows, see some discussion on // rust-lang/rust#71060 for the various strategies here. @@ -16,6 +17,30 @@ pub(super) fn native_libraries() -> Vec { return ret; } +#[cfg(target_family = "rust9x")] +unsafe fn module_entry_narrow_to_wide(narrow: &MODULEENTRY32) -> MODULEENTRY32W { + let mut out = MaybeUninit::::zeroed().assume_init(); + + // This is safe because MODULEENTRY32 is identical to MODULEENTRY32W aside from + // having a smaller end of the structure which we will replace anyway + let src: *const MODULEENTRY32 = narrow; + let dst: *mut MODULEENTRY32W = &mut out; + let len = mem::size_of::(); + ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, len); + + unsafe fn convert_path(narrow: &[u8], wide: &mut [u16]) { + MultiByteToWideChar(CP_ACP, 0, + narrow.as_ptr(), narrow.len().try_into().unwrap(), + wide.as_mut_ptr(), wide.len().try_into().unwrap()); + } + + convert_path(&narrow.szModule, &mut out.szModule); + convert_path(&narrow.szExePath, &mut out.szExePath); + + out +} + +#[cfg(not(target_family = "rust9x"))] unsafe fn add_loaded_images(ret: &mut Vec) { unsafe { let snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); @@ -42,6 +67,44 @@ unsafe fn add_loaded_images(ret: &mut Vec) { } } +#[cfg(target_family = "rust9x")] +unsafe fn add_loaded_images(ret: &mut Vec) { + unsafe { + let module = GetModuleHandleA(c"KERNEL32.DLL".as_ptr().cast()); // TODO: may be 0 + + let Some(snapshot_proc) = GetProcAddress(module, c"CreateToolhelp32Snapshot".as_ptr().cast()) else { return }; + let Some(module32first_proc) = GetProcAddress(module, c"Module32First".as_ptr().cast()) else { return }; + let Some(module32next_proc) = GetProcAddress(module, c"Module32Next".as_ptr().cast()) else { return }; + + let snapshot_func: extern "stdcall" fn(CREATE_TOOLHELP_SNAPSHOT_FLAGS, u32) -> HANDLE = mem::transmute(snapshot_proc); + let module32first_func: extern "stdcall" fn(HANDLE, *mut MODULEENTRY32) -> BOOL = mem::transmute(module32first_proc); + let module32next_func: extern "stdcall" fn(HANDLE, *mut MODULEENTRY32) -> BOOL = mem::transmute(module32next_proc); + + let snap = snapshot_func(TH32CS_SNAPMODULE, 0); + if snap == INVALID_HANDLE_VALUE { + return; + } + + // huge struct, probably should avoid manually initializing it even if we can + let mut me = MaybeUninit::::zeroed().assume_init(); + me.dwSize = mem::size_of_val(&me) as u32; + if module32first_func(snap, &mut me) == TRUE { + loop { + let me_wide = module_entry_narrow_to_wide(&me); + if let Some(lib) = load_library(&me_wide) { + ret.push(lib); + } + + if module32next_func(snap, &mut me) != TRUE { + break; + } + } + } + + CloseHandle(snap); + } +} + // Safety: long_path should be null-terminated #[cfg(target_os = "cygwin")] unsafe fn get_posix_path(long_path: &[u16]) -> Option { diff --git a/src/windows_sys.rs b/src/windows_sys.rs index 030cd47e..22378058 100644 --- a/src/windows_sys.rs +++ b/src/windows_sys.rs @@ -26,10 +26,13 @@ windows_link::link!("kernel32.dll" "system" fn CreateToolhelp32Snapshot(dwflags windows_link::link!("kernel32.dll" "system" fn GetCurrentProcess() -> HANDLE); windows_link::link!("kernel32.dll" "system" fn GetCurrentProcessId() -> u32); windows_link::link!("kernel32.dll" "system" fn GetCurrentThread() -> HANDLE); +windows_link::link!("kernel32.dll" "system" fn GetModuleHandleA(lpmodulename : PCSTR) -> HMODULE); windows_link::link!("kernel32.dll" "system" fn GetProcAddress(hmodule : HMODULE, lpprocname : PCSTR) -> FARPROC); windows_link::link!("kernel32.dll" "system" fn LoadLibraryA(lplibfilename : PCSTR) -> HMODULE); windows_link::link!("kernel32.dll" "system" fn MapViewOfFile(hfilemappingobject : HANDLE, dwdesiredaccess : FILE_MAP, dwfileoffsethigh : u32, dwfileoffsetlow : u32, dwnumberofbytestomap : usize) -> MEMORY_MAPPED_VIEW_ADDRESS); +windows_link::link!("kernel32.dll" "system" fn Module32First(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32) -> BOOL); windows_link::link!("kernel32.dll" "system" fn Module32FirstW(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32W) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn Module32Next(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32) -> BOOL); windows_link::link!("kernel32.dll" "system" fn Module32NextW(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32W) -> BOOL); windows_link::link!("kernel32.dll" "system" fn MultiByteToWideChar(codepage : u32, dwflags : MULTI_BYTE_TO_WIDE_CHAR_FLAGS, lpmultibytestr : PCSTR, cbmultibyte : i32, lpwidecharstr : PWSTR, cchwidechar : i32) -> i32); windows_link::link!("kernel32.dll" "system" fn ReleaseMutex(hmutex : HANDLE) -> BOOL); @@ -243,6 +246,7 @@ pub struct CONTEXT { pub ExtendedRegisters: [u8; 512], } pub type CONTEXT_FLAGS = u32; +pub const CP_ACP: u32 = 0u32; pub const CP_THREAD_ACP: u32 = 3u32; pub const CP_UTF8: u32 = 65001u32; pub type CREATE_TOOLHELP_SNAPSHOT_FLAGS = u32; @@ -481,6 +485,20 @@ pub struct MEMORY_MAPPED_VIEW_ADDRESS { } #[repr(C)] #[derive(Clone, Copy)] +pub struct MODULEENTRY32 { + pub dwSize: u32, + pub th32ModuleID: u32, + pub th32ProcessID: u32, + pub GlblcntUsage: u32, + pub ProccntUsage: u32, + pub modBaseAddr: *mut u8, + pub modBaseSize: u32, + pub hModule: HMODULE, + pub szModule: [u8; 256], + pub szExePath: [u8; 260], +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct MODULEENTRY32W { pub dwSize: u32, pub th32ModuleID: u32,