From a53ae08a080c50e117c2ee9330e67fd8436e437b Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Sat, 9 Mar 2024 20:46:54 +0000 Subject: [PATCH 1/3] Revert dbghelp 32-bit to known good version --- src/backtrace/dbghelp32.rs | 257 +++++++++++++++++++++ src/backtrace/{dbghelp.rs => dbghelp64.rs} | 0 src/backtrace/mod.rs | 10 +- 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 src/backtrace/dbghelp32.rs rename src/backtrace/{dbghelp.rs => dbghelp64.rs} (100%) diff --git a/src/backtrace/dbghelp32.rs b/src/backtrace/dbghelp32.rs new file mode 100644 index 000000000..ba0f05f3b --- /dev/null +++ b/src/backtrace/dbghelp32.rs @@ -0,0 +1,257 @@ +//! 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) + } + + 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. + cfg_if::cfg_if! { + if #[cfg(target_pointer_width = "64")] { + use core::ptr; + + unsafe extern "system" fn function_table_access(_process: HANDLE, addr: DWORD64) -> PVOID { + let mut base = 0; + RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()).cast() + } + + unsafe extern "system" fn get_module_base(_process: HANDLE, addr: DWORD64) -> DWORD64 { + let mut base = 0; + RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()); + base + } + } else { + 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_64")] +fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { + frame.addr_pc_mut().Offset = ctx.Rip as u64; + frame.addr_pc_mut().Mode = AddrModeFlat; + frame.addr_stack_mut().Offset = ctx.Rsp as u64; + frame.addr_stack_mut().Mode = AddrModeFlat; + frame.addr_frame_mut().Offset = ctx.Rbp as u64; + frame.addr_frame_mut().Mode = AddrModeFlat; + + IMAGE_FILE_MACHINE_AMD64 +} + +#[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 = "aarch64")] +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.u.s().Fp as u64; + } + frame.addr_frame_mut().Mode = AddrModeFlat; + IMAGE_FILE_MACHINE_ARM64 +} + +#[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/dbghelp.rs b/src/backtrace/dbghelp64.rs similarity index 100% rename from src/backtrace/dbghelp.rs rename to src/backtrace/dbghelp64.rs diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index c872b1c37..df9f536a4 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 { From 1e38a0ca54bc14f158f67fa9b8dbf7a469023786 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Sat, 9 Mar 2024 20:47:28 +0000 Subject: [PATCH 2/3] Update dbghelp32 for new code --- src/backtrace/dbghelp32.rs | 54 +++++++------------------------------- 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/src/backtrace/dbghelp32.rs b/src/backtrace/dbghelp32.rs index ba0f05f3b..672bcba20 100644 --- a/src/backtrace/dbghelp32.rs +++ b/src/backtrace/dbghelp32.rs @@ -49,6 +49,14 @@ impl Frame { 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, @@ -111,25 +119,8 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { // // Note that `RtlLookupFunctionEntry` only works for in-process backtraces, // but that's all we support anyway, so it all lines up well. - cfg_if::cfg_if! { - if #[cfg(target_pointer_width = "64")] { - use core::ptr; - - unsafe extern "system" fn function_table_access(_process: HANDLE, addr: DWORD64) -> PVOID { - let mut base = 0; - RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()).cast() - } - - unsafe extern "system" fn get_module_base(_process: HANDLE, addr: DWORD64) -> DWORD64 { - let mut base = 0; - RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()); - base - } - } else { - let function_table_access = dbghelp.SymFunctionTableAccess64(); - let get_module_base = dbghelp.SymGetModuleBase64(); - } - } + let function_table_access = dbghelp.SymFunctionTableAccess64(); + let get_module_base = dbghelp.SymGetModuleBase64(); let process_handle = GetCurrentProcess(); @@ -206,18 +197,6 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { } } -#[cfg(target_arch = "x86_64")] -fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { - frame.addr_pc_mut().Offset = ctx.Rip as u64; - frame.addr_pc_mut().Mode = AddrModeFlat; - frame.addr_stack_mut().Offset = ctx.Rsp as u64; - frame.addr_stack_mut().Mode = AddrModeFlat; - frame.addr_frame_mut().Offset = ctx.Rbp as u64; - frame.addr_frame_mut().Mode = AddrModeFlat; - - IMAGE_FILE_MACHINE_AMD64 -} - #[cfg(target_arch = "x86")] fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { frame.addr_pc_mut().Offset = ctx.Eip as u64; @@ -230,19 +209,6 @@ fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { IMAGE_FILE_MACHINE_I386 } -#[cfg(target_arch = "aarch64")] -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.u.s().Fp as u64; - } - frame.addr_frame_mut().Mode = AddrModeFlat; - IMAGE_FILE_MACHINE_ARM64 -} - #[cfg(target_arch = "arm")] fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { frame.addr_pc_mut().Offset = ctx.Pc as u64; From f3a893342ed3df9eaa41b91489ad8d868470ff53 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Sat, 9 Mar 2024 20:48:40 +0000 Subject: [PATCH 3/3] Remove 32-bit from dbghelp64 --- src/backtrace/dbghelp64.rs | 146 ------------------------------------- 1 file changed, 146 deletions(-) diff --git a/src/backtrace/dbghelp64.rs b/src/backtrace/dbghelp64.rs index 71058a038..d3ae06dff 100644 --- a/src/backtrace/dbghelp64.rs +++ b/src/backtrace/dbghelp64.rs @@ -86,42 +86,6 @@ impl MyContext { } } -#[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", @@ -172,113 +136,3 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { ); } } - -#[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; - } - } - } - } -}