|
| 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 { |
| 36 | + pub fn ip(&self) -> *mut c_void { |
| 37 | + self.addr_pc().Offset as *mut _ |
| 38 | + } |
| 39 | + |
| 40 | + pub fn sp(&self) -> *mut c_void { |
| 41 | + self.addr_stack().Offset as *mut _ |
| 42 | + } |
| 43 | + |
| 44 | + pub fn symbol_address(&self) -> *mut c_void { |
| 45 | + self.ip() |
| 46 | + } |
| 47 | + |
| 48 | + pub fn module_base_address(&self) -> Option<*mut c_void> { |
| 49 | + Some(self.base_address) |
| 50 | + } |
| 51 | + |
| 52 | + 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 | + |
| 59 | + 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 | + |
| 66 | + 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 | + |
| 73 | + 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 | + |
| 80 | + 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)] |
| 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 inner: STACKFRAME_EX = mem::zeroed(); |
| 141 | + inner.StackFrameSize = mem::size_of::<STACKFRAME_EX>() as DWORD; |
| 142 | + let mut frame = super::Frame { |
| 143 | + inner: Frame { |
| 144 | + stack_frame: StackFrame::New(inner), |
| 145 | + base_address: 0 as _, |
| 146 | + }, |
| 147 | + }; |
| 148 | + let image = init_frame(&mut frame.inner, &context.0); |
| 149 | + let frame_ptr = match &mut frame.inner.stack_frame { |
| 150 | + StackFrame::New(ptr) => ptr as *mut STACKFRAME_EX, |
| 151 | + _ => unreachable!(), |
| 152 | + }; |
| 153 | + |
| 154 | + while StackWalkEx( |
| 155 | + image as DWORD, |
| 156 | + process, |
| 157 | + thread, |
| 158 | + frame_ptr, |
| 159 | + &mut context.0 as *mut CONTEXT as *mut _, |
| 160 | + None, |
| 161 | + Some(function_table_access), |
| 162 | + Some(get_module_base), |
| 163 | + None, |
| 164 | + 0, |
| 165 | + ) == TRUE |
| 166 | + { |
| 167 | + frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _; |
| 168 | + |
| 169 | + if !cb(&frame) { |
| 170 | + break; |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + None => { |
| 175 | + let mut frame = super::Frame { |
| 176 | + inner: Frame { |
| 177 | + stack_frame: StackFrame::Old(mem::zeroed()), |
| 178 | + base_address: 0 as _, |
| 179 | + }, |
| 180 | + }; |
| 181 | + let image = init_frame(&mut frame.inner, &context.0); |
| 182 | + let frame_ptr = match &mut frame.inner.stack_frame { |
| 183 | + StackFrame::Old(ptr) => ptr as *mut STACKFRAME64, |
| 184 | + _ => unreachable!(), |
| 185 | + }; |
| 186 | + |
| 187 | + while dbghelp.StackWalk64()( |
| 188 | + image as DWORD, |
| 189 | + process, |
| 190 | + thread, |
| 191 | + frame_ptr, |
| 192 | + &mut context.0 as *mut CONTEXT as *mut _, |
| 193 | + None, |
| 194 | + Some(function_table_access), |
| 195 | + Some(get_module_base), |
| 196 | + None, |
| 197 | + ) == TRUE |
| 198 | + { |
| 199 | + frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _; |
| 200 | + |
| 201 | + if !cb(&frame) { |
| 202 | + break; |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +#[cfg(target_arch = "x86_64")] |
| 210 | +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { |
| 211 | + frame.addr_pc_mut().Offset = ctx.Rip as u64; |
| 212 | + frame.addr_pc_mut().Mode = AddrModeFlat; |
| 213 | + frame.addr_stack_mut().Offset = ctx.Rsp as u64; |
| 214 | + frame.addr_stack_mut().Mode = AddrModeFlat; |
| 215 | + frame.addr_frame_mut().Offset = ctx.Rbp as u64; |
| 216 | + frame.addr_frame_mut().Mode = AddrModeFlat; |
| 217 | + |
| 218 | + IMAGE_FILE_MACHINE_AMD64 |
| 219 | +} |
| 220 | + |
| 221 | +#[cfg(target_arch = "x86")] |
| 222 | +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { |
| 223 | + frame.addr_pc_mut().Offset = ctx.Eip as u64; |
| 224 | + frame.addr_pc_mut().Mode = AddrModeFlat; |
| 225 | + frame.addr_stack_mut().Offset = ctx.Esp as u64; |
| 226 | + frame.addr_stack_mut().Mode = AddrModeFlat; |
| 227 | + frame.addr_frame_mut().Offset = ctx.Ebp as u64; |
| 228 | + frame.addr_frame_mut().Mode = AddrModeFlat; |
| 229 | + |
| 230 | + IMAGE_FILE_MACHINE_I386 |
| 231 | +} |
| 232 | + |
| 233 | +#[cfg(target_arch = "aarch64")] |
| 234 | +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { |
| 235 | + frame.addr_pc_mut().Offset = ctx.Pc as u64; |
| 236 | + frame.addr_pc_mut().Mode = AddrModeFlat; |
| 237 | + frame.addr_stack_mut().Offset = ctx.Sp as u64; |
| 238 | + frame.addr_stack_mut().Mode = AddrModeFlat; |
| 239 | + unsafe { |
| 240 | + frame.addr_frame_mut().Offset = ctx.u.s().Fp as u64; |
| 241 | + } |
| 242 | + frame.addr_frame_mut().Mode = AddrModeFlat; |
| 243 | + IMAGE_FILE_MACHINE_ARM64 |
| 244 | +} |
| 245 | + |
| 246 | +#[cfg(target_arch = "arm")] |
| 247 | +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { |
| 248 | + frame.addr_pc_mut().Offset = ctx.Pc as u64; |
| 249 | + frame.addr_pc_mut().Mode = AddrModeFlat; |
| 250 | + frame.addr_stack_mut().Offset = ctx.Sp as u64; |
| 251 | + frame.addr_stack_mut().Mode = AddrModeFlat; |
| 252 | + unsafe { |
| 253 | + frame.addr_frame_mut().Offset = ctx.R11 as u64; |
| 254 | + } |
| 255 | + frame.addr_frame_mut().Mode = AddrModeFlat; |
| 256 | + IMAGE_FILE_MACHINE_ARMNT |
| 257 | +} |
0 commit comments