use core::sync::atomic::{
AtomicBool, AtomicUsize,
Ordering::{self, Relaxed},
};
use std::sync::Once;
use crate::types::unwind::CompiledFunctionUnwindInfoReference;
pub struct UnwindRegistry {
published: bool,
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
registrations: Vec<usize>,
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
compact_unwind_mgr: compact_unwind::CompactUnwindManager,
}
unsafe extern "C" {
fn __register_frame(fde: *const u8);
fn __deregister_frame(fde: *const u8);
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod compact_unwind;
#[allow(dead_code)]
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;
}
if cfg!(target_env = "musl") {
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(),
c"__unw_add_dynamic_fde".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 {
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let result = libc::atexit(atexit_handler);
assert_eq!(result, 0, "libc::atexit must succeed");
});
assert!(
!EXIT_CALLED.load(Ordering::SeqCst),
"Cannot register unwind information during the process exit"
);
Self {
published: false,
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
registrations: Vec::new(),
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
compact_unwind_mgr: Default::default(),
}
}
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(())
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
pub fn publish_eh_frame(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> {
if self.published {
return Err("unwind registry has already been published".to_string());
}
unsafe {
if let Some(eh_frame) = eh_frame {
self.register_eh_frames(eh_frame);
}
}
self.published = true;
Ok(())
}
#[allow(clippy::cast_ptr_alignment)]
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
unsafe fn register_eh_frames(&mut self, eh_frame: &[u8]) {
{
let mut records_to_register = Vec::new();
let mut current = 0;
let mut last_len = 0;
let using_libunwind = using_libunwind();
while current <= (eh_frame.len() - size_of::<u32>()) {
let len = u32::from_ne_bytes(eh_frame[current..(current + 4)].try_into().unwrap());
if len == 0 {
current += size_of::<u32>();
last_len = 0;
continue;
}
let is_cie = last_len == 0;
last_len = len;
let record = eh_frame.as_ptr() as usize + current;
current = current + len as usize + 4;
if using_libunwind {
if !is_cie {
records_to_register.push(record);
}
} else {
if is_cie {
records_to_register.push(record);
}
}
}
assert_eq!(
last_len, 0,
"The last record in the `.eh_frame` must be a terminator (but it actually has length {last_len})"
);
assert_eq!(
current,
eh_frame.len(),
"The `.eh_frame` must be finished after the last record",
);
for record in records_to_register {
unsafe {
__register_frame(record as *const u8);
}
self.registrations.push(record);
}
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub(crate) fn publish_compact_unwind(
&mut self,
compact_unwind: &[u8],
eh_personality_addr_in_got: Option<usize>,
) -> Result<(), String> {
if self.published {
return Err("unwind registry has already been published".to_string());
}
unsafe {
self.compact_unwind_mgr.read_compact_unwind_section(
compact_unwind.as_ptr() as _,
compact_unwind.len(),
eh_personality_addr_in_got,
)?;
self.compact_unwind_mgr
.finalize()
.map_err(|v| v.to_string())?;
self.compact_unwind_mgr.register();
}
self.published = true;
Ok(())
}
}
pub static EXIT_CALLED: AtomicBool = AtomicBool::new(false);
extern "C" fn atexit_handler() {
EXIT_CALLED.store(true, Ordering::SeqCst);
}
impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
for registration in self.registrations.iter().rev() {
if EXIT_CALLED.load(Ordering::SeqCst) {
return;
}
unsafe {
__deregister_frame(*registration as *const _);
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
if EXIT_CALLED.load(Ordering::SeqCst) {
return;
}
self.compact_unwind_mgr.deregister();
}
}
}
}