diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs deleted file mode 100644 index 71058a03..00000000 --- a/src/backtrace/dbghelp.rs +++ /dev/null @@ -1,284 +0,0 @@ -//! Backtrace strategy for MSVC platforms. -//! -//! This module contains the ability to capture a backtrace on MSVC using one -//! of three possible methods. For `x86_64` and `aarch64`, we use `RtlVirtualUnwind` -//! to walk the stack one frame at a time. This function is much faster than using -//! `dbghelp!StackWalk*` because it does not load debug info to report inlined frames. -//! We still report inlined frames during symbolization by consulting the appropriate -//! `dbghelp` functions. -//! -//! For all other platforms, primarily `i686`, the `StackWalkEx` function is used if -//! possible, but not all systems have that. Failing that the `StackWalk64` function -//! is used instead. Note that `StackWalkEx` is favored because it handles debuginfo -//! internally and returns inline frame information. -//! -//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs` -//! for more information about that. - -#![allow(bad_style)] - -use super::super::windows::*; -use core::ffi::c_void; - -#[derive(Clone, Copy)] -pub struct Frame { - base_address: *mut c_void, - ip: *mut c_void, - sp: *mut c_void, - #[cfg(not(target_env = "gnu"))] - inline_context: Option, -} - -// we're just sending around raw pointers and reading them, never interpreting -// them so this should be safe to both send and share across threads. -unsafe impl Send for Frame {} -unsafe impl Sync for Frame {} - -impl Frame { - pub fn ip(&self) -> *mut c_void { - self.ip - } - - pub fn sp(&self) -> *mut c_void { - self.sp - } - - pub fn symbol_address(&self) -> *mut c_void { - self.ip - } - - pub fn module_base_address(&self) -> Option<*mut c_void> { - Some(self.base_address) - } - - #[cfg(not(target_env = "gnu"))] - pub fn inline_context(&self) -> Option { - self.inline_context - } -} - -#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now -struct MyContext(CONTEXT); - -#[cfg(any(target_arch = "x86_64", target_arch = "arm64ec"))] -impl MyContext { - #[inline(always)] - fn ip(&self) -> DWORD64 { - self.0.Rip - } - - #[inline(always)] - fn sp(&self) -> DWORD64 { - self.0.Rsp - } -} - -#[cfg(target_arch = "aarch64")] -impl MyContext { - #[inline(always)] - fn ip(&self) -> DWORD64 { - self.0.Pc - } - - #[inline(always)] - fn sp(&self) -> DWORD64 { - self.0.Sp - } -} - -#[cfg(target_arch = "x86")] -impl MyContext { - #[inline(always)] - fn ip(&self) -> DWORD { - self.0.Eip - } - - #[inline(always)] - fn sp(&self) -> DWORD { - self.0.Esp - } - - #[inline(always)] - fn fp(&self) -> DWORD { - self.0.Ebp - } -} - -#[cfg(target_arch = "arm")] -impl MyContext { - #[inline(always)] - fn ip(&self) -> DWORD { - self.0.Pc - } - - #[inline(always)] - fn sp(&self) -> DWORD { - self.0.Sp - } - - #[inline(always)] - fn fp(&self) -> DWORD { - self.0.R11 - } -} - -#[cfg(any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "arm64ec" -))] -#[inline(always)] -pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { - use core::ptr; - - let mut context = core::mem::zeroed::(); - RtlCaptureContext(&mut context.0); - - // Call `RtlVirtualUnwind` to find the previous stack frame, walking until we hit ip = 0. - while context.ip() != 0 { - let mut base = 0; - - let fn_entry = RtlLookupFunctionEntry(context.ip(), &mut base, ptr::null_mut()); - if fn_entry.is_null() { - break; - } - - let frame = super::Frame { - inner: Frame { - base_address: fn_entry.cast::(), - ip: context.ip() as *mut c_void, - sp: context.sp() as *mut c_void, - #[cfg(not(target_env = "gnu"))] - inline_context: None, - }, - }; - - if !cb(&frame) { - break; - } - - let mut handler_data = 0usize; - let mut establisher_frame = 0; - - RtlVirtualUnwind( - 0, - base, - context.ip(), - fn_entry, - &mut context.0, - ptr::addr_of_mut!(handler_data).cast::(), - &mut establisher_frame, - ptr::null_mut(), - ); - } -} - -#[cfg(any(target_arch = "x86", target_arch = "arm"))] -#[inline(always)] -pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { - use core::{mem, ptr}; - - // Allocate necessary structures for doing the stack walk - let process = GetCurrentProcess(); - let thread = GetCurrentThread(); - - let mut context = mem::zeroed::(); - RtlCaptureContext(&mut context.0); - - // Ensure this process's symbols are initialized - let dbghelp = match super::super::dbghelp::init() { - Ok(dbghelp) => dbghelp, - Err(()) => return, // oh well... - }; - - let function_table_access = dbghelp.SymFunctionTableAccess64(); - let get_module_base = dbghelp.SymGetModuleBase64(); - - let process_handle = GetCurrentProcess(); - - #[cfg(target_arch = "x86")] - let image = IMAGE_FILE_MACHINE_I386; - #[cfg(target_arch = "arm")] - let image = IMAGE_FILE_MACHINE_ARMNT; - - // Attempt to use `StackWalkEx` if we can, but fall back to `StackWalk64` - // since it's in theory supported on more systems. - match (*dbghelp.dbghelp()).StackWalkEx() { - Some(StackWalkEx) => { - let mut stack_frame_ex: STACKFRAME_EX = mem::zeroed(); - stack_frame_ex.StackFrameSize = mem::size_of::() as DWORD; - stack_frame_ex.AddrPC.Offset = context.ip() as u64; - stack_frame_ex.AddrPC.Mode = AddrModeFlat; - stack_frame_ex.AddrStack.Offset = context.sp() as u64; - stack_frame_ex.AddrStack.Mode = AddrModeFlat; - stack_frame_ex.AddrFrame.Offset = context.fp() as u64; - stack_frame_ex.AddrFrame.Mode = AddrModeFlat; - - while StackWalkEx( - image as DWORD, - process, - thread, - &mut stack_frame_ex, - ptr::addr_of_mut!(context.0) as PVOID, - None, - Some(function_table_access), - Some(get_module_base), - None, - 0, - ) == TRUE - { - let frame = super::Frame { - inner: Frame { - base_address: get_module_base(process_handle, stack_frame_ex.AddrPC.Offset) - as *mut c_void, - ip: stack_frame_ex.AddrPC.Offset as *mut c_void, - sp: stack_frame_ex.AddrStack.Offset as *mut c_void, - #[cfg(not(target_env = "gnu"))] - inline_context: Some(stack_frame_ex.InlineFrameContext), - }, - }; - - if !cb(&frame) { - break; - } - } - } - None => { - let mut stack_frame64: STACKFRAME64 = mem::zeroed(); - stack_frame64.AddrPC.Offset = context.ip() as u64; - stack_frame64.AddrPC.Mode = AddrModeFlat; - stack_frame64.AddrStack.Offset = context.sp() as u64; - stack_frame64.AddrStack.Mode = AddrModeFlat; - stack_frame64.AddrFrame.Offset = context.fp() as u64; - stack_frame64.AddrFrame.Mode = AddrModeFlat; - - while dbghelp.StackWalk64()( - image as DWORD, - process, - thread, - &mut stack_frame64, - ptr::addr_of_mut!(context.0) as PVOID, - None, - Some(function_table_access), - Some(get_module_base), - None, - ) == TRUE - { - let frame = super::Frame { - inner: Frame { - base_address: get_module_base(process_handle, stack_frame64.AddrPC.Offset) - as *mut c_void, - ip: stack_frame64.AddrPC.Offset as *mut c_void, - sp: stack_frame64.AddrStack.Offset as *mut c_void, - #[cfg(not(target_env = "gnu"))] - inline_context: None, - }, - }; - - if !cb(&frame) { - break; - } - } - } - } -} diff --git a/src/backtrace/dbghelp32.rs b/src/backtrace/dbghelp32.rs new file mode 100644 index 00000000..672bcba2 --- /dev/null +++ b/src/backtrace/dbghelp32.rs @@ -0,0 +1,223 @@ +//! Backtrace strategy for MSVC platforms. +//! +//! This module contains the ability to generate a backtrace on MSVC using one +//! of two possible methods. The `StackWalkEx` function is primarily used if +//! possible, but not all systems have that. Failing that the `StackWalk64` +//! function is used instead. Note that `StackWalkEx` is favored because it +//! handles debuginfo internally and returns inline frame information. +//! +//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs` +//! for more information about that. + +#![allow(bad_style)] + +use super::super::{dbghelp, windows::*}; +use core::ffi::c_void; +use core::mem; + +#[derive(Clone, Copy)] +pub enum StackFrame { + New(STACKFRAME_EX), + Old(STACKFRAME64), +} + +#[derive(Clone, Copy)] +pub struct Frame { + pub(crate) stack_frame: StackFrame, + base_address: *mut c_void, +} + +// we're just sending around raw pointers and reading them, never interpreting +// them so this should be safe to both send and share across threads. +unsafe impl Send for Frame {} +unsafe impl Sync for Frame {} + +impl Frame { + pub fn ip(&self) -> *mut c_void { + self.addr_pc().Offset as *mut _ + } + + pub fn sp(&self) -> *mut c_void { + self.addr_stack().Offset as *mut _ + } + + pub fn symbol_address(&self) -> *mut c_void { + self.ip() + } + + pub fn module_base_address(&self) -> Option<*mut c_void> { + Some(self.base_address) + } + + #[cfg(not(target_env = "gnu"))] + pub fn inline_context(&self) -> Option { + match self.stack_frame { + StackFrame::New(ref new) => Some(new.InlineFrameContext), + StackFrame::Old(_) => None, + } + } + + fn addr_pc(&self) -> &ADDRESS64 { + match self.stack_frame { + StackFrame::New(ref new) => &new.AddrPC, + StackFrame::Old(ref old) => &old.AddrPC, + } + } + + fn addr_pc_mut(&mut self) -> &mut ADDRESS64 { + match self.stack_frame { + StackFrame::New(ref mut new) => &mut new.AddrPC, + StackFrame::Old(ref mut old) => &mut old.AddrPC, + } + } + + fn addr_frame_mut(&mut self) -> &mut ADDRESS64 { + match self.stack_frame { + StackFrame::New(ref mut new) => &mut new.AddrFrame, + StackFrame::Old(ref mut old) => &mut old.AddrFrame, + } + } + + fn addr_stack(&self) -> &ADDRESS64 { + match self.stack_frame { + StackFrame::New(ref new) => &new.AddrStack, + StackFrame::Old(ref old) => &old.AddrStack, + } + } + + fn addr_stack_mut(&mut self) -> &mut ADDRESS64 { + match self.stack_frame { + StackFrame::New(ref mut new) => &mut new.AddrStack, + StackFrame::Old(ref mut old) => &mut old.AddrStack, + } + } +} + +#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now +struct MyContext(CONTEXT); + +#[inline(always)] +pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { + // Allocate necessary structures for doing the stack walk + let process = GetCurrentProcess(); + let thread = GetCurrentThread(); + + let mut context = mem::zeroed::(); + RtlCaptureContext(&mut context.0); + + // Ensure this process's symbols are initialized + let dbghelp = match dbghelp::init() { + Ok(dbghelp) => dbghelp, + Err(()) => return, // oh well... + }; + + // On x86_64 and ARM64 we opt to not use the default `Sym*` functions from + // dbghelp for getting the function table and module base. Instead we use + // the `RtlLookupFunctionEntry` function in kernel32 which will account for + // JIT compiler frames as well. These should be equivalent, but using + // `Rtl*` allows us to backtrace through JIT frames. + // + // Note that `RtlLookupFunctionEntry` only works for in-process backtraces, + // but that's all we support anyway, so it all lines up well. + let function_table_access = dbghelp.SymFunctionTableAccess64(); + let get_module_base = dbghelp.SymGetModuleBase64(); + + let process_handle = GetCurrentProcess(); + + // Attempt to use `StackWalkEx` if we can, but fall back to `StackWalk64` + // since it's in theory supported on more systems. + match (*dbghelp.dbghelp()).StackWalkEx() { + Some(StackWalkEx) => { + let mut inner: STACKFRAME_EX = mem::zeroed(); + inner.StackFrameSize = mem::size_of::() as DWORD; + let mut frame = super::Frame { + inner: Frame { + stack_frame: StackFrame::New(inner), + base_address: 0 as _, + }, + }; + let image = init_frame(&mut frame.inner, &context.0); + let frame_ptr = match &mut frame.inner.stack_frame { + StackFrame::New(ptr) => ptr as *mut STACKFRAME_EX, + _ => unreachable!(), + }; + + while StackWalkEx( + image as DWORD, + process, + thread, + frame_ptr, + &mut context.0 as *mut CONTEXT as *mut _, + None, + Some(function_table_access), + Some(get_module_base), + None, + 0, + ) == TRUE + { + frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _; + + if !cb(&frame) { + break; + } + } + } + None => { + let mut frame = super::Frame { + inner: Frame { + stack_frame: StackFrame::Old(mem::zeroed()), + base_address: 0 as _, + }, + }; + let image = init_frame(&mut frame.inner, &context.0); + let frame_ptr = match &mut frame.inner.stack_frame { + StackFrame::Old(ptr) => ptr as *mut STACKFRAME64, + _ => unreachable!(), + }; + + while dbghelp.StackWalk64()( + image as DWORD, + process, + thread, + frame_ptr, + &mut context.0 as *mut CONTEXT as *mut _, + None, + Some(function_table_access), + Some(get_module_base), + None, + ) == TRUE + { + frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _; + + if !cb(&frame) { + break; + } + } + } + } +} + +#[cfg(target_arch = "x86")] +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { + frame.addr_pc_mut().Offset = ctx.Eip as u64; + frame.addr_pc_mut().Mode = AddrModeFlat; + frame.addr_stack_mut().Offset = ctx.Esp as u64; + frame.addr_stack_mut().Mode = AddrModeFlat; + frame.addr_frame_mut().Offset = ctx.Ebp as u64; + frame.addr_frame_mut().Mode = AddrModeFlat; + + IMAGE_FILE_MACHINE_I386 +} + +#[cfg(target_arch = "arm")] +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { + frame.addr_pc_mut().Offset = ctx.Pc as u64; + frame.addr_pc_mut().Mode = AddrModeFlat; + frame.addr_stack_mut().Offset = ctx.Sp as u64; + frame.addr_stack_mut().Mode = AddrModeFlat; + unsafe { + frame.addr_frame_mut().Offset = ctx.R11 as u64; + } + frame.addr_frame_mut().Mode = AddrModeFlat; + IMAGE_FILE_MACHINE_ARMNT +} diff --git a/src/backtrace/dbghelp64.rs b/src/backtrace/dbghelp64.rs new file mode 100644 index 00000000..d3ae06df --- /dev/null +++ b/src/backtrace/dbghelp64.rs @@ -0,0 +1,138 @@ +//! Backtrace strategy for MSVC platforms. +//! +//! This module contains the ability to capture a backtrace on MSVC using one +//! of three possible methods. For `x86_64` and `aarch64`, we use `RtlVirtualUnwind` +//! to walk the stack one frame at a time. This function is much faster than using +//! `dbghelp!StackWalk*` because it does not load debug info to report inlined frames. +//! We still report inlined frames during symbolization by consulting the appropriate +//! `dbghelp` functions. +//! +//! For all other platforms, primarily `i686`, the `StackWalkEx` function is used if +//! possible, but not all systems have that. Failing that the `StackWalk64` function +//! is used instead. Note that `StackWalkEx` is favored because it handles debuginfo +//! internally and returns inline frame information. +//! +//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs` +//! for more information about that. + +#![allow(bad_style)] + +use super::super::windows::*; +use core::ffi::c_void; + +#[derive(Clone, Copy)] +pub struct Frame { + base_address: *mut c_void, + ip: *mut c_void, + sp: *mut c_void, + #[cfg(not(target_env = "gnu"))] + inline_context: Option, +} + +// we're just sending around raw pointers and reading them, never interpreting +// them so this should be safe to both send and share across threads. +unsafe impl Send for Frame {} +unsafe impl Sync for Frame {} + +impl Frame { + pub fn ip(&self) -> *mut c_void { + self.ip + } + + pub fn sp(&self) -> *mut c_void { + self.sp + } + + pub fn symbol_address(&self) -> *mut c_void { + self.ip + } + + pub fn module_base_address(&self) -> Option<*mut c_void> { + Some(self.base_address) + } + + #[cfg(not(target_env = "gnu"))] + pub fn inline_context(&self) -> Option { + self.inline_context + } +} + +#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now +struct MyContext(CONTEXT); + +#[cfg(any(target_arch = "x86_64", target_arch = "arm64ec"))] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD64 { + self.0.Rip + } + + #[inline(always)] + fn sp(&self) -> DWORD64 { + self.0.Rsp + } +} + +#[cfg(target_arch = "aarch64")] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD64 { + self.0.Pc + } + + #[inline(always)] + fn sp(&self) -> DWORD64 { + self.0.Sp + } +} + +#[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "arm64ec" +))] +#[inline(always)] +pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { + use core::ptr; + + let mut context = core::mem::zeroed::(); + RtlCaptureContext(&mut context.0); + + // Call `RtlVirtualUnwind` to find the previous stack frame, walking until we hit ip = 0. + while context.ip() != 0 { + let mut base = 0; + + let fn_entry = RtlLookupFunctionEntry(context.ip(), &mut base, ptr::null_mut()); + if fn_entry.is_null() { + break; + } + + let frame = super::Frame { + inner: Frame { + base_address: fn_entry.cast::(), + ip: context.ip() as *mut c_void, + sp: context.sp() as *mut c_void, + #[cfg(not(target_env = "gnu"))] + inline_context: None, + }, + }; + + if !cb(&frame) { + break; + } + + let mut handler_data = 0usize; + let mut establisher_frame = 0; + + RtlVirtualUnwind( + 0, + base, + context.ip(), + fn_entry, + &mut context.0, + ptr::addr_of_mut!(handler_data).cast::(), + &mut establisher_frame, + ptr::null_mut(), + ); + } +} diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index c872b1c3..df9f536a 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -193,7 +193,15 @@ cfg_if::cfg_if! { use self::libunwind::trace as trace_imp; pub(crate) use self::libunwind::Frame as FrameImp; } else if #[cfg(all(windows, not(target_vendor = "uwp")))] { - mod dbghelp; + cfg_if::cfg_if! { + if #[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm64ec"))] { + mod dbghelp64; + use dbghelp64 as dbghelp; + } else if #[cfg(any(target_arch = "x86", target_arch = "arm"))] { + mod dbghelp32; + use dbghelp32 as dbghelp; + } + } use self::dbghelp::trace as trace_imp; pub(crate) use self::dbghelp::Frame as FrameImp; } else {