wasmer_vm/libcalls/eh/
gcc.rs

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