wasmer_vm/libcalls/eh/
gcc.rs

1use libunwind as uw;
2
3use super::dwarf::eh::{self, EHAction, EHContext};
4
5// In case where multiple copies of std exist in a single process,
6// we use address of this static variable to distinguish an exception raised by
7// this copy and some other copy (which needs to be treated as foreign exception).
8static CANARY: u8 = 0;
9const WASMER_EXCEPTION_CLASS: uw::_Unwind_Exception_Class = u64::from_ne_bytes(*b"WMERWASM");
10
11#[repr(C)]
12pub struct UwExceptionWrapper {
13    pub _uwe: uw::_Unwind_Exception,
14    pub canary: *const u8,
15    pub cause: Box<dyn std::any::Any + Send>,
16}
17
18impl UwExceptionWrapper {
19    pub fn new(tag: u64, data_ptr: usize, data_size: u64) -> Self {
20        Self {
21            _uwe: uw::_Unwind_Exception {
22                exception_class: WASMER_EXCEPTION_CLASS,
23                exception_cleanup: None,
24                private_1: core::ptr::null::<u8>() as usize as _,
25                private_2: 0,
26            },
27            canary: &CANARY,
28            cause: Box::new(WasmerException {
29                tag,
30                data_ptr,
31                data_size,
32            }),
33        }
34    }
35}
36
37#[repr(C)]
38#[derive(Debug, thiserror::Error, Clone)]
39#[error("Uncaught exception in wasm code!")]
40pub struct WasmerException {
41    pub tag: u64,
42    pub data_ptr: usize,
43    pub data_size: u64,
44}
45
46impl WasmerException {
47    pub fn new(tag: u64, data_ptr: usize, data_size: u64) -> Self {
48        Self {
49            tag,
50            data_ptr,
51            data_size,
52        }
53    }
54}
55
56#[cfg(target_arch = "x86_64")]
57const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
58
59#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
60const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 / X0, X1
61
62#[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))]
63const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11
64
65#[cfg(target_arch = "loongarch64")]
66const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1
67
68#[no_mangle]
69/// The implementation of Wasmer's personality function.
70///
71/// # Safety
72///
73/// Performs libunwind unwinding magic.
74pub unsafe extern "C" fn wasmer_eh_personality(
75    version: std::ffi::c_int,
76    actions: uw::_Unwind_Action,
77    exception_class: uw::_Unwind_Exception_Class,
78    exception_object: *mut uw::_Unwind_Exception,
79    context: *mut uw::_Unwind_Context,
80) -> uw::_Unwind_Reason_Code {
81    unsafe {
82        if version != 1 {
83            return uw::_Unwind_Reason_Code__URC_FATAL_PHASE1_ERROR;
84        }
85
86        let uw_exc = std::mem::transmute::<*mut uw::_Unwind_Exception, *mut UwExceptionWrapper>(
87            exception_object,
88        );
89
90        if exception_class != WASMER_EXCEPTION_CLASS {
91            return uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND;
92        }
93
94        let wasmer_exc = (*uw_exc).cause.downcast_ref::<WasmerException>();
95        let wasmer_exc = match wasmer_exc {
96            Some(e) => e,
97            None => {
98                return uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND;
99            }
100        };
101
102        let eh_action = match find_eh_action(context, wasmer_exc.tag) {
103            Ok(action) => action,
104            Err(_) => {
105                return uw::_Unwind_Reason_Code__URC_FATAL_PHASE1_ERROR;
106            }
107        };
108
109        if actions as i32 & uw::_Unwind_Action__UA_SEARCH_PHASE as i32 != 0 {
110            match eh_action {
111                EHAction::None | EHAction::Cleanup(_) => {
112                    uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND
113                }
114                EHAction::Catch { .. } | EHAction::Filter { .. } => {
115                    uw::_Unwind_Reason_Code__URC_HANDLER_FOUND
116                }
117                EHAction::Terminate => uw::_Unwind_Reason_Code__URC_FATAL_PHASE1_ERROR,
118            }
119        } else {
120            match eh_action {
121                EHAction::None => uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND,
122                // Forced unwinding hits a terminate action.
123                EHAction::Filter { .. }
124                    if actions as i32 & uw::_Unwind_Action__UA_FORCE_UNWIND as i32 != 0 =>
125                {
126                    uw::_Unwind_Reason_Code__URC_CONTINUE_UNWIND
127                }
128                EHAction::Cleanup(lpad) => {
129                    uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, uw_exc as _);
130                    uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0);
131                    uw::_Unwind_SetIP(context, lpad as usize as _);
132                    uw::_Unwind_Reason_Code__URC_INSTALL_CONTEXT
133                }
134                EHAction::Catch { lpad, tag } | EHAction::Filter { lpad, tag } => {
135                    uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, uw_exc as _);
136                    #[allow(trivial_numeric_casts)]
137                    uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, tag as _);
138                    uw::_Unwind_SetIP(context, lpad as usize as _);
139                    uw::_Unwind_Reason_Code__URC_INSTALL_CONTEXT
140                }
141                EHAction::Terminate => uw::_Unwind_Reason_Code__URC_FATAL_PHASE2_ERROR,
142            }
143        }
144    }
145}
146
147unsafe fn find_eh_action(context: *mut uw::_Unwind_Context, tag: u64) -> Result<EHAction, ()> {
148    unsafe {
149        let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
150        let mut ip_before_instr: std::ffi::c_int = 0;
151        let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr);
152        let eh_context = EHContext {
153            // The return address points 1 byte past the call instruction,
154            // which could be in the next IP range in LSDA range table.
155            //
156            // `ip = -1` has special meaning, so use wrapping sub to allow for that
157            ip: if ip_before_instr != 0 {
158                ip as _
159            } else {
160                ip.wrapping_sub(1) as _
161            },
162            func_start: uw::_Unwind_GetRegionStart(context) as *const _,
163            get_text_start: &|| uw::_Unwind_GetTextRelBase(context) as *const _,
164            get_data_start: &|| uw::_Unwind_GetDataRelBase(context) as *const _,
165            tag,
166        };
167        eh::find_eh_action(lsda, &eh_context)
168    }
169}
170
171pub unsafe fn throw(tag: u64, data_ptr: usize, data_size: u64) -> ! {
172    let exception = Box::new(UwExceptionWrapper::new(tag, data_ptr, data_size));
173    let exception_param = Box::into_raw(exception) as *mut libunwind::_Unwind_Exception;
174
175    match uw::_Unwind_RaiseException(exception_param) {
176        libunwind::_Unwind_Reason_Code__URC_END_OF_STACK => {
177            crate::raise_lib_trap(crate::Trap::lib(wasmer_types::TrapCode::UncaughtException))
178        }
179        _ => {
180            unreachable!()
181        }
182    }
183}
184
185pub unsafe fn rethrow(exc: *mut UwExceptionWrapper) -> ! {
186    if exc.is_null() {
187        panic!();
188    }
189
190    match uw::_Unwind_Resume_or_Rethrow(std::mem::transmute::<
191        *mut UwExceptionWrapper,
192        *mut libunwind::_Unwind_Exception,
193    >(exc))
194    {
195        libunwind::_Unwind_Reason_Code__URC_END_OF_STACK => {
196            crate::raise_lib_trap(crate::Trap::lib(wasmer_types::TrapCode::UncaughtException))
197        }
198        _ => unreachable!(),
199    }
200}