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 
20 use super::super::{backtrace::StackFrame, dbghelp, windows::*};
21 use super::{BytesOrWideString, ResolveWhat, SymbolName};
22 use core::char;
23 use core::ffi::c_void;
24 use core::marker;
25 use core::mem;
26 use core::slice;
27 
28 // Store an OsString on std so we can provide the symbol name and filename.
29 pub struct Symbol<'a> {
30     name: *const [u8],
31     addr: *mut c_void,
32     line: Option<u32>,
33     filename: Option<*const [u16]>,
34     #[cfg(feature = "std")]
35     _filename_cache: Option<::std::ffi::OsString>,
36     #[cfg(not(feature = "std"))]
37     _filename_cache: (),
38     _marker: marker::PhantomData<&'a i32>,
39 }
40 
41 impl Symbol<'_> {
name(&self) -> Option<SymbolName<'_>>42     pub fn name(&self) -> Option<SymbolName<'_>> {
43         Some(SymbolName::new(unsafe { &*self.name }))
44     }
45 
addr(&self) -> Option<*mut c_void>46     pub fn addr(&self) -> Option<*mut c_void> {
47         Some(self.addr as *mut _)
48     }
49 
filename_raw(&self) -> Option<BytesOrWideString<'_>>50     pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
51         self.filename
52             .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) })
53     }
54 
colno(&self) -> Option<u32>55     pub fn colno(&self) -> Option<u32> {
56         None
57     }
58 
lineno(&self) -> Option<u32>59     pub fn lineno(&self) -> Option<u32> {
60         self.line
61     }
62 
63     #[cfg(feature = "std")]
filename(&self) -> Option<&::std::path::Path>64     pub fn filename(&self) -> Option<&::std::path::Path> {
65         use std::path::Path;
66 
67         self._filename_cache.as_ref().map(Path::new)
68     }
69 }
70 
71 #[repr(C, align(8))]
72 struct Aligned8<T>(T);
73 
resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol))74 pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
75     // Ensure this process's symbols are initialized
76     let dbghelp = match dbghelp::init() {
77         Ok(dbghelp) => dbghelp,
78         Err(()) => return, // oh well...
79     };
80 
81     match what {
82         ResolveWhat::Address(_) => resolve_without_inline(&dbghelp, what.address_or_ip(), cb),
83         ResolveWhat::Frame(frame) => match &frame.inner.stack_frame {
84             StackFrame::New(frame) => resolve_with_inline(&dbghelp, frame, cb),
85             StackFrame::Old(_) => resolve_without_inline(&dbghelp, frame.ip(), cb),
86         },
87     }
88 }
89 
resolve_with_inline( dbghelp: &dbghelp::Init, frame: &STACKFRAME_EX, cb: &mut dyn FnMut(&super::Symbol), )90 unsafe fn resolve_with_inline(
91     dbghelp: &dbghelp::Init,
92     frame: &STACKFRAME_EX,
93     cb: &mut dyn FnMut(&super::Symbol),
94 ) {
95     do_resolve(
96         |info| {
97             dbghelp.SymFromInlineContextW()(
98                 GetCurrentProcess(),
99                 super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64,
100                 frame.InlineFrameContext,
101                 &mut 0,
102                 info,
103             )
104         },
105         |line| {
106             dbghelp.SymGetLineFromInlineContextW()(
107                 GetCurrentProcess(),
108                 super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64,
109                 frame.InlineFrameContext,
110                 0,
111                 &mut 0,
112                 line,
113             )
114         },
115         cb,
116     )
117 }
118 
resolve_without_inline( dbghelp: &dbghelp::Init, addr: *mut c_void, cb: &mut dyn FnMut(&super::Symbol), )119 unsafe fn resolve_without_inline(
120     dbghelp: &dbghelp::Init,
121     addr: *mut c_void,
122     cb: &mut dyn FnMut(&super::Symbol),
123 ) {
124     do_resolve(
125         |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr as DWORD64, &mut 0, info),
126         |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr as DWORD64, &mut 0, line),
127         cb,
128     )
129 }
130 
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), )131 unsafe fn do_resolve(
132     sym_from_addr: impl FnOnce(*mut SYMBOL_INFOW) -> BOOL,
133     get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINEW64) -> BOOL,
134     cb: &mut dyn FnMut(&super::Symbol),
135 ) {
136     const SIZE: usize = 2 * MAX_SYM_NAME + mem::size_of::<SYMBOL_INFOW>();
137     let mut data = Aligned8([0u8; SIZE]);
138     let data = &mut data.0;
139     let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW);
140     info.MaxNameLen = MAX_SYM_NAME as ULONG;
141     // the struct size in C.  the value is different to
142     // `size_of::<SYMBOL_INFOW>() - MAX_SYM_NAME + 1` (== 81)
143     // due to struct alignment.
144     info.SizeOfStruct = 88;
145 
146     if sym_from_addr(info) != TRUE {
147         return;
148     }
149 
150     // If the symbol name is greater than MaxNameLen, SymFromAddrW will
151     // give a buffer of (MaxNameLen - 1) characters and set NameLen to
152     // the real value.
153     let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1);
154     let name_ptr = info.Name.as_ptr() as *const u16;
155     let name = slice::from_raw_parts(name_ptr, name_len);
156 
157     // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like
158     // all other platforms
159     let mut name_len = 0;
160     let mut name_buffer = [0; 256];
161     {
162         let mut remaining = &mut name_buffer[..];
163         for c in char::decode_utf16(name.iter().cloned()) {
164             let c = c.unwrap_or(char::REPLACEMENT_CHARACTER);
165             let len = c.len_utf8();
166             if len < remaining.len() {
167                 c.encode_utf8(remaining);
168                 let tmp = remaining;
169                 remaining = &mut tmp[len..];
170                 name_len += len;
171             } else {
172                 break;
173             }
174         }
175     }
176     let name = &name_buffer[..name_len] as *const [u8];
177 
178     let mut line = mem::zeroed::<IMAGEHLP_LINEW64>();
179     line.SizeOfStruct = mem::size_of::<IMAGEHLP_LINEW64>() as DWORD;
180 
181     let mut filename = None;
182     let mut lineno = None;
183     if get_line_from_addr(&mut line) == TRUE {
184         lineno = Some(line.LineNumber as u32);
185 
186         let base = line.FileName;
187         let mut len = 0;
188         while *base.offset(len) != 0 {
189             len += 1;
190         }
191 
192         let len = len as usize;
193 
194         filename = Some(slice::from_raw_parts(base, len) as *const [u16]);
195     }
196 
197     cb(&super::Symbol {
198         inner: Symbol {
199             name,
200             addr: info.Address as *mut _,
201             line: lineno,
202             filename,
203             _filename_cache: cache(filename),
204             _marker: marker::PhantomData,
205         },
206     })
207 }
208 
209 #[cfg(feature = "std")]
cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString>210 unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> {
211     use std::os::windows::ffi::OsStringExt;
212     filename.map(|f| ::std::ffi::OsString::from_wide(&*f))
213 }
214 
215 #[cfg(not(feature = "std"))]
cache(_filename: Option<*const [u16]>)216 unsafe fn cache(_filename: Option<*const [u16]>) {}
217 
clear_symbol_cache()218 pub unsafe fn clear_symbol_cache() {}
219