wasmer_vm/libcalls/eh/gcc.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
//! Implementation of personality function and unwinding support for Wasmer.
//!
//! On a native platform, when an exception is thrown, the type info of the
//! exception is known, and can be matched against the LSDA table within the
//! personality function (e.g. __gxx_personality_v0 for Itanium ABI).
//!
//! However, in WASM, the exception "type" can change between compilation
//! and instantiation because tags can be imported from other modules. Also,
//! a single module can be instantiated many times, but all instances share
//! the same code, differing only in their VMContext data. This means that,
//! to be able to match the thrown exception against the expected tag in
//! catch clauses, we need to go through the VMContext of the specific instance
//! to which the stack frame belongs; nothing else can tell us exactly which
//! instance we're currently looking at, including the IP which will be the
//! same for all instances of the same module.
//!
//! To achieve this, we use a two-stage personality function. The first stage
//! is the normal personality function which is called by libunwind; this
//! function always catches the exception as long as it's a Wasmer exception,
//! without looking at the specific tags. Afterwards, control is transferred
//! to the module's landing pad, which can load its VMContext and pass it to
//! the second stage of the personality function. Afterwards, the second stage
//! can take the "local tag number" (the tag index as seen from the WASM
//! module's point of view) from the LSDA and translate it to the unique tag
//! within the Store, and match that against the thrown exception's tag.
//!
//! The throw function also uses the VMContext of its own instance to get the
//! unique tag from the Store, and uses that as the final exception tag.
//!
//! It's important to note that we can't count on libunwind behaving properly
//! if we make calls from the second stage of the personality function; this is
//! why the first stage has to extract all the data necessary for the second
//! stage and place it in the exception object. The second stage will clear
//! out the data before returning, so further stack frames will not get stale
//! data by mistake.
use libunwind as uw;
use wasmer_types::TagIndex;
use crate::VMContext;
use super::dwarf::eh::{self, EHAction, EHContext};
// In case where multiple copies of std exist in a single process,
// we use address of this static variable to distinguish an exception raised by
// this copy and some other copy (which needs to be treated as foreign exception).
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;
// This constant is not reflected in the generated code, but the switch block
// has a default action of rethrowing the exception, which this value should
// trigger.
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 cause: Box<dyn std::any::Any + Send>,
// First stage -> second stage communication
pub current_frame_info: Option<Box<CurrentFrameInfo>>,
}
#[repr(C)]
pub struct CurrentFrameInfo {
pub exception_tag: u32,
pub catch_tags: Vec<u32>,
pub has_catch_all: bool,
}
impl UwExceptionWrapper {
pub fn new(tag: u32, data_ptr: usize, data_size: u64) -> Self {
Self {
_uwe: uw::_Unwind_Exception {
exception_class: WASMER_EXCEPTION_CLASS,
exception_cleanup: None,
private_1: core::ptr::null::<u8>() as usize as _,
private_2: 0,
},
canary: &CANARY,
cause: Box::new(WasmerException {
tag,
data_ptr,
data_size,
}),
current_frame_info: None,
}
}
}
#[repr(C)]
#[derive(Debug, thiserror::Error, Clone)]
#[error("Uncaught exception in wasm code!")]
pub struct WasmerException {
// This is the store-unique tag index.
pub tag: u32,
pub data_ptr: usize,
pub data_size: u64,
}
impl WasmerException {
pub fn new(tag: u32, data_ptr: usize, data_size: u64) -> Self {
Self {
tag,
data_ptr,
data_size,
}
}
}
#[cfg(target_arch = "x86_64")]
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 / X0, X1
#[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))]
const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11
#[cfg(target_arch = "loongarch64")]
const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1
#[no_mangle]
/// The implementation of Wasmer's personality function.
///
/// # Safety
///
/// Performs libunwind unwinding magic.
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 wasmer_exc = (*uw_exc).cause.downcast_ref::<WasmerException>();
let wasmer_exc = match wasmer_exc {
Some(e) => e,
None => {
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;
}
};
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 {
// For the catch-specific vs catch-specific-or-all case below, checked before
// we move eh_action out in the match
let has_catch_all = matches!(eh_action, EHAction::CatchSpecificOrAll { .. });
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 _);
// Zero means immediate catch-all
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 } => {
(*uw_exc).current_frame_info = Some(Box::new(CurrentFrameInfo {
exception_tag: wasmer_exc.tag,
catch_tags: tags,
has_catch_all,
}));
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, uw_exc as _);
// One means enter phase 2
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,
}
}
}
}
#[no_mangle]
/// The second stage of the personality function. See module level documentation
/// for an explanation of the exact procedure used during unwinding.
///
/// # Safety
///
/// Does pointer accesses, which must be valid.
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 {
// This should never happen
unreachable!("wasmer_eh_personality2 called without current_frame_info");
};
let instance = (*vmctx).instance();
for tag in current_frame_info.catch_tags {
let unique_tag = instance.shared_tag_ptr(TagIndex::from_u32(tag)).index();
if unique_tag == current_frame_info.exception_tag {
return tag as i32;
}
}
if current_frame_info.has_catch_all {
CATCH_ALL_TAG_VALUE
} else {
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 {
// The return address points 1 byte past the call instruction,
// which could be in the next IP range in LSDA range table.
//
// `ip = -1` has special meaning, so use wrapping sub to allow for that
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 throw(tag: u32, vmctx: *mut VMContext, data_ptr: usize, data_size: u64) -> ! {
// Look up the unique tag from the VMContext.
let unique_tag = (*vmctx)
.instance()
.shared_tag_ptr(TagIndex::from_u32(tag))
.index();
let exception = Box::new(UwExceptionWrapper::new(unique_tag, data_ptr, data_size));
let exception_param = Box::into_raw(exception) as *mut libunwind::_Unwind_Exception;
match uw::_Unwind_RaiseException(exception_param) {
libunwind::_Unwind_Reason_Code__URC_END_OF_STACK => {
crate::raise_lib_trap(crate::Trap::lib(wasmer_types::TrapCode::UncaughtException))
}
_ => {
unreachable!()
}
}
}
pub unsafe fn rethrow(exc: *mut UwExceptionWrapper) -> ! {
if exc.is_null() {
panic!();
}
match uw::_Unwind_Resume_or_Rethrow(std::mem::transmute::<
*mut UwExceptionWrapper,
*mut libunwind::_Unwind_Exception,
>(exc))
{
libunwind::_Unwind_Reason_Code__URC_END_OF_STACK => {
crate::raise_lib_trap(crate::Trap::lib(wasmer_types::TrapCode::UncaughtException))
}
_ => unreachable!(),
}
}