std\backtrace\src\symbolize/
dbghelp.rs

1//! Symbolication strategy using `dbghelp.dll` on Windows, only used for MSVC
2//!
3//! This symbolication strategy, like with backtraces, uses dynamically loaded
4//! information from `dbghelp.dll`. (see `src/dbghelp.rs` for info about why
5//! it's dynamically loaded).
6//!
7//! This API selects its resolution strategy based on the frame provided or the
8//! information we have at hand. If a frame from `StackWalkEx` is given to us
9//! then we use similar APIs to generate correct information about inlined
10//! functions. Otherwise if all we have is an address or an older stack frame
11//! from `StackWalk64` we use the older APIs for symbolication.
12//!
13//! There's a good deal of support in this module, but a good chunk of it is
14//! converting back and forth between Windows types and Rust types. For example
15//! symbols come to us as wide strings which we then convert to utf-8 strings if
16//! we can.
17
18#![allow(bad_style)]
19
20use super::super::{dbghelp, windows_sys::*};
21use super::{BytesOrWideString, ResolveWhat, SymbolName};
22use core::ffi::c_void;
23use core::marker;
24use core::mem;
25use core::ptr;
26use core::slice;
27
28// FIXME: replace with ptr::from_ref once MSRV is high enough
29#[inline(always)]
30#[must_use]
31const fn ptr_from_ref<T: ?Sized>(r: &T) -> *const T {
32    r
33}
34
35// Store an OsString on std so we can provide the symbol name and filename.
36pub struct Symbol<'a> {
37    name: *const [u8],
38    addr: *mut c_void,
39    line: Option<u32>,
40    filename: Option<*const [u16]>,
41    #[cfg(feature = "std")]
42    _filename_cache: Option<::std::ffi::OsString>,
43    #[cfg(not(feature = "std"))]
44    _filename_cache: (),
45    _marker: marker::PhantomData<&'a i32>,
46}
47
48impl Symbol<'_> {
49    pub fn name(&self) -> Option<SymbolName<'_>> {
50        Some(SymbolName::new(unsafe { &*self.name }))
51    }
52
53    pub fn addr(&self) -> Option<*mut c_void> {
54        Some(self.addr)
55    }
56
57    pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
58        self.filename
59            .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) })
60    }
61
62    pub fn colno(&self) -> Option<u32> {
63        None
64    }
65
66    pub fn lineno(&self) -> Option<u32> {
67        self.line
68    }
69
70    #[cfg(feature = "std")]
71    pub fn filename(&self) -> Option<&::std::path::Path> {
72        use std::path::Path;
73
74        self._filename_cache.as_ref().map(Path::new)
75    }
76}
77
78#[repr(C, align(8))]
79struct Aligned8<T>(T);
80
81#[cfg(not(target_vendor = "win7"))]
82pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
83    // Ensure this process's symbols are initialized
84    let dbghelp = match dbghelp::init() {
85        Ok(dbghelp) => dbghelp,
86        Err(()) => return, // oh well...
87    };
88    unsafe {
89        match what {
90            ResolveWhat::Address(_) => {
91                resolve_with_inline(&dbghelp, what.address_or_ip(), None, cb)
92            }
93            ResolveWhat::Frame(frame) => {
94                resolve_with_inline(&dbghelp, frame.ip(), frame.inner.inline_context(), cb)
95            }
96        };
97    }
98}
99
100#[cfg(target_vendor = "win7")]
101pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
102    // Ensure this process's symbols are initialized
103    let dbghelp = match dbghelp::init() {
104        Ok(dbghelp) => dbghelp,
105        Err(()) => return, // oh well...
106    };
107
108    unsafe {
109        let resolve_inner = if (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace().is_some() {
110            // We are on a version of dbghelp 6.2+, which contains the more modern
111            // Inline APIs.
112            resolve_with_inline
113        } else {
114            // We are on an older version of dbghelp which doesn't contain the Inline
115            // APIs.
116            resolve_legacy
117        };
118        match what {
119            ResolveWhat::Address(_) => resolve_inner(&dbghelp, what.address_or_ip(), None, cb),
120            ResolveWhat::Frame(frame) => {
121                resolve_inner(&dbghelp, frame.ip(), frame.inner.inline_context(), cb)
122            }
123        };
124    }
125}
126
127/// Resolve the address using the legacy dbghelp API.
128///
129/// This should work all the way down to Windows XP. The inline context is
130/// ignored, since this concept was only introduced in dbghelp 6.2+.
131#[cfg(target_vendor = "win7")]
132unsafe fn resolve_legacy(
133    dbghelp: &dbghelp::Init,
134    addr: *mut c_void,
135    _inline_context: Option<u32>,
136    cb: &mut dyn FnMut(&super::Symbol),
137) -> Option<()> {
138    let addr = super::adjust_ip(addr) as u64;
139    unsafe {
140        do_resolve(
141            |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr, &mut 0, info),
142            |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr, &mut 0, line),
143            cb,
144        );
145    }
146    Some(())
147}
148
149/// Resolve the address using the modern dbghelp APIs.
150///
151/// Note that calling this function requires having dbghelp 6.2+ loaded - and
152/// will panic otherwise.
153unsafe fn resolve_with_inline(
154    dbghelp: &dbghelp::Init,
155    addr: *mut c_void,
156    inline_context: Option<u32>,
157    cb: &mut dyn FnMut(&super::Symbol),
158) -> Option<()> {
159    unsafe {
160        let current_process = GetCurrentProcess();
161        // Ensure we have the functions we need. Return if any aren't found.
162        let SymFromInlineContextW = (*dbghelp.dbghelp()).SymFromInlineContextW()?;
163        let SymGetLineFromInlineContextW = (*dbghelp.dbghelp()).SymGetLineFromInlineContextW()?;
164
165        let addr = super::adjust_ip(addr) as u64;
166
167        let (inlined_frame_count, inline_context) = if let Some(ic) = inline_context {
168            (0, ic)
169        } else {
170            let SymAddrIncludeInlineTrace = (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace()?;
171            let SymQueryInlineTrace = (*dbghelp.dbghelp()).SymQueryInlineTrace()?;
172
173            let mut inlined_frame_count = SymAddrIncludeInlineTrace(current_process, addr);
174
175            let mut inline_context = 0;
176
177            // If there is are inlined frames but we can't load them for some reason OR if there are no
178            // inlined frames, then we disregard inlined_frame_count and inline_context.
179            if (inlined_frame_count > 0
180                && SymQueryInlineTrace(
181                    current_process,
182                    addr,
183                    0,
184                    addr,
185                    addr,
186                    &mut inline_context,
187                    &mut 0,
188                ) != TRUE)
189                || inlined_frame_count == 0
190            {
191                inlined_frame_count = 0;
192                inline_context = 0;
193            }
194
195            (inlined_frame_count, inline_context)
196        };
197
198        let last_inline_context = inline_context + 1 + inlined_frame_count;
199
200        for inline_context in inline_context..last_inline_context {
201            do_resolve(
202                |info| SymFromInlineContextW(current_process, addr, inline_context, &mut 0, info),
203                |line| {
204                    SymGetLineFromInlineContextW(
205                        current_process,
206                        addr,
207                        inline_context,
208                        0,
209                        &mut 0,
210                        line,
211                    )
212                },
213                cb,
214            );
215        }
216    }
217    Some(())
218}
219
220unsafe fn do_resolve(
221    sym_from_addr: impl FnOnce(*mut SYMBOL_INFOW) -> BOOL,
222    get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINEW64) -> BOOL,
223    cb: &mut dyn FnMut(&super::Symbol),
224) {
225    const SIZE: usize = 2 * MAX_SYM_NAME as usize + mem::size_of::<SYMBOL_INFOW>();
226    let mut data = Aligned8([0u8; SIZE]);
227    let info = unsafe { &mut *data.0.as_mut_ptr().cast::<SYMBOL_INFOW>() };
228    info.MaxNameLen = MAX_SYM_NAME as u32;
229    // the struct size in C.  the value is different to
230    // `size_of::<SYMBOL_INFOW>() - MAX_SYM_NAME + 1` (== 81)
231    // due to struct alignment.
232    info.SizeOfStruct = 88;
233
234    if sym_from_addr(info) != TRUE {
235        return;
236    }
237
238    // If the symbol name is greater than MaxNameLen, SymFromAddrW will
239    // give a buffer of (MaxNameLen - 1) characters and set NameLen to
240    // the real value.
241    let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1);
242    let name_ptr = info.Name.as_ptr().cast::<u16>();
243
244    // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like
245    // all other platforms
246    let mut name_buffer = [0_u8; 256];
247    let mut name_len = unsafe {
248        WideCharToMultiByte(
249            CP_UTF8,
250            0,
251            name_ptr,
252            name_len as i32,
253            name_buffer.as_mut_ptr(),
254            name_buffer.len() as i32,
255            core::ptr::null_mut(),
256            core::ptr::null_mut(),
257        ) as usize
258    };
259    if name_len == 0 {
260        // If the returned length is zero that means the buffer wasn't big enough.
261        // However, the buffer will be filled with as much as will fit.
262        name_len = name_buffer.len();
263    } else if name_len > name_buffer.len() {
264        // This can't happen.
265        return;
266    }
267    let name = ptr::addr_of!(name_buffer[..name_len]);
268
269    let mut line = IMAGEHLP_LINEW64 {
270        SizeOfStruct: 0,
271        Key: core::ptr::null_mut(),
272        LineNumber: 0,
273        FileName: core::ptr::null_mut(),
274        Address: 0,
275    };
276    line.SizeOfStruct = mem::size_of::<IMAGEHLP_LINEW64>() as u32;
277
278    let mut filename = None;
279    let mut lineno = None;
280    if get_line_from_addr(&mut line) == TRUE {
281        lineno = Some(line.LineNumber);
282
283        let base = line.FileName;
284        let mut len = 0;
285        while unsafe { *base.offset(len) != 0 } {
286            len += 1;
287        }
288
289        let len = len as usize;
290
291        unsafe {
292            filename = Some(ptr_from_ref(slice::from_raw_parts(base, len)));
293        }
294    }
295
296    cb(&super::Symbol {
297        inner: Symbol {
298            name,
299            addr: info.Address as *mut _,
300            line: lineno,
301            filename,
302            _filename_cache: unsafe { cache(filename) },
303            _marker: marker::PhantomData,
304        },
305    })
306}
307
308#[cfg(feature = "std")]
309unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> {
310    use std::os::windows::ffi::OsStringExt;
311    unsafe { filename.map(|f| ::std::ffi::OsString::from_wide(&*f)) }
312}
313
314#[cfg(not(feature = "std"))]
315unsafe fn cache(_filename: Option<*const [u16]>) {}
316
317pub unsafe fn clear_symbol_cache() {}