//! 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::{backtrace::StackFrame, dbghelp, windows::*}; use super::{BytesOrWideString, ResolveWhat, SymbolName}; use core::char; use core::ffi::c_void; use core::marker; use core::mem; use core::slice; // 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 as *mut _) } 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); 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_without_inline(&dbghelp, what.address_or_ip(), cb), ResolveWhat::Frame(frame) => match &frame.inner.stack_frame { StackFrame::New(frame) => resolve_with_inline(&dbghelp, frame, cb), StackFrame::Old(_) => resolve_without_inline(&dbghelp, frame.ip(), cb), }, } } unsafe fn resolve_with_inline( dbghelp: &dbghelp::Init, frame: &STACKFRAME_EX, cb: &mut dyn FnMut(&super::Symbol), ) { do_resolve( |info| { dbghelp.SymFromInlineContextW()( GetCurrentProcess(), super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64, frame.InlineFrameContext, &mut 0, info, ) }, |line| { dbghelp.SymGetLineFromInlineContextW()( GetCurrentProcess(), super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64, frame.InlineFrameContext, 0, &mut 0, line, ) }, cb, ) } unsafe fn resolve_without_inline( dbghelp: &dbghelp::Init, addr: *mut c_void, cb: &mut dyn FnMut(&super::Symbol), ) { do_resolve( |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr as DWORD64, &mut 0, info), |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr as DWORD64, &mut 0, line), cb, ) } 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 + mem::size_of::(); let mut data = Aligned8([0u8; SIZE]); let data = &mut data.0; let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW); info.MaxNameLen = MAX_SYM_NAME as ULONG; // 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() as *const u16; let name = slice::from_raw_parts(name_ptr, name_len); // 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.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 DWORD; let mut filename = None; let mut lineno = None; 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; filename = Some(slice::from_raw_parts(base, len) 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(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() {}