use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use crate::types::unwind::CompiledFunctionUnwindInfoReference;
pub struct UnwindRegistry {
registrations: Vec<usize>,
published: bool,
}
extern "C" {
fn __register_frame(fde: *const u8);
fn __deregister_frame(fde: *const u8);
}
fn using_libunwind() -> bool {
static USING_LIBUNWIND: AtomicUsize = AtomicUsize::new(LIBUNWIND_UNKNOWN);
const LIBUNWIND_UNKNOWN: usize = 0;
const LIBUNWIND_YES: usize = 1;
const LIBUNWIND_NO: usize = 2;
if cfg!(target_os = "macos") {
return true;
}
match USING_LIBUNWIND.load(Relaxed) {
LIBUNWIND_YES => true,
LIBUNWIND_NO => false,
LIBUNWIND_UNKNOWN => {
let looks_like_libunwind = unsafe {
!libc::dlsym(
std::ptr::null_mut(),
"__unw_add_dynamic_fde\0".as_ptr().cast(),
)
.is_null()
};
USING_LIBUNWIND.store(
if looks_like_libunwind {
LIBUNWIND_YES
} else {
LIBUNWIND_NO
},
Relaxed,
);
looks_like_libunwind
}
_ => unreachable!(),
}
}
impl UnwindRegistry {
pub fn new() -> Self {
Self {
registrations: Vec::new(),
published: false,
}
}
pub fn register(
&mut self,
_base_address: usize,
_func_start: u32,
_func_len: u32,
info: &CompiledFunctionUnwindInfoReference,
) -> Result<(), String> {
match info {
CompiledFunctionUnwindInfoReference::Dwarf => {}
_ => return Err(format!("unsupported unwind information {info:?}")),
};
Ok(())
}
pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> {
if self.published {
return Err("unwind registry has already been published".to_string());
}
if let Some(eh_frame) = eh_frame {
unsafe {
self.register_frames(eh_frame);
}
}
self.published = true;
Ok(())
}
#[allow(clippy::cast_ptr_alignment)]
unsafe fn register_frames(&mut self, eh_frame: &[u8]) {
if !using_libunwind() {
debug_assert_ne!(
eh_frame,
&[0, 0, 0, 0],
"`eh_frame` seems to contain empty FDEs"
);
let ptr = eh_frame.as_ptr();
__register_frame(ptr);
self.registrations.push(ptr as usize);
} else {
let start = eh_frame.as_ptr();
let end = start.add(eh_frame.len());
let mut current = start;
while current < end {
let len = std::ptr::read::<u32>(current as *const u32) as usize;
if current != start && len != 0 {
__register_frame(current);
self.registrations.push(current as usize);
}
current = current.add(len + 4);
}
}
}
}
impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
unsafe {
for fde in self.registrations.iter().rev() {
__deregister_frame(*fde as *const _);
}
}
}
}
}