use std::ffi::c_void;
use libunwind::{self as uw};
use wasmer_types::TagIndex;
use crate::{InternalStoreHandle, StoreHandle, StoreObjects, VMContext, VMExceptionRef};
use super::dwarf::eh::{self, EHAction, EHContext};
static CANARY: u8 = 0;
const WASMER_EXCEPTION_CLASS: uw::_Unwind_Exception_Class = u64::from_ne_bytes(*b"WMERWASM");
const CATCH_ALL_TAG_VALUE: i32 = i32::MAX;
const NO_MATCH_FOUND_TAG_VALUE: i32 = i32::MAX - 1;
#[repr(C)]
pub struct UwExceptionWrapper {
pub _uwe: uw::_Unwind_Exception,
pub canary: *const u8,
pub exnref: u32,
pub current_frame_info: Option<Box<CurrentFrameInfo>>,
}
#[repr(C)]
pub struct CurrentFrameInfo {
pub catch_tags: Vec<u32>,
pub has_catch_all: bool,
}
impl UwExceptionWrapper {
pub fn new(exnref: u32) -> Self {
Self {
_uwe: uw::_Unwind_Exception {
exception_class: WASMER_EXCEPTION_CLASS,
exception_cleanup: Some(deallocate_exception),
private_1: core::ptr::null::<u8>() as usize as _,
private_2: 0,
},
canary: &CANARY,
exnref,
current_frame_info: None,
}
}
}
#[cfg(target_arch = "x86_64")]
const UNWIND_DATA_REG: (i32, i32) = (0, 1);
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
const UNWIND_DATA_REG: (i32, i32) = (0, 1);
#[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))]
const UNWIND_DATA_REG: (i32, i32) = (10, 11);
#[cfg(target_arch = "loongarch64")]
const UNWIND_DATA_REG: (i32, i32) = (4, 5);
#[cfg(target_arch = "powerpc64")]
const UNWIND_DATA_REG: (i32, i32) = (3, 4);
macro_rules! log {
($e: expr) => {
if false {
eprintln!($e)
}
};
($($e: expr),*) => {
if false {
eprintln!($($e),*)
}
};
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wasmer_eh_personality(
version: std::ffi::c_int,
actions: uw::_Unwind_Action,
exception_class: uw::_Unwind_Exception_Class,
exception_object: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context,
) -> uw::_Unwind_Reason_Code {
unsafe {
if version != 1 {
return uw::_Unwind_Reason_Code__URC_FATAL_PHASE1_ERROR;
}
let uw_exc = std::mem::transmute::<*mut uw::_Unwind_Exception, *mut UwExceptionWrapper>(
exception_object,
);
if exception_class != WASMER_EXCEPTION_CLASS {
return uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND;
}
let eh_action = match find_eh_action(context) {
Ok(action) => action,
Err(_) => {
return uw::_Unwind_Reason_Code__URC_FATAL_PHASE1_ERROR;
}
};
let is_catch_specific_or_all = matches!(&eh_action, EHAction::CatchSpecificOrAll { .. });
log!("[wasmer][eh] stage1 action: {:?}", eh_action);
if actions as i32 & uw::_Unwind_Action__UA_SEARCH_PHASE as i32 != 0 {
match eh_action {
EHAction::None => uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND,
EHAction::CatchAll { .. }
| EHAction::CatchSpecific { .. }
| EHAction::CatchSpecificOrAll { .. } => uw::_Unwind_Reason_Code__URC_HANDLER_FOUND,
EHAction::Terminate => uw::_Unwind_Reason_Code__URC_FATAL_PHASE1_ERROR,
}
} else {
match eh_action {
EHAction::None => uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND,
EHAction::CatchAll { lpad } => {
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, uw_exc as _);
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0);
uw::_Unwind_SetIP(context, lpad as usize as _);
uw::_Unwind_Reason_Code__URC_INSTALL_CONTEXT
}
EHAction::CatchSpecific { lpad, tags }
| EHAction::CatchSpecificOrAll { lpad, tags } => {
log!(
"[wasmer][eh] stage1 catch tags={:?} catch_all={}",
tags,
is_catch_specific_or_all
);
(*uw_exc).current_frame_info = Some(Box::new(CurrentFrameInfo {
catch_tags: tags,
has_catch_all: is_catch_specific_or_all,
}));
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, uw_exc as _);
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 1);
uw::_Unwind_SetIP(context, lpad as usize as _);
uw::_Unwind_Reason_Code__URC_INSTALL_CONTEXT
}
EHAction::Terminate => uw::_Unwind_Reason_Code__URC_FATAL_PHASE2_ERROR,
}
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wasmer_eh_personality2(
vmctx: *mut VMContext,
exception_object: *mut UwExceptionWrapper,
) -> i32 {
unsafe {
let Some(current_frame_info) = (*exception_object).current_frame_info.take() else {
unreachable!("wasmer_eh_personality2 called without current_frame_info");
};
log!(
"[wasmer][eh] stage2 catch_tags={:?} has_catch_all={}",
current_frame_info.catch_tags,
current_frame_info.has_catch_all
);
let instance = (*vmctx).instance();
let exn = super::exn_obj_from_exnref(vmctx, (*exception_object).exnref);
for tag in current_frame_info.catch_tags {
log!("[wasmer][eh] stage2 checking tag {}", tag);
let unique_tag = instance.shared_tag_ptr(TagIndex::from_u32(tag)).index();
if unique_tag == (*exn).tag_index() {
return tag as i32;
}
}
if current_frame_info.has_catch_all {
log!("[wasmer][eh] stage2 falling back to catch-all");
CATCH_ALL_TAG_VALUE
} else {
log!("[wasmer][eh] stage2 found no match");
NO_MATCH_FOUND_TAG_VALUE
}
}
}
unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result<EHAction, ()> {
unsafe {
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
let mut ip_before_instr: std::ffi::c_int = 0;
let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr);
let eh_context = EHContext {
ip: if ip_before_instr != 0 {
ip as _
} else {
ip.wrapping_sub(1) as _
},
func_start: uw::_Unwind_GetRegionStart(context) as *const _,
get_text_start: &|| uw::_Unwind_GetTextRelBase(context) as *const _,
get_data_start: &|| uw::_Unwind_GetDataRelBase(context) as *const _,
};
eh::find_eh_action(lsda, &eh_context)
}
}
pub unsafe fn read_exnref(exception: *mut c_void) -> u32 {
if exception.is_null() {
0
} else {
unsafe { (*(exception as *mut UwExceptionWrapper)).exnref }
}
}
pub unsafe fn throw(ctx: &StoreObjects, exnref: u32) -> ! {
unsafe {
log!("[wasmer][eh] throw resolved exnref={exnref}");
if exnref == 0 {
crate::raise_lib_trap(crate::Trap::lib(
wasmer_types::TrapCode::UninitializedExnRef,
))
}
let exception = Box::new(UwExceptionWrapper::new(exnref));
let exception_ptr = Box::into_raw(exception);
let exit_code =
uw::_Unwind_RaiseException(exception_ptr as *mut libunwind::_Unwind_Exception);
delete_exception(exception_ptr as *mut c_void);
let exnref = VMExceptionRef(StoreHandle::from_internal(
ctx.id(),
InternalStoreHandle::from_index(exnref as usize).unwrap(),
));
log!(
"[wasmer][eh] throw -> _Unwind_RaiseException returned (personality={:p}) (exit_code={exit_code})",
wasmer_eh_personality as *const ()
);
crate::raise_lib_trap(crate::Trap::uncaught_exception(exnref, ctx))
}
}
pub unsafe fn delete_exception(exception: *mut c_void) {
unsafe {
if !exception.is_null() {
uw::_Unwind_DeleteException(exception as *mut uw::_Unwind_Exception);
}
}
}
unsafe extern "C" fn deallocate_exception(
_: uw::_Unwind_Reason_Code,
exception: *mut uw::_Unwind_Exception,
) {
unsafe {
let exception = Box::from_raw(exception as *mut UwExceptionWrapper);
drop(exception);
}
}