1 // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10 
11 //! Symbolication strategy using `dbghelp.dll` on Windows, only used for MSVC
12 //!
13 //! This symbolication strategy, like with backtraces, uses dynamically loaded
14 //! information from `dbghelp.dll`. (see `src/dbghelp.rs` for info about why
15 //! it's dynamically loaded).
16 //!
17 //! This API selects its resolution strategy based on the frame provided or the
18 //! information we have at hand. If a frame from `StackWalkEx` is given to us
19 //! then we use similar APIs to generate correct information about inlined
20 //! functions. Otherwise if all we have is an address or an older stack frame
21 //! from `StackWalk64` we use the older APIs for symbolication.
22 //!
23 //! There's a good deal of support in this module, but a good chunk of it is
24 //! converting back and forth between Windows types and Rust types. For example
25 //! symbols come to us as wide strings which we then convert to utf-8 strings if
26 //! we can.
27 
28 #![allow(bad_style)]
29 
30 use crate::backtrace::FrameImp as Frame;
31 use crate::dbghelp;
32 use crate::symbolize::ResolveWhat;
33 use crate::types::BytesOrWideString;
34 use crate::windows::*;
35 use crate::SymbolName;
36 use core::char;
37 use core::ffi::c_void;
38 use core::marker;
39 use core::mem;
40 use core::slice;
41 
42 // Store an OsString on std so we can provide the symbol name and filename.
43 pub struct Symbol<'a> {
44     name: *const [u8],
45     addr: *mut c_void,
46     line: Option<u32>,
47     filename: Option<*const [u16]>,
48     #[cfg(feature = "std")]
49     _filename_cache: Option<::std::ffi::OsString>,
50     #[cfg(not(feature = "std"))]
51     _filename_cache: (),
52     _marker: marker::PhantomData<&'a i32>,
53 }
54 
55 impl Symbol<'_> {
name(&self) -> Option<SymbolName<'_>>56     pub fn name(&self) -> Option<SymbolName<'_>> {
57         Some(SymbolName::new(unsafe { &*self.name }))
58     }
59 
addr(&self) -> Option<*mut c_void>60     pub fn addr(&self) -> Option<*mut c_void> {
61         Some(self.addr as *mut _)
62     }
63 
filename_raw(&self) -> Option<BytesOrWideString<'_>>64     pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
65         self.filename
66             .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) })
67     }
68 
lineno(&self) -> Option<u32>69     pub fn lineno(&self) -> Option<u32> {
70         self.line
71     }
72 
73     #[cfg(feature = "std")]
filename(&self) -> Option<&::std::path::Path>74     pub fn filename(&self) -> Option<&::std::path::Path> {
75         use std::path::Path;
76 
77         self._filename_cache.as_ref().map(Path::new)
78     }
79 }
80 
81 #[repr(C, align(8))]
82 struct Aligned8<T>(T);
83 
resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol))84 pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
85     // Ensure this process's symbols are initialized
86     let dbghelp = match dbghelp::init() {
87         Ok(dbghelp) => dbghelp,
88         Err(()) => return, // oh well...
89     };
90 
91     match what {
92         ResolveWhat::Address(_) => resolve_without_inline(&dbghelp, what.address_or_ip(), cb),
93         ResolveWhat::Frame(frame) => match &frame.inner {
94             Frame::New(frame) => resolve_with_inline(&dbghelp, frame, cb),
95             Frame::Old(_) => resolve_without_inline(&dbghelp, frame.ip(), cb),
96         },
97     }
98 }
99 
resolve_with_inline( dbghelp: &dbghelp::Init, frame: &STACKFRAME_EX, cb: &mut dyn FnMut(&super::Symbol), )100 unsafe fn resolve_with_inline(
101     dbghelp: &dbghelp::Init,
102     frame: &STACKFRAME_EX,
103     cb: &mut dyn FnMut(&super::Symbol),
104 ) {
105     do_resolve(
106         |info| {
107             dbghelp.SymFromInlineContextW()(
108                 GetCurrentProcess(),
109                 super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64,
110                 frame.InlineFrameContext,
111                 &mut 0,
112                 info,
113             )
114         },
115         |line| {
116             dbghelp.SymGetLineFromInlineContextW()(
117                 GetCurrentProcess(),
118                 super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64,
119                 frame.InlineFrameContext,
120                 0,
121                 &mut 0,
122                 line,
123             )
124         },
125         cb,
126     )
127 }
128 
resolve_without_inline( dbghelp: &dbghelp::Init, addr: *mut c_void, cb: &mut dyn FnMut(&super::Symbol), )129 unsafe fn resolve_without_inline(
130     dbghelp: &dbghelp::Init,
131     addr: *mut c_void,
132     cb: &mut dyn FnMut(&super::Symbol),
133 ) {
134     do_resolve(
135         |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr as DWORD64, &mut 0, info),
136         |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr as DWORD64, &mut 0, line),
137         cb,
138     )
139 }
140 
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), )141 unsafe fn do_resolve(
142     sym_from_addr: impl FnOnce(*mut SYMBOL_INFOW) -> BOOL,
143     get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINEW64) -> BOOL,
144     cb: &mut dyn FnMut(&super::Symbol),
145 ) {
146     const SIZE: usize = 2 * MAX_SYM_NAME + mem::size_of::<SYMBOL_INFOW>();
147     let mut data = Aligned8([0u8; SIZE]);
148     let data = &mut data.0;
149     let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW);
150     info.MaxNameLen = MAX_SYM_NAME as ULONG;
151     // the struct size in C.  the value is different to
152     // `size_of::<SYMBOL_INFOW>() - MAX_SYM_NAME + 1` (== 81)
153     // due to struct alignment.
154     info.SizeOfStruct = 88;
155 
156     if sym_from_addr(info) != TRUE {
157         return;
158     }
159 
160     // If the symbol name is greater than MaxNameLen, SymFromAddrW will
161     // give a buffer of (MaxNameLen - 1) characters and set NameLen to
162     // the real value.
163     let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1);
164     let name_ptr = info.Name.as_ptr() as *const u16;
165     let name = slice::from_raw_parts(name_ptr, name_len);
166 
167     // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like
168     // all other platforms
169     let mut name_len = 0;
170     let mut name_buffer = [0; 256];
171     {
172         let mut remaining = &mut name_buffer[..];
173         for c in char::decode_utf16(name.iter().cloned()) {
174             let c = c.unwrap_or(char::REPLACEMENT_CHARACTER);
175             let len = c.len_utf8();
176             if len < remaining.len() {
177                 c.encode_utf8(remaining);
178                 let tmp = remaining;
179                 remaining = &mut tmp[len..];
180                 name_len += len;
181             } else {
182                 break;
183             }
184         }
185     }
186     let name = &name_buffer[..name_len] as *const [u8];
187 
188     let mut line = mem::zeroed::<IMAGEHLP_LINEW64>();
189     line.SizeOfStruct = mem::size_of::<IMAGEHLP_LINEW64>() as DWORD;
190 
191     let mut filename = None;
192     let mut lineno = None;
193     if get_line_from_addr(&mut line) == TRUE {
194         lineno = Some(line.LineNumber as u32);
195 
196         let base = line.FileName;
197         let mut len = 0;
198         while *base.offset(len) != 0 {
199             len += 1;
200         }
201 
202         let len = len as usize;
203 
204         filename = Some(slice::from_raw_parts(base, len) as *const [u16]);
205     }
206 
207     cb(&super::Symbol {
208         inner: Symbol {
209             name,
210             addr: info.Address as *mut _,
211             line: lineno,
212             filename,
213             _filename_cache: cache(filename),
214             _marker: marker::PhantomData,
215         },
216     })
217 }
218 
219 #[cfg(feature = "std")]
cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString>220 unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> {
221     use std::os::windows::ffi::OsStringExt;
222     filename.map(|f| ::std::ffi::OsString::from_wide(&*f))
223 }
224 
225 #[cfg(not(feature = "std"))]
cache(_filename: Option<*const [u16]>)226 unsafe fn cache(_filename: Option<*const [u16]>) {}
227