//! Symbolication strategy using `dbghelp.dll` on Windows, only used for MSVC //! //! This symbolication strategy, like with backtraces, uses dynamically loaded //! information from `dbghelp.dll`. (see `src/dbghelp.rs` for info about why //! it's dynamically loaded). //! //! This API selects its resolution strategy based on the frame provided or the //! information we have at hand. If a frame from `StackWalkEx` is given to us //! then we use similar APIs to generate correct information about inlined //! functions. Otherwise if all we have is an address or an older stack frame //! from `StackWalk64` we use the older APIs for symbolication. //! //! There's a good deal of support in this module, but a good chunk of it is //! converting back and forth between Windows types and Rust types. For example //! symbols come to us as wide strings which we then convert to utf-8 strings if //! we can. #![allow(bad_style)] use super::super::{dbghelp, windows_sys::*}; use super::{BytesOrWideString, ResolveWhat, SymbolName}; use core::ffi::c_void; use core::marker; use core::mem; use core::ptr; use core::slice; // FIXME: replace with ptr::from_ref once MSRV is high enough #[inline(always)] #[must_use] const fn ptr_from_ref(r: &T) -> *const T { r } // Store an OsString on std so we can provide the symbol name and filename. pub struct Symbol<'a> { name: *const [u8], addr: *mut c_void, line: Option, filename: Option<*const [u16]>, #[cfg(feature = "std")] _filename_cache: Option<::std::ffi::OsString>, #[cfg(not(feature = "std"))] _filename_cache: (), _marker: marker::PhantomData<&'a i32>, } impl Symbol<'_> { pub fn name(&self) -> Option> { Some(SymbolName::new(unsafe { &*self.name })) } pub fn addr(&self) -> Option<*mut c_void> { Some(self.addr) } pub fn filename_raw(&self) -> Option> { self.filename .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) }) } pub fn colno(&self) -> Option { None } pub fn lineno(&self) -> Option { self.line } #[cfg(feature = "std")] pub fn filename(&self) -> Option<&::std::path::Path> { use std::path::Path; self._filename_cache.as_ref().map(Path::new) } } #[repr(C, align(8))] struct Aligned8(T); #[cfg(not(target_vendor = "win7"))] 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... }; match what { ResolveWhat::Address(_) => resolve_with_inline(&dbghelp, what.address_or_ip(), None, cb), ResolveWhat::Frame(frame) => { resolve_with_inline(&dbghelp, frame.ip(), frame.inner.inline_context(), cb) } }; } #[cfg(target_vendor = "win7")] 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 { // We are on an older version of dbghelp which doesn't contain the Inline // APIs. resolve_legacy }; 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_vendor = "win7")] 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(()) } /// Resolve the address using the modern dbghelp APIs. /// /// Note that calling this function requires having dbghelp 6.2+ loaded - and /// will panic otherwise. unsafe fn resolve_with_inline( dbghelp: &dbghelp::Init, addr: *mut c_void, inline_context: Option, cb: &mut dyn FnMut(&super::Symbol), ) -> Option<()> { let current_process = GetCurrentProcess(); // Ensure we have the functions we need. Return if any aren't found. let SymFromInlineContextW = (*dbghelp.dbghelp()).SymFromInlineContextW()?; let SymGetLineFromInlineContextW = (*dbghelp.dbghelp()).SymGetLineFromInlineContextW()?; let addr = super::adjust_ip(addr) as u64; let (inlined_frame_count, inline_context) = if let Some(ic) = inline_context { (0, ic) } else { let SymAddrIncludeInlineTrace = (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace()?; let SymQueryInlineTrace = (*dbghelp.dbghelp()).SymQueryInlineTrace()?; let mut inlined_frame_count = SymAddrIncludeInlineTrace(current_process, addr); let mut inline_context = 0; // If there is are inlined frames but we can't load them for some reason OR if there are no // inlined frames, then we disregard inlined_frame_count and inline_context. if (inlined_frame_count > 0 && SymQueryInlineTrace( current_process, addr, 0, addr, addr, &mut inline_context, &mut 0, ) != TRUE) || inlined_frame_count == 0 { inlined_frame_count = 0; inline_context = 0; } (inlined_frame_count, inline_context) }; let last_inline_context = inline_context + 1 + inlined_frame_count; for inline_context in inline_context..last_inline_context { do_resolve( |info| SymFromInlineContextW(current_process, addr, inline_context, &mut 0, info), |line| { SymGetLineFromInlineContextW(current_process, addr, inline_context, 0, &mut 0, line) }, cb, ); } Some(()) } unsafe fn do_resolve( sym_from_addr: impl FnOnce(*mut SYMBOL_INFOW) -> BOOL, get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINEW64) -> BOOL, cb: &mut dyn FnMut(&super::Symbol), ) { const SIZE: usize = 2 * MAX_SYM_NAME as usize + mem::size_of::(); let mut data = Aligned8([0u8; SIZE]); let info = &mut *data.0.as_mut_ptr().cast::(); info.MaxNameLen = MAX_SYM_NAME as u32; // 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 name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1); let name_ptr = info.Name.as_ptr().cast::(); // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like // all other platforms let mut name_buffer = [0_u8; 256]; let mut name_len = WideCharToMultiByte( CP_UTF8, 0, name_ptr, name_len as i32, name_buffer.as_mut_ptr(), name_buffer.len() as i32, core::ptr::null_mut(), core::ptr::null_mut(), ) as usize; if name_len == 0 { // If the returned length is zero that means the buffer wasn't big enough. // However, the buffer will be filled with as much as will fit. name_len = name_buffer.len(); } else if name_len > name_buffer.len() { // This can't happen. return; } let name = ptr::addr_of!(name_buffer[..name_len]); let mut line = mem::zeroed::(); line.SizeOfStruct = mem::size_of::() as u32; let mut filename = None; let mut lineno = None; if get_line_from_addr(&mut line) == TRUE { lineno = Some(line.LineNumber); let base = line.FileName; let mut len = 0; while *base.offset(len) != 0 { len += 1; } let len = len as usize; filename = Some(ptr_from_ref(slice::from_raw_parts(base, len))); } cb(&super::Symbol { inner: Symbol { name, addr: info.Address as *mut _, line: lineno, filename, _filename_cache: cache(filename), _marker: marker::PhantomData, }, }) } #[cfg(feature = "std")] unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> { use std::os::windows::ffi::OsStringExt; filename.map(|f| ::std::ffi::OsString::from_wide(&*f)) } #[cfg(not(feature = "std"))] unsafe fn cache(_filename: Option<*const [u16]>) {} pub unsafe fn clear_symbol_cache() {}