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