diff --git a/Cargo.toml b/Cargo.toml index 973fa00..96288e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ fde-phdr-aux = ["fde-phdr"] fde-registry = ["alloc"] fde-static = [] fde-gnu-eh-frame-hdr = [] +fde-custom = [] dwarf-expr = [] hide-trace = [] personality = [] diff --git a/README.md b/README.md index 21f5509..abe55cd 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ The unwinder can be enabled with `unwinder` feature. Here are the feature gates | fde-registry | Yes | Provide `__register__frame` and others for dynamic registration. Requires either `libc` or `spin` for a mutex implementation. | | fde-gnu-eh-frame-hdr | No | Use `__executable_start`, `__etext` and `__GNU_EH_FRAME_HDR` to retrieve frame unwind table. The former two symbols are usually provided by the linker, while the last one is provided if GNU LD is used and --eh-frame-hdr option is enabled. | | fde-static | No | Use `__executable_start`, `__etext` and `__eh_frame` to retrieve frame unwind table. The former two symbols are usually provided by the linker, while the last one would need to be provided by the user via linker script. | +| fde-custom | No | Allow the program to provide a custom means of retrieving frame unwind table at runtime via the `set_custom_eh_frame_finder` function. | | dwarf-expr | Yes | Enable the dwarf expression evaluator. Usually not necessary for Rust | | hide-trace | Yes | Hide unwinder frames in back trace | diff --git a/src/lib.rs b/src/lib.rs index bdeb56d..6152309 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,9 @@ extern crate alloc; #[cfg(feature = "unwinder")] mod unwinder; +#[cfg(all(feature = "unwinder", feature = "fde-custom"))] +pub use unwinder::custom_eh_frame_finder; + pub mod abi; mod arch; diff --git a/src/unwinder/find_fde/custom.rs b/src/unwinder/find_fde/custom.rs new file mode 100644 index 0000000..cf9883d --- /dev/null +++ b/src/unwinder/find_fde/custom.rs @@ -0,0 +1,165 @@ +use super::{FDEFinder, FDESearchResult}; +use crate::util::{deref_pointer, get_unlimited_slice}; + +use core::sync::atomic::{AtomicU32, Ordering}; +use gimli::{BaseAddresses, EhFrame, EhFrameHdr, NativeEndian, UnwindSection}; + +pub(crate) struct CustomFinder(()); + +pub(crate) fn get_finder() -> &'static CustomFinder { + &CustomFinder(()) +} + +impl FDEFinder for CustomFinder { + fn find_fde(&self, pc: usize) -> Option { + get_custom_eh_frame_finder().and_then(|eh_frame_finder| find_fde(eh_frame_finder, pc)) + } +} + +/// A trait for types whose values can be used as the global EH frame finder set by [`set_custom_eh_frame_finder`]. +pub unsafe trait EhFrameFinder { + fn find(&self, pc: usize) -> Option; +} + +pub struct FrameInfo { + pub text_base: usize, + pub kind: FrameInfoKind, +} + +pub enum FrameInfoKind { + EhFrameHdr(usize), + EhFrame(usize), +} + +static mut CUSTOM_EH_FRAME_FINDER: Option<&(dyn EhFrameFinder + Sync)> = None; + +static CUSTOM_EH_FRAME_FINDER_STATE: AtomicU32 = AtomicU32::new(UNINITIALIZED); + +const UNINITIALIZED: u32 = 0; +const INITIALIZING: u32 = 1; +const INITIALIZED: u32 = 2; + +/// The type returned by [`set_custom_eh_frame_finder`] if [`set_custom_eh_frame_finder`] has +/// already been called. +#[derive(Debug)] +pub struct SetCustomEhFrameFinderError(()); + +/// Sets the global EH frame finder. +/// +/// This function should only be called once during the lifetime of the program. +/// +/// # Errors +/// +/// An error is returned if this function has already been called during the lifetime of the +/// program. +pub fn set_custom_eh_frame_finder( + fde_finder: &'static (dyn EhFrameFinder + Sync), +) -> Result<(), SetCustomEhFrameFinderError> { + match CUSTOM_EH_FRAME_FINDER_STATE.compare_exchange( + UNINITIALIZED, + INITIALIZING, + Ordering::SeqCst, + Ordering::SeqCst, + ) { + Ok(UNINITIALIZED) => { + unsafe { + CUSTOM_EH_FRAME_FINDER = Some(fde_finder); + } + CUSTOM_EH_FRAME_FINDER_STATE.store(INITIALIZED, Ordering::SeqCst); + Ok(()) + } + Err(INITIALIZING) => { + while CUSTOM_EH_FRAME_FINDER_STATE.load(Ordering::SeqCst) == INITIALIZING { + core::hint::spin_loop(); + } + Err(SetCustomEhFrameFinderError(())) + } + Err(INITIALIZED) => Err(SetCustomEhFrameFinderError(())), + _ => { + unreachable!() + } + } +} + +fn get_custom_eh_frame_finder() -> Option<&'static dyn EhFrameFinder> { + if CUSTOM_EH_FRAME_FINDER_STATE.load(Ordering::SeqCst) == INITIALIZED { + Some(unsafe { CUSTOM_EH_FRAME_FINDER.unwrap() }) + } else { + None + } +} + +fn find_fde(eh_frame_finder: &T, pc: usize) -> Option { + let info = eh_frame_finder.find(pc)?; + let text_base = info.text_base; + match info.kind { + FrameInfoKind::EhFrameHdr(eh_frame_hdr) => { + find_fde_with_eh_frame_hdr(pc, text_base, eh_frame_hdr) + } + FrameInfoKind::EhFrame(eh_frame) => find_fde_with_eh_frame(pc, text_base, eh_frame), + } +} + +fn find_fde_with_eh_frame_hdr( + pc: usize, + text_base: usize, + eh_frame_hdr: usize, +) -> Option { + unsafe { + let bases = BaseAddresses::default() + .set_text(text_base as _) + .set_eh_frame_hdr(eh_frame_hdr as _); + let eh_frame_hdr = EhFrameHdr::new( + get_unlimited_slice(eh_frame_hdr as usize as _), + NativeEndian, + ) + .parse(&bases, core::mem::size_of::() as _) + .ok()?; + let eh_frame = deref_pointer(eh_frame_hdr.eh_frame_ptr()); + let bases = bases.set_eh_frame(eh_frame as _); + let eh_frame = EhFrame::new(get_unlimited_slice(eh_frame as _), NativeEndian); + + // Use binary search table for address if available. + if let Some(table) = eh_frame_hdr.table() { + if let Ok(fde) = + table.fde_for_address(&eh_frame, &bases, pc as _, EhFrame::cie_from_offset) + { + return Some(FDESearchResult { + fde, + bases, + eh_frame, + }); + } + } + + // Otherwise do the linear search. + if let Ok(fde) = eh_frame.fde_for_address(&bases, pc as _, EhFrame::cie_from_offset) { + return Some(FDESearchResult { + fde, + bases, + eh_frame, + }); + } + + None + } +} + +fn find_fde_with_eh_frame(pc: usize, text_base: usize, eh_frame: usize) -> Option { + unsafe { + let bases = BaseAddresses::default() + .set_text(text_base as _) + .set_eh_frame(eh_frame as _); + let eh_frame = EhFrame::new(get_unlimited_slice(eh_frame as _), NativeEndian); + + if let Ok(fde) = eh_frame.fde_for_address(&bases, pc as _, EhFrame::cie_from_offset) { + return Some(FDESearchResult { + fde, + bases, + eh_frame, + }); + } + + None + } +} diff --git a/src/unwinder/find_fde/mod.rs b/src/unwinder/find_fde/mod.rs index d203df4..4db64a0 100644 --- a/src/unwinder/find_fde/mod.rs +++ b/src/unwinder/find_fde/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "fde-custom")] +mod custom; #[cfg(feature = "fde-static")] mod fixed; #[cfg(feature = "fde-gnu-eh-frame-hdr")] @@ -10,6 +12,14 @@ mod registry; use crate::util::*; use gimli::{BaseAddresses, EhFrame, FrameDescriptionEntry}; +#[cfg(feature = "fde-custom")] +pub mod custom_eh_frame_finder { + pub use super::custom::{ + set_custom_eh_frame_finder, EhFrameFinder, FrameInfo, FrameInfoKind, + SetCustomEhFrameFinderError, + }; +} + #[derive(Debug)] pub struct FDESearchResult { pub fde: FrameDescriptionEntry, @@ -25,6 +35,10 @@ pub struct GlobalFinder(()); impl FDEFinder for GlobalFinder { fn find_fde(&self, pc: usize) -> Option { + #[cfg(feature = "fde-custom")] + if let Some(v) = custom::get_finder().find_fde(pc) { + return Some(v); + } #[cfg(feature = "fde-registry")] if let Some(v) = registry::get_finder().find_fde(pc) { return Some(v); diff --git a/src/unwinder/mod.rs b/src/unwinder/mod.rs index a62a911..07ee029 100644 --- a/src/unwinder/mod.rs +++ b/src/unwinder/mod.rs @@ -13,6 +13,9 @@ use arch::*; use find_fde::FDEFinder; use frame::Frame; +#[cfg(feature = "fde-custom")] +pub use find_fde::custom_eh_frame_finder; + #[repr(C)] pub struct UnwindException { pub exception_class: u64,