1 //! Backtrace strategy for MSVC platforms.
2 //!
3 //! This module contains the ability to generate a backtrace on MSVC using one
4 //! of two possible methods. The `StackWalkEx` function is primarily used if
5 //! possible, but not all systems have that. Failing that the `StackWalk64`
6 //! function is used instead. Note that `StackWalkEx` is favored because it
7 //! handles debuginfo internally and returns inline frame information.
8 //!
9 //! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs`
10 //! for more information about that.
11 
12 #![allow(bad_style)]
13 
14 use super::super::{dbghelp, windows::*};
15 use core::ffi::c_void;
16 use core::mem;
17 
18 #[derive(Clone, Copy)]
19 pub enum StackFrame {
20     New(STACKFRAME_EX),
21     Old(STACKFRAME64),
22 }
23 
24 #[derive(Clone, Copy)]
25 pub struct Frame {
26     pub(crate) stack_frame: StackFrame,
27     base_address: *mut c_void,
28 }
29 
30 // we're just sending around raw pointers and reading them, never interpreting
31 // them so this should be safe to both send and share across threads.
32 unsafe impl Send for Frame {}
33 unsafe impl Sync for Frame {}
34 
35 impl Frame {
ip(&self) -> *mut c_void36     pub fn ip(&self) -> *mut c_void {
37         self.addr_pc().Offset as *mut _
38     }
39 
sp(&self) -> *mut c_void40     pub fn sp(&self) -> *mut c_void {
41         self.addr_stack().Offset as *mut _
42     }
43 
symbol_address(&self) -> *mut c_void44     pub fn symbol_address(&self) -> *mut c_void {
45         self.ip()
46     }
47 
module_base_address(&self) -> Option<*mut c_void>48     pub fn module_base_address(&self) -> Option<*mut c_void> {
49         Some(self.base_address)
50     }
51 
addr_pc(&self) -> &ADDRESS6452     fn addr_pc(&self) -> &ADDRESS64 {
53         match self.stack_frame {
54             StackFrame::New(ref new) => &new.AddrPC,
55             StackFrame::Old(ref old) => &old.AddrPC,
56         }
57     }
58 
addr_pc_mut(&mut self) -> &mut ADDRESS6459     fn addr_pc_mut(&mut self) -> &mut ADDRESS64 {
60         match self.stack_frame {
61             StackFrame::New(ref mut new) => &mut new.AddrPC,
62             StackFrame::Old(ref mut old) => &mut old.AddrPC,
63         }
64     }
65 
addr_frame_mut(&mut self) -> &mut ADDRESS6466     fn addr_frame_mut(&mut self) -> &mut ADDRESS64 {
67         match self.stack_frame {
68             StackFrame::New(ref mut new) => &mut new.AddrFrame,
69             StackFrame::Old(ref mut old) => &mut old.AddrFrame,
70         }
71     }
72 
addr_stack(&self) -> &ADDRESS6473     fn addr_stack(&self) -> &ADDRESS64 {
74         match self.stack_frame {
75             StackFrame::New(ref new) => &new.AddrStack,
76             StackFrame::Old(ref old) => &old.AddrStack,
77         }
78     }
79 
addr_stack_mut(&mut self) -> &mut ADDRESS6480     fn addr_stack_mut(&mut self) -> &mut ADDRESS64 {
81         match self.stack_frame {
82             StackFrame::New(ref mut new) => &mut new.AddrStack,
83             StackFrame::Old(ref mut old) => &mut old.AddrStack,
84         }
85     }
86 }
87 
88 #[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now
89 struct MyContext(CONTEXT);
90 
91 #[inline(always)]
trace(cb: &mut dyn FnMut(&super::Frame) -> bool)92 pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
93     // Allocate necessary structures for doing the stack walk
94     let process = GetCurrentProcess();
95     let thread = GetCurrentThread();
96 
97     let mut context = mem::zeroed::<MyContext>();
98     RtlCaptureContext(&mut context.0);
99 
100     // Ensure this process's symbols are initialized
101     let dbghelp = match dbghelp::init() {
102         Ok(dbghelp) => dbghelp,
103         Err(()) => return, // oh well...
104     };
105 
106     // On x86_64 and ARM64 we opt to not use the default `Sym*` functions from
107     // dbghelp for getting the function table and module base. Instead we use
108     // the `RtlLookupFunctionEntry` function in kernel32 which will account for
109     // JIT compiler frames as well. These should be equivalent, but using
110     // `Rtl*` allows us to backtrace through JIT frames.
111     //
112     // Note that `RtlLookupFunctionEntry` only works for in-process backtraces,
113     // but that's all we support anyway, so it all lines up well.
114     cfg_if::cfg_if! {
115         if #[cfg(target_pointer_width = "64")] {
116             use core::ptr;
117 
118             unsafe extern "system" fn function_table_access(_process: HANDLE, addr: DWORD64) -> PVOID {
119                 let mut base = 0;
120                 RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()).cast()
121             }
122 
123             unsafe extern "system" fn get_module_base(_process: HANDLE, addr: DWORD64) -> DWORD64 {
124                 let mut base = 0;
125                 RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut());
126                 base
127             }
128         } else {
129             let function_table_access = dbghelp.SymFunctionTableAccess64();
130             let get_module_base = dbghelp.SymGetModuleBase64();
131         }
132     }
133 
134     let process_handle = GetCurrentProcess();
135 
136     // Attempt to use `StackWalkEx` if we can, but fall back to `StackWalk64`
137     // since it's in theory supported on more systems.
138     match (*dbghelp.dbghelp()).StackWalkEx() {
139         Some(StackWalkEx) => {
140             let mut frame = super::Frame {
141                 inner: Frame {
142                     stack_frame: StackFrame::New(mem::zeroed()),
143                     base_address: 0 as _,
144                 },
145             };
146             let image = init_frame(&mut frame.inner, &context.0);
147             let frame_ptr = match &mut frame.inner.stack_frame {
148                 StackFrame::New(ptr) => ptr as *mut STACKFRAME_EX,
149                 _ => unreachable!(),
150             };
151 
152             while StackWalkEx(
153                 image as DWORD,
154                 process,
155                 thread,
156                 frame_ptr,
157                 &mut context.0 as *mut CONTEXT as *mut _,
158                 None,
159                 Some(function_table_access),
160                 Some(get_module_base),
161                 None,
162                 0,
163             ) == TRUE
164             {
165                 frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _;
166 
167                 if !cb(&frame) {
168                     break;
169                 }
170             }
171         }
172         None => {
173             let mut frame = super::Frame {
174                 inner: Frame {
175                     stack_frame: StackFrame::Old(mem::zeroed()),
176                     base_address: 0 as _,
177                 },
178             };
179             let image = init_frame(&mut frame.inner, &context.0);
180             let frame_ptr = match &mut frame.inner.stack_frame {
181                 StackFrame::Old(ptr) => ptr as *mut STACKFRAME64,
182                 _ => unreachable!(),
183             };
184 
185             while dbghelp.StackWalk64()(
186                 image as DWORD,
187                 process,
188                 thread,
189                 frame_ptr,
190                 &mut context.0 as *mut CONTEXT as *mut _,
191                 None,
192                 Some(function_table_access),
193                 Some(get_module_base),
194                 None,
195             ) == TRUE
196             {
197                 frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _;
198 
199                 if !cb(&frame) {
200                     break;
201                 }
202             }
203         }
204     }
205 }
206 
207 #[cfg(target_arch = "x86_64")]
init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD208 fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD {
209     frame.addr_pc_mut().Offset = ctx.Rip as u64;
210     frame.addr_pc_mut().Mode = AddrModeFlat;
211     frame.addr_stack_mut().Offset = ctx.Rsp as u64;
212     frame.addr_stack_mut().Mode = AddrModeFlat;
213     frame.addr_frame_mut().Offset = ctx.Rbp as u64;
214     frame.addr_frame_mut().Mode = AddrModeFlat;
215 
216     IMAGE_FILE_MACHINE_AMD64
217 }
218 
219 #[cfg(target_arch = "x86")]
init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD220 fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD {
221     frame.addr_pc_mut().Offset = ctx.Eip as u64;
222     frame.addr_pc_mut().Mode = AddrModeFlat;
223     frame.addr_stack_mut().Offset = ctx.Esp as u64;
224     frame.addr_stack_mut().Mode = AddrModeFlat;
225     frame.addr_frame_mut().Offset = ctx.Ebp as u64;
226     frame.addr_frame_mut().Mode = AddrModeFlat;
227 
228     IMAGE_FILE_MACHINE_I386
229 }
230 
231 #[cfg(target_arch = "aarch64")]
init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD232 fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD {
233     frame.addr_pc_mut().Offset = ctx.Pc as u64;
234     frame.addr_pc_mut().Mode = AddrModeFlat;
235     frame.addr_stack_mut().Offset = ctx.Sp as u64;
236     frame.addr_stack_mut().Mode = AddrModeFlat;
237     unsafe {
238         frame.addr_frame_mut().Offset = ctx.u.s().Fp as u64;
239     }
240     frame.addr_frame_mut().Mode = AddrModeFlat;
241     IMAGE_FILE_MACHINE_ARM64
242 }
243 
244 #[cfg(target_arch = "arm")]
init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD245 fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD {
246     frame.addr_pc_mut().Offset = ctx.Pc as u64;
247     frame.addr_pc_mut().Mode = AddrModeFlat;
248     frame.addr_stack_mut().Offset = ctx.Sp as u64;
249     frame.addr_stack_mut().Mode = AddrModeFlat;
250     unsafe {
251         frame.addr_frame_mut().Offset = ctx.R11 as u64;
252     }
253     frame.addr_frame_mut().Mode = AddrModeFlat;
254     IMAGE_FILE_MACHINE_ARMNT
255 }
256