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