wasmer_vm/trap/
traphandlers.rs

1// This file contains code from external sources.
2// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
3
4//! WebAssembly trap handling, which is built on top of the lower-level
5//! signalhandling mechanisms.
6
7use super::trapcode::TrapCode;
8use crate::vmcontext::{VMFunctionBody, VMFunctionEnvironment, VMTrampoline};
9use backtrace::Backtrace;
10use std::any::Any;
11use std::cell::{Cell, UnsafeCell};
12use std::error::Error;
13use std::mem::{self, MaybeUninit};
14use std::ptr;
15pub use tls::TlsRestore;
16
17extern "C" {
18    fn wasmer_register_setjmp(
19        jmp_buf: *mut *const u8,
20        callback: extern "C" fn(*mut u8),
21        payload: *mut u8,
22    ) -> i32;
23    fn wasmer_unwind(jmp_buf: *const u8) -> !;
24}
25
26/// Raises a user-defined trap immediately.
27///
28/// This function performs as-if a wasm trap was just executed, only the trap
29/// has a dynamic payload associated with it which is user-provided. This trap
30/// payload is then returned from `catch_traps` below.
31///
32/// # Safety
33///
34/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
35/// have been previous called and not yet returned.
36/// Additionally no Rust destructors may be on the stack.
37/// They will be skipped and not executed.
38pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
39    tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
40}
41
42/// Raises a trap from inside library code immediately.
43///
44/// This function performs as-if a wasm trap was just executed. This trap
45/// payload is then returned from `catch_traps` below.
46///
47/// # Safety
48///
49/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
50/// have been previous called and not yet returned.
51/// Additionally no Rust destructors may be on the stack.
52/// They will be skipped and not executed.
53pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
54    tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap)))
55}
56
57/// Carries a Rust panic across wasm code and resumes the panic on the other
58/// side.
59///
60/// # Safety
61///
62/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
63/// have been previously called and not returned. Additionally no Rust destructors may be on the
64/// stack. They will be skipped and not executed.
65pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
66    tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload)))
67}
68
69/// Stores trace message with backtrace.
70#[derive(Debug)]
71pub enum Trap {
72    /// A user-raised trap through `raise_user_trap`.
73    User(Box<dyn Error + Send + Sync>),
74
75    /// A trap raised from the Wasm generated code
76    ///
77    /// Note: this trap is deterministic (assuming a deterministic host implementation)
78    Wasm {
79        /// The program counter in generated code where this trap happened.
80        pc: usize,
81        /// Native stack backtrace at the time the trap occurred
82        backtrace: Backtrace,
83        /// Optional trapcode associated to the signal that caused the trap
84        signal_trap: Option<TrapCode>,
85    },
86
87    /// A trap raised from a wasm libcall
88    ///
89    /// Note: this trap is deterministic (assuming a deterministic host implementation)
90    Lib {
91        /// Code of the trap.
92        trap_code: TrapCode,
93        /// Native stack backtrace at the time the trap occurred
94        backtrace: Backtrace,
95    },
96
97    /// A trap indicating that the runtime was unable to allocate sufficient memory.
98    ///
99    /// Note: this trap is nondeterministic, since it depends on the host system.
100    OOM {
101        /// Native stack backtrace at the time the OOM occurred
102        backtrace: Backtrace,
103    },
104}
105
106impl Trap {
107    /// Construct a new Wasm trap with the given source location and backtrace.
108    ///
109    /// Internally saves a backtrace when constructed.
110    pub fn wasm(pc: usize, backtrace: Backtrace, signal_trap: Option<TrapCode>) -> Self {
111        Self::Wasm {
112            pc,
113            backtrace,
114            signal_trap,
115        }
116    }
117
118    /// Construct a new Wasm trap with the given trap code.
119    ///
120    /// Internally saves a backtrace when constructed.
121    pub fn lib(trap_code: TrapCode) -> Self {
122        let backtrace = Backtrace::new_unresolved();
123        Self::Lib {
124            trap_code,
125            backtrace,
126        }
127    }
128
129    /// Construct a new OOM trap with the given source location and trap code.
130    ///
131    /// Internally saves a backtrace when constructed.
132    pub fn oom() -> Self {
133        let backtrace = Backtrace::new_unresolved();
134        Self::OOM { backtrace }
135    }
136}
137
138/// Call the VM function pointed to by `callee`.
139///
140/// * `callee_env` - the function environment
141/// * `trampoline` - the jit-generated trampoline whose ABI takes 3 values, the
142///    callee funcenv, the `callee` argument below, and then the `values_vec` argument.
143/// * `callee` - the 2nd argument to the `trampoline` function
144/// * `values_vec` - points to a buffer which holds the incoming arguments, and to
145///   which the outgoing return values will be written.
146///
147/// Prefer invoking this via `Instance::invoke_trampoline`.
148///
149/// # Safety
150///
151/// Wildly unsafe because it calls raw function pointers and reads/writes raw
152/// function pointers.
153pub unsafe fn wasmer_call_trampoline(
154    callee_env: VMFunctionEnvironment,
155    trampoline: VMTrampoline,
156    callee: *const VMFunctionBody,
157    values_vec: *mut u8,
158) -> Result<(), Trap> {
159    catch_traps(|| {
160        mem::transmute::<_, extern "C" fn(VMFunctionEnvironment, *const VMFunctionBody, *mut u8)>(
161            trampoline,
162        )(callee_env, callee, values_vec);
163    })
164}
165
166/// Catches any wasm traps that happen within the execution of `closure`,
167/// returning them as a `Result`.
168///
169/// # Safety
170///
171/// Soundness must not depend on `closure` destructors being run.
172pub unsafe fn catch_traps<F>(mut closure: F) -> Result<(), Trap>
173where
174    F: FnMut(),
175{
176    return CallThreadState::new().with(|cx| {
177        wasmer_register_setjmp(
178            cx.jmp_buf.as_ptr(),
179            call_closure::<F>,
180            &mut closure as *mut F as *mut u8,
181        )
182    });
183
184    extern "C" fn call_closure<F>(payload: *mut u8)
185    where
186        F: FnMut(),
187    {
188        unsafe { (*(payload as *mut F))() }
189    }
190}
191
192/// Catches any wasm traps that happen within the execution of `closure`,
193/// returning them as a `Result`, with the closure contents.
194///
195/// The main difference from this method and `catch_traps`, is that is able
196/// to return the results from the closure.
197///
198/// # Safety
199///
200/// Check [`catch_traps`].
201pub unsafe fn catch_traps_with_result<F, R>(mut closure: F) -> Result<R, Trap>
202where
203    F: FnMut() -> R,
204{
205    let mut global_results = MaybeUninit::<R>::uninit();
206    catch_traps(|| {
207        global_results.as_mut_ptr().write(closure());
208    })?;
209    Ok(global_results.assume_init())
210}
211
212/// Temporary state stored on the stack which is registered in the `tls` module
213/// below for calls into wasm.
214pub struct CallThreadState {
215    unwind: UnsafeCell<MaybeUninit<UnwindReason>>,
216    jmp_buf: Cell<*const u8>,
217    prev: Cell<tls::Ptr>,
218}
219
220enum UnwindReason {
221    /// A panic caused by the host
222    Panic(Box<dyn Any + Send>),
223    /// A custom error triggered by the user
224    UserTrap(Box<dyn Error + Send + Sync>),
225    /// A Trap triggered by a wasm libcall
226    LibTrap(Trap),
227    /// A trap caused by the Wasm generated code
228    WasmTrap {
229        backtrace: Backtrace,
230        pc: usize,
231        signal_trap: Option<TrapCode>,
232    },
233}
234
235impl<'a> CallThreadState {
236    #[inline]
237    fn new() -> Self {
238        Self {
239            unwind: UnsafeCell::new(MaybeUninit::uninit()),
240            jmp_buf: Cell::new(ptr::null()),
241            prev: Cell::new(ptr::null()),
242        }
243    }
244
245    fn with(self, closure: impl FnOnce(&Self) -> i32) -> Result<(), Trap> {
246        let ret = tls::set(&self, || closure(&self))?;
247        if ret != 0 {
248            return Ok(());
249        }
250        // We will only reach this path if ret == 0. And that will
251        // only happen if a trap did happen. As such, it's safe to
252        // assume that the `unwind` field is already initialized
253        // at this moment.
254        match unsafe { (*self.unwind.get()).as_ptr().read() } {
255            UnwindReason::UserTrap(data) => Err(Trap::User(data)),
256            UnwindReason::LibTrap(trap) => Err(trap),
257            UnwindReason::WasmTrap {
258                backtrace,
259                pc,
260                signal_trap,
261            } => Err(Trap::wasm(pc, backtrace, signal_trap)),
262            UnwindReason::Panic(panic) => std::panic::resume_unwind(panic),
263        }
264    }
265
266    fn unwind_with(&self, reason: UnwindReason) -> ! {
267        unsafe {
268            (*self.unwind.get()).as_mut_ptr().write(reason);
269            wasmer_unwind(self.jmp_buf.get());
270        }
271    }
272}
273
274// A private inner module for managing the TLS state that we require across
275// calls in wasm. The WebAssembly code is called from C++ and then a trap may
276// happen which requires us to read some contextual state to figure out what to
277// do with the trap. This `tls` module is used to persist that information from
278// the caller to the trap site.
279mod tls {
280    use super::CallThreadState;
281    use crate::Trap;
282    use std::mem;
283    use std::ptr;
284
285    pub use raw::Ptr;
286
287    // An even *more* inner module for dealing with TLS. This actually has the
288    // thread local variable and has functions to access the variable.
289    //
290    // Note that this is specially done to fully encapsulate that the accessors
291    // for tls must not be inlined. Wasmer's async support will employ stack
292    // switching which can resume execution on different OS threads. This means
293    // that borrows of our TLS pointer must never live across accesses because
294    // otherwise the access may be split across two threads and cause unsafety.
295    //
296    // This also means that extra care is taken by the runtime to save/restore
297    // these TLS values when the runtime may have crossed threads.
298    mod raw {
299        use super::CallThreadState;
300        use crate::Trap;
301        use std::cell::Cell;
302        use std::ptr;
303
304        pub type Ptr = *const CallThreadState;
305
306        // The first entry here is the `Ptr` which is what's used as part of the
307        // public interface of this module. The second entry is a boolean which
308        // allows the runtime to perform per-thread initialization if necessary
309        // for handling traps (e.g. setting up ports on macOS and sigaltstack on
310        // Unix).
311        thread_local!(static PTR: Cell<Ptr> = Cell::new(ptr::null()));
312
313        #[inline(never)] // see module docs for why this is here
314        pub fn replace(val: Ptr) -> Result<Ptr, Trap> {
315            PTR.with(|p| {
316                // When a new value is configured that means that we may be
317                // entering WebAssembly so check to see if this thread has
318                // performed per-thread initialization for traps.
319                let prev = p.get();
320                p.set(val);
321                Ok(prev)
322            })
323        }
324
325        #[inline(never)] // see module docs for why this is here
326        pub fn get() -> Ptr {
327            PTR.with(|p| p.get())
328        }
329    }
330
331    /// Opaque state used to help control TLS state across stack switches for
332    /// async support.
333    pub struct TlsRestore(raw::Ptr);
334
335    impl TlsRestore {
336        /// Takes the TLS state that is currently configured and returns a
337        /// token that is used to replace it later.
338        ///
339        /// # Safety
340        ///
341        /// This is not a safe operation since it's intended to only be used
342        /// with stack switching found with fibers and async wasmer.
343        pub unsafe fn take() -> Result<Self, Trap> {
344            // Our tls pointer must be set at this time, and it must not be
345            // null. We need to restore the previous pointer since we're
346            // removing ourselves from the call-stack, and in the process we
347            // null out our own previous field for safety in case it's
348            // accidentally used later.
349            let raw = raw::get();
350            assert!(!raw.is_null());
351            let prev = (*raw).prev.replace(ptr::null());
352            raw::replace(prev)?;
353            Ok(Self(raw))
354        }
355
356        /// Restores a previous tls state back into this thread's TLS.
357        ///
358        /// # Safety
359        ///
360        /// This is unsafe because it's intended to only be used within the
361        /// context of stack switching within wasmer.
362        pub unsafe fn replace(self) -> Result<(), super::Trap> {
363            // We need to configure our previous TLS pointer to whatever is in
364            // TLS at this time, and then we set the current state to ourselves.
365            let prev = raw::get();
366            assert!((*self.0).prev.get().is_null());
367            (*self.0).prev.set(prev);
368            raw::replace(self.0)?;
369            Ok(())
370        }
371    }
372
373    /// Configures thread local state such that for the duration of the
374    /// execution of `closure` any call to `with` will yield `ptr`, unless this
375    /// is recursively called again.
376    pub fn set<R>(state: &CallThreadState, closure: impl FnOnce() -> R) -> Result<R, Trap> {
377        struct Reset<'a>(&'a CallThreadState);
378
379        impl Drop for Reset<'_> {
380            #[inline]
381            fn drop(&mut self) {
382                raw::replace(self.0.prev.replace(ptr::null()))
383                    .expect("tls should be previously initialized");
384            }
385        }
386
387        // Note that this extension of the lifetime to `'static` should be
388        // safe because we only ever access it below with an anonymous
389        // lifetime, meaning `'static` never leaks out of this module.
390        let ptr = unsafe { mem::transmute::<*const CallThreadState, _>(state) };
391        let prev = raw::replace(ptr)?;
392        state.prev.set(prev);
393        let _reset = Reset(state);
394        Ok(closure())
395    }
396
397    /// Returns the last pointer configured with `set` above. Panics if `set`
398    /// has not been previously called and not returned.
399    pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState>) -> R) -> R {
400        let p = raw::get();
401        unsafe { closure(if p.is_null() { None } else { Some(&*p) }) }
402    }
403}
404
405extern "C" fn signal_less_trap_handler(pc: *const u8, trap: TrapCode) {
406    let jmp_buf = tls::with(|info| {
407        let backtrace = Backtrace::new_unresolved();
408        let info = info.unwrap();
409        unsafe {
410            (*info.unwind.get())
411                .as_mut_ptr()
412                .write(UnwindReason::WasmTrap {
413                    backtrace,
414                    signal_trap: Some(trap),
415                    pc: pc as usize,
416                });
417            info.jmp_buf.get()
418        }
419    });
420    unsafe {
421        wasmer_unwind(jmp_buf);
422    }
423}
424
425/// Returns pointer to the trap handler used in VMContext.
426pub fn get_trap_handler() -> *const u8 {
427    signal_less_trap_handler as *const u8
428}