//! Backtrace support using libunwind/gcc_s/etc APIs. //! //! This module contains the ability to unwind the stack using libunwind-style //! APIs. Note that there's a whole bunch of implementations of the //! libunwind-like API, and this is just trying to be compatible with most of //! them all at once instead of being picky. //! //! The libunwind API is powered by `_Unwind_Backtrace` and is in practice very //! reliable at generating a backtrace. It's not entirely clear how it does it //! (frame pointers? eh_frame info? both?) but it seems to work! //! //! Most of the complexity of this module is handling the various platform //! differences across libunwind implementations. Otherwise this is a pretty //! straightforward Rust binding to the libunwind APIs. //! //! This is the default unwinding API for all non-Windows platforms currently. use super::super::Bomb; use core::ffi::c_void; pub enum Frame { Raw(*mut uw::_Unwind_Context), Cloned { ip: *mut c_void, sp: *mut c_void, symbol_address: *mut c_void, }, } // With a raw libunwind pointer it should only ever be access in a readonly // threadsafe fashion, so it's `Sync`. When sending to other threads via `Clone` // we always switch to a version which doesn't retain interior pointers, so we // should be `Send` as well. unsafe impl Send for Frame {} unsafe impl Sync for Frame {} impl Frame { pub fn ip(&self) -> *mut c_void { let ctx = match *self { Frame::Raw(ctx) => ctx, Frame::Cloned { ip, .. } => return ip, }; unsafe { uw::_Unwind_GetIP(ctx) as *mut c_void } } pub fn sp(&self) -> *mut c_void { match *self { Frame::Raw(ctx) => unsafe { uw::get_sp(ctx) as *mut c_void }, Frame::Cloned { sp, .. } => sp, } } pub fn symbol_address(&self) -> *mut c_void { if let Frame::Cloned { symbol_address, .. } = *self { return symbol_address; } // The macOS linker emits a "compact" unwind table that only includes an // entry for a function if that function either has an LSDA or its // encoding differs from that of the previous entry. Consequently, on // macOS, `_Unwind_FindEnclosingFunction` is unreliable (it can return a // pointer to some totally unrelated function). Instead, we just always // return the ip. // // https://github.com/rust-lang/rust/issues/74771#issuecomment-664056788 // // Note the `skip_inner_frames.rs` test is skipped on macOS due to this // clause, and if this is fixed that test in theory can be run on macOS! if cfg!(target_os = "macos") || cfg!(target_os = "ios") { self.ip() } else { unsafe { uw::_Unwind_FindEnclosingFunction(self.ip()) } } } pub fn module_base_address(&self) -> Option<*mut c_void> { None } } impl Clone for Frame { fn clone(&self) -> Frame { Frame::Cloned { ip: self.ip(), sp: self.sp(), symbol_address: self.symbol_address(), } } } #[inline(always)] pub unsafe fn trace(mut cb: &mut dyn FnMut(&super::Frame) -> bool) { uw::_Unwind_Backtrace(trace_fn, &mut cb as *mut _ as *mut _); extern "C" fn trace_fn( ctx: *mut uw::_Unwind_Context, arg: *mut c_void, ) -> uw::_Unwind_Reason_Code { let cb = unsafe { &mut *(arg as *mut &mut dyn FnMut(&super::Frame) -> bool) }; let cx = super::Frame { inner: Frame::Raw(ctx), }; let mut bomb = Bomb { enabled: true }; let keep_going = cb(&cx); bomb.enabled = false; if keep_going { uw::_URC_NO_REASON } else { uw::_URC_FAILURE } } } /// Unwind library interface used for backtraces /// /// Note that dead code is allowed as here are just bindings /// iOS doesn't use all of them it but adding more /// platform-specific configs pollutes the code too much #[allow(non_camel_case_types)] #[allow(non_snake_case)] #[allow(dead_code)] mod uw { pub use self::_Unwind_Reason_Code::*; use core::ffi::c_void; #[repr(C)] pub enum _Unwind_Reason_Code { _URC_NO_REASON = 0, _URC_FOREIGN_EXCEPTION_CAUGHT = 1, _URC_FATAL_PHASE2_ERROR = 2, _URC_FATAL_PHASE1_ERROR = 3, _URC_NORMAL_STOP = 4, _URC_END_OF_STACK = 5, _URC_HANDLER_FOUND = 6, _URC_INSTALL_CONTEXT = 7, _URC_CONTINUE_UNWIND = 8, _URC_FAILURE = 9, // used only by ARM EABI } pub enum _Unwind_Context {} pub type _Unwind_Trace_Fn = extern "C" fn(ctx: *mut _Unwind_Context, arg: *mut c_void) -> _Unwind_Reason_Code; extern "C" { pub fn _Unwind_Backtrace( trace: _Unwind_Trace_Fn, trace_argument: *mut c_void, ) -> _Unwind_Reason_Code; } cfg_if::cfg_if! { // available since GCC 4.2.0, should be fine for our purpose if #[cfg(all( not(all(target_os = "android", target_arch = "arm")), not(all(target_os = "freebsd", target_arch = "arm")), not(all(target_os = "linux", target_arch = "arm")), not(all(target_os = "horizon", target_arch = "arm")) ))] { extern "C" { pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t; pub fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void; #[cfg(not(all(target_os = "linux", target_arch = "s390x")))] // This function is a misnomer: rather than getting this frame's // Canonical Frame Address (aka the caller frame's SP) it // returns this frame's SP. // // https://github.com/libunwind/libunwind/blob/d32956507cf29d9b1a98a8bce53c78623908f4fe/src/unwind/GetCFA.c#L28-L35 #[link_name = "_Unwind_GetCFA"] pub fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t; } // s390x uses a biased CFA value, therefore we need to use // _Unwind_GetGR to get the stack pointer register (%r15) // instead of relying on _Unwind_GetCFA. #[cfg(all(target_os = "linux", target_arch = "s390x"))] pub unsafe fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t { extern "C" { pub fn _Unwind_GetGR(ctx: *mut _Unwind_Context, index: libc::c_int) -> libc::uintptr_t; } _Unwind_GetGR(ctx, 15) } } else { // On android and arm, the function `_Unwind_GetIP` and a bunch of // others are macros, so we define functions containing the // expansion of the macros. // // TODO: link to the header file that defines these macros, if you // can find it. (I, fitzgen, cannot find the header file that some // of these macro expansions were originally borrowed from.) #[repr(C)] enum _Unwind_VRS_Result { _UVRSR_OK = 0, _UVRSR_NOT_IMPLEMENTED = 1, _UVRSR_FAILED = 2, } #[repr(C)] enum _Unwind_VRS_RegClass { _UVRSC_CORE = 0, _UVRSC_VFP = 1, _UVRSC_FPA = 2, _UVRSC_WMMXD = 3, _UVRSC_WMMXC = 4, } #[repr(C)] enum _Unwind_VRS_DataRepresentation { _UVRSD_UINT32 = 0, _UVRSD_VFPX = 1, _UVRSD_FPAX = 2, _UVRSD_UINT64 = 3, _UVRSD_FLOAT = 4, _UVRSD_DOUBLE = 5, } type _Unwind_Word = libc::c_uint; extern "C" { fn _Unwind_VRS_Get( ctx: *mut _Unwind_Context, klass: _Unwind_VRS_RegClass, word: _Unwind_Word, repr: _Unwind_VRS_DataRepresentation, data: *mut c_void, ) -> _Unwind_VRS_Result; } pub unsafe fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t { let mut val: _Unwind_Word = 0; let ptr = &mut val as *mut _Unwind_Word; let _ = _Unwind_VRS_Get( ctx, _Unwind_VRS_RegClass::_UVRSC_CORE, 15, _Unwind_VRS_DataRepresentation::_UVRSD_UINT32, ptr as *mut c_void, ); (val & !1) as libc::uintptr_t } // R13 is the stack pointer on arm. const SP: _Unwind_Word = 13; pub unsafe fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t { let mut val: _Unwind_Word = 0; let ptr = &mut val as *mut _Unwind_Word; let _ = _Unwind_VRS_Get( ctx, _Unwind_VRS_RegClass::_UVRSC_CORE, SP, _Unwind_VRS_DataRepresentation::_UVRSD_UINT32, ptr as *mut c_void, ); val as libc::uintptr_t } // This function also doesn't exist on Android or ARM/Linux, so make it // a no-op. pub unsafe fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void { pc } } } }