wasmer_vm/trap/
traphandlers.rs

1// This file contains code from external sources.
2// Attributions: https://github.com/wasmerio/wasmer/blob/main/docs/ATTRIBUTIONS.md
3
4#![allow(static_mut_refs)]
5
6//! WebAssembly trap handling, which is built on top of the lower-level
7//! signalhandling mechanisms.
8
9use crate::vmcontext::{VMFunctionContext, VMTrampoline};
10use crate::{Trap, VMContext, VMFunctionBody};
11use backtrace::Backtrace;
12use core::ptr::{read, read_unaligned};
13use corosensei::stack::DefaultStack;
14use corosensei::trap::{CoroutineTrapHandler, TrapHandlerRegs};
15use corosensei::{CoroutineResult, ScopedCoroutine, Yielder};
16use scopeguard::defer;
17use std::any::Any;
18use std::cell::Cell;
19use std::error::Error;
20use std::io;
21use std::mem;
22#[cfg(unix)]
23use std::mem::MaybeUninit;
24use std::ptr::{self, NonNull};
25use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering, compiler_fence};
26use std::sync::{LazyLock, Once};
27use wasmer_types::TrapCode;
28
29/// Configuration for the runtime VM
30/// Currently only the stack size is configurable
31pub struct VMConfig {
32    /// Optional stack size (in byte) of the VM. Value lower than 8K will be rounded to 8K.
33    pub wasm_stack_size: Option<usize>,
34}
35
36// TrapInformation can be stored in the "Undefined Instruction" itself.
37// On x86_64, 0xC? select a "Register" for the Mod R/M part of "ud1" (so with no other bytes after)
38// On Arm64, the udf alows for a 16bits values, so we'll use the same 0xC? to store the trapinfo
39static MAGIC: u8 = 0xc0;
40
41static DEFAULT_STACK_SIZE: AtomicUsize = AtomicUsize::new(1024 * 1024);
42
43// Current definition of `ucontext_t` in the `libc` crate is incorrect
44// on aarch64-apple-drawin so it's defined here with a more accurate definition.
45#[repr(C)]
46#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
47#[allow(non_camel_case_types)]
48struct ucontext_t {
49    uc_onstack: libc::c_int,
50    uc_sigmask: libc::sigset_t,
51    uc_stack: libc::stack_t,
52    uc_link: *mut libc::ucontext_t,
53    uc_mcsize: usize,
54    uc_mcontext: libc::mcontext_t,
55}
56
57// Current definition of `ucontext_t` in the `libc` crate is not present
58// on aarch64-unknown-freebsd so it's defined here.
59#[repr(C)]
60#[cfg(all(target_arch = "aarch64", target_os = "freebsd"))]
61#[allow(non_camel_case_types)]
62struct ucontext_t {
63    uc_sigmask: libc::sigset_t,
64    uc_mcontext: libc::mcontext_t,
65    uc_link: *mut ucontext_t,
66    uc_stack: libc::stack_t,
67    uc_flags: libc::c_int,
68    spare: [libc::c_int; 4],
69}
70
71#[cfg(all(
72    unix,
73    not(all(target_arch = "aarch64", target_os = "macos")),
74    not(all(target_arch = "aarch64", target_os = "freebsd"))
75))]
76use libc::ucontext_t;
77
78/// Default stack size is 1MB.
79pub fn set_stack_size(size: usize) {
80    DEFAULT_STACK_SIZE.store(size.clamp(8 * 1024, 100 * 1024 * 1024), Ordering::Relaxed);
81}
82
83cfg_if::cfg_if! {
84    if #[cfg(unix)] {
85        /// Function which may handle custom signals while processing traps.
86        pub type TrapHandlerFn<'a> = dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
87    } else if #[cfg(target_os = "windows")] {
88        /// Function which may handle custom signals while processing traps.
89        pub type TrapHandlerFn<'a> = dyn Fn(*mut windows_sys::Win32::System::Diagnostics::Debug::EXCEPTION_POINTERS) -> bool + Send + Sync + 'a;
90    }
91}
92
93// Process an IllegalOpcode to see if it has a TrapCode payload
94unsafe fn process_illegal_op(addr: usize) -> Option<TrapCode> {
95    unsafe {
96        let mut val: Option<u8> = None;
97        if cfg!(target_arch = "x86_64") {
98            val = if read(addr as *mut u8) & 0xf0 == 0x40
99                && read((addr + 1) as *mut u8) == 0x0f
100                && read((addr + 2) as *mut u8) == 0xb9
101            {
102                Some(read((addr + 3) as *mut u8))
103            } else if read(addr as *mut u8) == 0x0f && read((addr + 1) as *mut u8) == 0xb9 {
104                Some(read((addr + 2) as *mut u8))
105            } else {
106                None
107            }
108        }
109        if cfg!(target_arch = "aarch64") {
110            val = if read_unaligned(addr as *mut u32) & 0xffff0000 == 0 {
111                Some(read(addr as *mut u8))
112            } else {
113                None
114            }
115        }
116        match val.and_then(|val| {
117            if val & MAGIC == MAGIC {
118                Some(val & 0xf)
119            } else {
120                None
121            }
122        }) {
123            None => None,
124            Some(val) => match val {
125                0 => Some(TrapCode::StackOverflow),
126                1 => Some(TrapCode::HeapAccessOutOfBounds),
127                2 => Some(TrapCode::HeapMisaligned),
128                3 => Some(TrapCode::TableAccessOutOfBounds),
129                4 => Some(TrapCode::IndirectCallToNull),
130                5 => Some(TrapCode::BadSignature),
131                6 => Some(TrapCode::IntegerOverflow),
132                7 => Some(TrapCode::IntegerDivisionByZero),
133                8 => Some(TrapCode::BadConversionToInteger),
134                9 => Some(TrapCode::UnreachableCodeReached),
135                10 => Some(TrapCode::UnalignedAtomic),
136                _ => None,
137            },
138        }
139    }
140}
141
142cfg_if::cfg_if! {
143    if #[cfg(unix)] {
144        static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
145        static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
146        static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
147        static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
148
149        unsafe fn platform_init() { unsafe {
150            let register = |slot: &mut MaybeUninit<libc::sigaction>, signal: i32| {
151                let mut handler: libc::sigaction = mem::zeroed();
152                // The flags here are relatively careful, and they are...
153                //
154                // SA_SIGINFO gives us access to information like the program
155                // counter from where the fault happened.
156                //
157                // SA_ONSTACK allows us to handle signals on an alternate stack,
158                // so that the handler can run in response to running out of
159                // stack space on the main stack. Rust installs an alternate
160                // stack with sigaltstack, so we rely on that.
161                //
162                // SA_NODEFER allows us to reenter the signal handler if we
163                // crash while handling the signal, and fall through to the
164                // Breakpad handler by testing handlingSegFault.
165                handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
166                handler.sa_sigaction = trap_handler as *const () as usize;
167                libc::sigemptyset(&mut handler.sa_mask);
168                if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 {
169                    panic!(
170                        "unable to install signal handler: {}",
171                        io::Error::last_os_error(),
172                    );
173                }
174            };
175
176            // Allow handling OOB with signals on all architectures
177            register(&mut PREV_SIGSEGV, libc::SIGSEGV);
178
179            // Handle `unreachable` instructions which execute `ud2` right now
180            register(&mut PREV_SIGILL, libc::SIGILL);
181
182            // x86 uses SIGFPE to report division by zero
183            if cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") {
184                register(&mut PREV_SIGFPE, libc::SIGFPE);
185            }
186
187            // On ARM, handle Unaligned Accesses.
188            // On Darwin, guard page accesses are raised as SIGBUS.
189            if cfg!(target_arch = "arm") || cfg!(target_vendor = "apple") {
190                register(&mut PREV_SIGBUS, libc::SIGBUS);
191            }
192
193            // This is necessary to support debugging under LLDB on Darwin.
194            // For more details see https://github.com/mono/mono/commit/8e75f5a28e6537e56ad70bf870b86e22539c2fb7
195            #[cfg(target_vendor = "apple")]
196            {
197                use mach2::exception_types::*;
198                use mach2::kern_return::*;
199                use mach2::port::*;
200                use mach2::thread_status::*;
201                use mach2::traps::*;
202                use mach2::mach_types::*;
203
204                unsafe extern "C" {
205                    fn task_set_exception_ports(
206                        task: task_t,
207                        exception_mask: exception_mask_t,
208                        new_port: mach_port_t,
209                        behavior: exception_behavior_t,
210                        new_flavor: thread_state_flavor_t,
211                    ) -> kern_return_t;
212                }
213
214                #[allow(non_snake_case)]
215                #[cfg(target_arch = "x86_64")]
216                let MACHINE_THREAD_STATE = x86_THREAD_STATE64;
217                #[allow(non_snake_case)]
218                #[cfg(target_arch = "aarch64")]
219                let MACHINE_THREAD_STATE = 6;
220
221                task_set_exception_ports(
222                    mach_task_self(),
223                    EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC | EXC_MASK_BAD_INSTRUCTION,
224                    MACH_PORT_NULL,
225                    EXCEPTION_STATE_IDENTITY as exception_behavior_t,
226                    MACHINE_THREAD_STATE,
227                );
228            }
229        }}
230
231        unsafe extern "C" fn trap_handler(
232            signum: libc::c_int,
233            siginfo: *mut libc::siginfo_t,
234            context: *mut libc::c_void,
235        ) { unsafe {
236            let previous = match signum {
237                libc::SIGSEGV => &PREV_SIGSEGV,
238                libc::SIGBUS => &PREV_SIGBUS,
239                libc::SIGFPE => &PREV_SIGFPE,
240                libc::SIGILL => &PREV_SIGILL,
241                _ => panic!("unknown signal: {signum}"),
242            };
243            // We try to get the fault address associated to this signal
244            let maybe_fault_address = match signum {
245                libc::SIGSEGV | libc::SIGBUS => {
246                    Some((*siginfo).si_addr() as usize)
247                }
248                _ => None,
249            };
250            let trap_code = match signum {
251                // check if it was cased by a UD and if the Trap info is a payload to it
252                libc::SIGILL => {
253                    let addr = (*siginfo).si_addr() as usize;
254                    process_illegal_op(addr)
255                }
256                _ => None,
257            };
258            let ucontext = &mut *(context as *mut ucontext_t);
259            let (pc, sp) = get_pc_sp(ucontext);
260            let handled = TrapHandlerContext::handle_trap(
261                pc,
262                sp,
263                maybe_fault_address,
264                trap_code,
265                |regs| update_context(ucontext, regs),
266                |handler| handler(signum, siginfo, context),
267            );
268
269            if handled {
270                return;
271            }
272
273            // This signal is not for any compiled wasm code we expect, so we
274            // need to forward the signal to the next handler. If there is no
275            // next handler (SIG_IGN or SIG_DFL), then it's time to crash. To do
276            // this, we set the signal back to its original disposition and
277            // return. This will cause the faulting op to be re-executed which
278            // will crash in the normal way. If there is a next handler, call
279            // it. It will either crash synchronously, fix up the instruction
280            // so that execution can continue and return, or trigger a crash by
281            // returning the signal to it's original disposition and returning.
282            let previous = &*previous.as_ptr();
283            if previous.sa_flags & libc::SA_SIGINFO != 0 {
284                mem::transmute::<
285                    usize,
286                    extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
287                >(previous.sa_sigaction)(signum, siginfo, context)
288            } else if previous.sa_sigaction == libc::SIG_DFL
289            {
290                libc::sigaction(signum, previous, ptr::null_mut());
291            } else if previous.sa_sigaction != libc::SIG_IGN {
292                mem::transmute::<usize, extern "C" fn(libc::c_int)>(
293                    previous.sa_sigaction
294                )(signum)
295            }
296        }}
297
298        unsafe fn get_pc_sp(context: &ucontext_t) -> (usize, usize) {
299            let (pc, sp);
300            cfg_if::cfg_if! {
301                if #[cfg(all(
302                    any(target_os = "linux", target_os = "android"),
303                    target_arch = "x86_64",
304                ))] {
305                    pc = context.uc_mcontext.gregs[libc::REG_RIP as usize] as usize;
306                    sp = context.uc_mcontext.gregs[libc::REG_RSP as usize] as usize;
307                } else if #[cfg(all(
308                    any(target_os = "linux", target_os = "android"),
309                    target_arch = "x86",
310                ))] {
311                    pc = context.uc_mcontext.gregs[libc::REG_EIP as usize] as usize;
312                    sp = context.uc_mcontext.gregs[libc::REG_ESP as usize] as usize;
313                } else if #[cfg(all(target_os = "freebsd", target_arch = "x86"))] {
314                    pc = context.uc_mcontext.mc_eip as usize;
315                    sp = context.uc_mcontext.mc_esp as usize;
316                } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
317                    pc = context.uc_mcontext.mc_rip as usize;
318                    sp = context.uc_mcontext.mc_rsp as usize;
319                } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
320                    let mcontext = unsafe { &*context.uc_mcontext };
321                    pc = mcontext.__ss.__rip as usize;
322                    sp = mcontext.__ss.__rsp as usize;
323                } else if #[cfg(all(
324                        any(target_os = "linux", target_os = "android"),
325                        target_arch = "aarch64",
326                    ))] {
327                    pc = context.uc_mcontext.pc as usize;
328                    sp = context.uc_mcontext.sp as usize;
329                } else if #[cfg(all(
330                    any(target_os = "linux", target_os = "android"),
331                    target_arch = "arm",
332                ))] {
333                    pc = context.uc_mcontext.arm_pc as usize;
334                    sp = context.uc_mcontext.arm_sp as usize;
335                } else if #[cfg(all(
336                    any(target_os = "linux", target_os = "android"),
337                    any(target_arch = "riscv64", target_arch = "riscv32"),
338                ))] {
339                    pc = context.uc_mcontext.__gregs[libc::REG_PC] as usize;
340                    sp = context.uc_mcontext.__gregs[libc::REG_SP] as usize;
341                } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
342                    let mcontext = unsafe { &*context.uc_mcontext };
343                    pc = mcontext.__ss.__pc as usize;
344                    sp = mcontext.__ss.__sp as usize;
345                } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
346                    pc = context.uc_mcontext.mc_gpregs.gp_elr as usize;
347                    sp = context.uc_mcontext.mc_gpregs.gp_sp as usize;
348                } else if #[cfg(all(target_os = "linux", target_arch = "loongarch64"))] {
349                    pc = context.uc_mcontext.__gregs[1] as usize;
350                    sp = context.uc_mcontext.__gregs[3] as usize;
351                } else if #[cfg(all(target_os = "linux", target_arch = "powerpc64"))] {
352                    pc = (*context.uc_mcontext.regs).nip as usize;
353                    sp = (*context.uc_mcontext.regs).gpr[1] as usize;
354                } else {
355                    compile_error!("Unsupported platform");
356                }
357            };
358            (pc, sp)
359        }
360
361        unsafe fn update_context(context: &mut ucontext_t, regs: TrapHandlerRegs) {
362            cfg_if::cfg_if! {
363                if #[cfg(all(
364                        any(target_os = "linux", target_os = "android"),
365                        target_arch = "x86_64",
366                    ))] {
367                    let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
368                    context.uc_mcontext.gregs[libc::REG_RIP as usize] = rip as i64;
369                    context.uc_mcontext.gregs[libc::REG_RSP as usize] = rsp as i64;
370                    context.uc_mcontext.gregs[libc::REG_RBP as usize] = rbp as i64;
371                    context.uc_mcontext.gregs[libc::REG_RDI as usize] = rdi as i64;
372                    context.uc_mcontext.gregs[libc::REG_RSI as usize] = rsi as i64;
373                } else if #[cfg(all(
374                    any(target_os = "linux", target_os = "android"),
375                    target_arch = "x86",
376                ))] {
377                    let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
378                    context.uc_mcontext.gregs[libc::REG_EIP as usize] = eip as i32;
379                    context.uc_mcontext.gregs[libc::REG_ESP as usize] = esp as i32;
380                    context.uc_mcontext.gregs[libc::REG_EBP as usize] = ebp as i32;
381                    context.uc_mcontext.gregs[libc::REG_ECX as usize] = ecx as i32;
382                    context.uc_mcontext.gregs[libc::REG_EDX as usize] = edx as i32;
383                } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
384                    let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
385                    let mcontext = unsafe { &mut *context.uc_mcontext };
386                    mcontext.__ss.__rip = rip;
387                    mcontext.__ss.__rsp = rsp;
388                    mcontext.__ss.__rbp = rbp;
389                    mcontext.__ss.__rdi = rdi;
390                    mcontext.__ss.__rsi = rsi;
391                } else if #[cfg(all(target_os = "freebsd", target_arch = "x86"))] {
392                    let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
393                    context.uc_mcontext.mc_eip = eip as libc::register_t;
394                    context.uc_mcontext.mc_esp = esp as libc::register_t;
395                    context.uc_mcontext.mc_ebp = ebp as libc::register_t;
396                    context.uc_mcontext.mc_ecx = ecx as libc::register_t;
397                    context.uc_mcontext.mc_edx = edx as libc::register_t;
398                } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
399                    let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
400                    context.uc_mcontext.mc_rip = rip as libc::register_t;
401                    context.uc_mcontext.mc_rsp = rsp as libc::register_t;
402                    context.uc_mcontext.mc_rbp = rbp as libc::register_t;
403                    context.uc_mcontext.mc_rdi = rdi as libc::register_t;
404                    context.uc_mcontext.mc_rsi = rsi as libc::register_t;
405                } else if #[cfg(all(
406                        any(target_os = "linux", target_os = "android"),
407                        target_arch = "aarch64",
408                    ))] {
409                    let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
410                    context.uc_mcontext.pc = pc;
411                    context.uc_mcontext.sp = sp;
412                    context.uc_mcontext.regs[0] = x0;
413                    context.uc_mcontext.regs[1] = x1;
414                    context.uc_mcontext.regs[29] = x29;
415                    context.uc_mcontext.regs[30] = lr;
416                } else if #[cfg(all(
417                        any(target_os = "linux", target_os = "android"),
418                        target_arch = "arm",
419                    ))] {
420                    let TrapHandlerRegs {
421                        pc,
422                        r0,
423                        r1,
424                        r7,
425                        r11,
426                        r13,
427                        r14,
428                        cpsr_thumb,
429                        cpsr_endian,
430                    } = regs;
431                    context.uc_mcontext.arm_pc = pc;
432                    context.uc_mcontext.arm_r0 = r0;
433                    context.uc_mcontext.arm_r1 = r1;
434                    context.uc_mcontext.arm_r7 = r7;
435                    context.uc_mcontext.arm_fp = r11;
436                    context.uc_mcontext.arm_sp = r13;
437                    context.uc_mcontext.arm_lr = r14;
438                    if cpsr_thumb {
439                        context.uc_mcontext.arm_cpsr |= 0x20;
440                    } else {
441                        context.uc_mcontext.arm_cpsr &= !0x20;
442                    }
443                    if cpsr_endian {
444                        context.uc_mcontext.arm_cpsr |= 0x200;
445                    } else {
446                        context.uc_mcontext.arm_cpsr &= !0x200;
447                    }
448                } else if #[cfg(all(
449                    any(target_os = "linux", target_os = "android"),
450                    any(target_arch = "riscv64", target_arch = "riscv32"),
451                ))] {
452                    let TrapHandlerRegs { pc, ra, sp, a0, a1, s0 } = regs;
453                    context.uc_mcontext.__gregs[libc::REG_PC] = pc as libc::c_ulong;
454                    context.uc_mcontext.__gregs[libc::REG_RA] = ra as libc::c_ulong;
455                    context.uc_mcontext.__gregs[libc::REG_SP] = sp as libc::c_ulong;
456                    context.uc_mcontext.__gregs[libc::REG_A0] = a0 as libc::c_ulong;
457                    context.uc_mcontext.__gregs[libc::REG_A0 + 1] = a1 as libc::c_ulong;
458                    context.uc_mcontext.__gregs[libc::REG_S0] = s0 as libc::c_ulong;
459                } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
460                    let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
461                    let mcontext = unsafe { &mut *context.uc_mcontext };
462                    mcontext.__ss.__pc = pc;
463                    mcontext.__ss.__sp = sp;
464                    mcontext.__ss.__x[0] = x0;
465                    mcontext.__ss.__x[1] = x1;
466                    mcontext.__ss.__fp = x29;
467                    mcontext.__ss.__lr = lr;
468                } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
469                    let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
470                    context.uc_mcontext.mc_gpregs.gp_elr = pc as libc::register_t;
471                    context.uc_mcontext.mc_gpregs.gp_sp = sp as libc::register_t;
472                    context.uc_mcontext.mc_gpregs.gp_x[0] = x0 as libc::register_t;
473                    context.uc_mcontext.mc_gpregs.gp_x[1] = x1 as libc::register_t;
474                    context.uc_mcontext.mc_gpregs.gp_x[29] = x29 as libc::register_t;
475                    context.uc_mcontext.mc_gpregs.gp_x[30] = lr as libc::register_t;
476                } else if #[cfg(all(target_os = "linux", target_arch = "loongarch64"))] {
477                    let TrapHandlerRegs { pc, sp, a0, a1, fp, ra } = regs;
478                    context.uc_mcontext.__pc = pc;
479                    context.uc_mcontext.__gregs[1] = ra;
480                    context.uc_mcontext.__gregs[3] = sp;
481                    context.uc_mcontext.__gregs[4] = a0;
482                    context.uc_mcontext.__gregs[5] = a1;
483                    context.uc_mcontext.__gregs[22] = fp;
484                } else if #[cfg(all(target_os = "linux", target_arch = "powerpc64"))] {
485                    let TrapHandlerRegs { pc, sp, r3, r4, r31, lr } = regs;
486                    (*context.uc_mcontext.regs).nip = pc;
487                    (*context.uc_mcontext.regs).gpr[1] = sp;
488                    (*context.uc_mcontext.regs).gpr[3] = r3;
489                    (*context.uc_mcontext.regs).gpr[4] = r4;
490                    (*context.uc_mcontext.regs).gpr[31] = r31;
491                    (*context.uc_mcontext.regs).link = lr;
492                } else {
493                    compile_error!("Unsupported platform");
494                }
495            };
496        }
497    } else if #[cfg(target_os = "windows")] {
498        use windows_sys::Win32::System::Diagnostics::Debug::{
499            AddVectoredExceptionHandler,
500            CONTEXT,
501            EXCEPTION_CONTINUE_EXECUTION,
502            EXCEPTION_CONTINUE_SEARCH,
503            EXCEPTION_POINTERS,
504        };
505        use windows_sys::Win32::Foundation::{
506            EXCEPTION_ACCESS_VIOLATION,
507            EXCEPTION_ILLEGAL_INSTRUCTION,
508            EXCEPTION_INT_DIVIDE_BY_ZERO,
509            EXCEPTION_INT_OVERFLOW,
510            EXCEPTION_STACK_OVERFLOW,
511        };
512
513        unsafe fn platform_init() {
514            unsafe {
515                // our trap handler needs to go first, so that we can recover from
516                // wasm faults and continue execution, so pass `1` as a true value
517                // here.
518                let handler = AddVectoredExceptionHandler(1, Some(exception_handler));
519                if handler.is_null() {
520                    panic!("failed to add exception handler: {}", io::Error::last_os_error());
521                }
522            }
523        }
524
525        unsafe extern "system" fn exception_handler(
526            exception_info: *mut EXCEPTION_POINTERS
527        ) -> i32 {
528            unsafe {
529                // Check the kind of exception, since we only handle a subset within
530                // wasm code. If anything else happens we want to defer to whatever
531                // the rest of the system wants to do for this exception.
532                let record = &*(*exception_info).ExceptionRecord;
533                if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION &&
534                    record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION &&
535                    record.ExceptionCode != EXCEPTION_STACK_OVERFLOW &&
536                    record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO &&
537                    record.ExceptionCode != EXCEPTION_INT_OVERFLOW
538                {
539                    return EXCEPTION_CONTINUE_SEARCH;
540                }
541
542                // FIXME: this is what the previous C++ did to make sure that TLS
543                // works by the time we execute this trap handling code. This isn't
544                // exactly super easy to call from Rust though and it's not clear we
545                // necessarily need to do so. Leaving this here in case we need this
546                // in the future, but for now we can probably wait until we see a
547                // strange fault before figuring out how to reimplement this in
548                // Rust.
549                //
550                // if (!NtCurrentTeb()->Reserved1[sThreadLocalArrayPointerIndex]) {
551                //     return EXCEPTION_CONTINUE_SEARCH;
552                // }
553
554                let context = &mut *(*exception_info).ContextRecord;
555                let (pc, sp) = get_pc_sp(context);
556
557                // We try to get the fault address associated to this exception.
558                let maybe_fault_address = match record.ExceptionCode {
559                    EXCEPTION_ACCESS_VIOLATION => Some(record.ExceptionInformation[1]),
560                    EXCEPTION_STACK_OVERFLOW => Some(sp),
561                    _ => None,
562                };
563                let trap_code = match record.ExceptionCode {
564                    // check if it was cased by a UD and if the Trap info is a payload to it
565                    EXCEPTION_ILLEGAL_INSTRUCTION => {
566                        process_illegal_op(pc)
567                    }
568                    _ => None,
569                };
570                // This is basically the same as the unix version above, only with a
571                // few parameters tweaked here and there.
572                let handled = TrapHandlerContext::handle_trap(
573                    pc,
574                    sp,
575                    maybe_fault_address,
576                    trap_code,
577                    |regs| update_context(context, regs),
578                    |handler| handler(exception_info),
579                );
580
581                if handled {
582                    EXCEPTION_CONTINUE_EXECUTION
583                } else {
584                    EXCEPTION_CONTINUE_SEARCH
585                }
586            }
587        }
588
589        unsafe fn get_pc_sp(context: &CONTEXT) -> (usize, usize) {
590            let (pc, sp);
591            cfg_if::cfg_if! {
592                if #[cfg(target_arch = "x86_64")] {
593                    pc = context.Rip as usize;
594                    sp = context.Rsp as usize;
595                } else if #[cfg(target_arch = "x86")] {
596                    pc = context.Rip as usize;
597                    sp = context.Rsp as usize;
598                } else {
599                    compile_error!("Unsupported platform");
600                }
601            };
602            (pc, sp)
603        }
604
605        unsafe fn update_context(context: &mut CONTEXT, regs: TrapHandlerRegs) {
606            cfg_if::cfg_if! {
607                if #[cfg(target_arch = "x86_64")] {
608                    let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
609                    context.Rip = rip;
610                    context.Rsp = rsp;
611                    context.Rbp = rbp;
612                    context.Rdi = rdi;
613                    context.Rsi = rsi;
614                } else if #[cfg(target_arch = "x86")] {
615                    let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
616                    context.Eip = eip;
617                    context.Esp = esp;
618                    context.Ebp = ebp;
619                    context.Ecx = ecx;
620                    context.Edx = edx;
621                } else {
622                    compile_error!("Unsupported platform");
623                }
624            };
625        }
626    }
627}
628
629/// This function is required to be called before any WebAssembly is entered.
630/// This will configure global state such as signal handlers to prepare the
631/// process to receive wasm traps.
632///
633/// This function must not only be called globally once before entering
634/// WebAssembly but it must also be called once-per-thread that enters
635/// WebAssembly. Currently in wasmer's integration this function is called on
636/// creation of a `Store`.
637pub fn init_traps() {
638    static INIT: Once = Once::new();
639    INIT.call_once(|| unsafe {
640        platform_init();
641    });
642}
643
644/// Raises a user-defined trap immediately.
645///
646/// This function performs as-if a wasm trap was just executed, only the trap
647/// has a dynamic payload associated with it which is user-provided. This trap
648/// payload is then returned from `catch_traps` below.
649///
650/// # Safety
651///
652/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
653/// have been previous called and not yet returned.
654/// Additionally no Rust destructors may be on the stack.
655/// They will be skipped and not executed.
656pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
657    unsafe { unwind_with(UnwindReason::UserTrap(data)) }
658}
659
660/// Raises a trap from inside library code immediately.
661///
662/// This function performs as-if a wasm trap was just executed. This trap
663/// payload is then returned from `catch_traps` below.
664///
665/// # Safety
666///
667/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
668/// have been previous called and not yet returned.
669/// Additionally no Rust destructors may be on the stack.
670/// They will be skipped and not executed.
671pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
672    unsafe { unwind_with(UnwindReason::LibTrap(trap)) }
673}
674
675/// Carries a Rust panic across wasm code and resumes the panic on the other
676/// side.
677///
678/// # Safety
679///
680/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
681/// have been previously called and not returned. Additionally no Rust destructors may be on the
682/// stack. They will be skipped and not executed.
683pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
684    unsafe { unwind_with(UnwindReason::Panic(payload)) }
685}
686
687/// Call the wasm function pointed to by `callee`.
688///
689/// * `vmctx` - the callee vmctx argument
690/// * `caller_vmctx` - the caller vmctx argument
691/// * `trampoline` - the jit-generated trampoline whose ABI takes 4 values, the
692///   callee vmctx, the caller vmctx, the `callee` argument below, and then the
693///   `values_vec` argument.
694/// * `callee` - the third argument to the `trampoline` function
695/// * `values_vec` - points to a buffer which holds the incoming arguments, and to
696///   which the outgoing return values will be written.
697///
698/// # Safety
699///
700/// Wildly unsafe because it calls raw function pointers and reads/writes raw
701/// function pointers.
702pub unsafe fn wasmer_call_trampoline(
703    trap_handler: Option<*const TrapHandlerFn<'static>>,
704    config: &VMConfig,
705    vmctx: VMFunctionContext,
706    trampoline: VMTrampoline,
707    callee: *const VMFunctionBody,
708    values_vec: *mut u8,
709) -> Result<(), Trap> {
710    unsafe {
711        catch_traps(trap_handler, config, move || {
712            mem::transmute::<
713                unsafe extern "C" fn(
714                    *mut VMContext,
715                    *const VMFunctionBody,
716                    *mut wasmer_types::RawValue,
717                ),
718                extern "C" fn(VMFunctionContext, *const VMFunctionBody, *mut u8),
719            >(trampoline)(vmctx, callee, values_vec);
720        })
721    }
722}
723
724/// Catches any wasm traps that happen within the execution of `closure`,
725/// returning them as a `Result`.
726///
727/// # Safety
728///
729/// Highly unsafe since `closure` won't have any dtors run.
730pub unsafe fn catch_traps<F, R: 'static>(
731    trap_handler: Option<*const TrapHandlerFn<'static>>,
732    config: &VMConfig,
733    closure: F,
734) -> Result<R, Trap>
735where
736    F: FnOnce() -> R + 'static,
737{
738    // Ensure that per-thread initialization is done.
739    lazy_per_thread_init()?;
740    let stack_size = config
741        .wasm_stack_size
742        .unwrap_or_else(|| DEFAULT_STACK_SIZE.load(Ordering::Relaxed));
743    on_wasm_stack(stack_size, trap_handler, closure).map_err(UnwindReason::into_trap)
744}
745
746// We need two separate thread-local variables here:
747// - YIELDER is set within the new stack and is used to unwind back to the root
748//   of the stack from inside it.
749// - TRAP_HANDLER is set from outside the new stack and is solely used from
750//   signal handlers. It must be atomic since it is used by signal handlers.
751//
752// We also do per-thread signal stack initialization on the first time
753// TRAP_HANDLER is accessed.
754thread_local! {
755    static YIELDER: Cell<Option<NonNull<Yielder<(), UnwindReason>>>> = const { Cell::new(None) };
756    static TRAP_HANDLER: AtomicPtr<TrapHandlerContext> = const { AtomicPtr::new(ptr::null_mut()) };
757}
758
759/// Read-only information that is used by signal handlers to handle and recover
760/// from traps.
761#[allow(clippy::type_complexity)]
762struct TrapHandlerContext {
763    inner: *const u8,
764    handle_trap: fn(
765        *const u8,
766        usize,
767        usize,
768        Option<usize>,
769        Option<TrapCode>,
770        &mut dyn FnMut(TrapHandlerRegs),
771    ) -> bool,
772    custom_trap: Option<*const TrapHandlerFn<'static>>,
773}
774struct TrapHandlerContextInner<T> {
775    /// Information about the currently running coroutine. This is used to
776    /// reset execution to the root of the coroutine when a trap is handled.
777    coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
778}
779
780impl TrapHandlerContext {
781    /// Runs the given function with a trap handler context. The previous
782    /// trap handler context is preserved and restored afterwards.
783    fn install<T, R>(
784        custom_trap: Option<*const TrapHandlerFn<'static>>,
785        coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
786        f: impl FnOnce() -> R,
787    ) -> R {
788        // Type-erase the trap handler function so that it can be placed in TLS.
789        fn func<T>(
790            ptr: *const u8,
791            pc: usize,
792            sp: usize,
793            maybe_fault_address: Option<usize>,
794            trap_code: Option<TrapCode>,
795            update_regs: &mut dyn FnMut(TrapHandlerRegs),
796        ) -> bool {
797            unsafe {
798                (*(ptr as *const TrapHandlerContextInner<T>)).handle_trap(
799                    pc,
800                    sp,
801                    maybe_fault_address,
802                    trap_code,
803                    update_regs,
804                )
805            }
806        }
807        let inner = TrapHandlerContextInner { coro_trap_handler };
808        let ctx = Self {
809            inner: &inner as *const _ as *const u8,
810            handle_trap: func::<T>,
811            custom_trap,
812        };
813
814        compiler_fence(Ordering::Release);
815        let prev = TRAP_HANDLER.with(|ptr| {
816            let prev = ptr.load(Ordering::Relaxed);
817            ptr.store(&ctx as *const Self as *mut Self, Ordering::Relaxed);
818            prev
819        });
820
821        defer! {
822            TRAP_HANDLER.with(|ptr| ptr.store(prev, Ordering::Relaxed));
823            compiler_fence(Ordering::Acquire);
824        }
825
826        f()
827    }
828
829    /// Attempts to handle the trap if it's a wasm trap.
830    unsafe fn handle_trap(
831        pc: usize,
832        sp: usize,
833        maybe_fault_address: Option<usize>,
834        trap_code: Option<TrapCode>,
835        mut update_regs: impl FnMut(TrapHandlerRegs),
836        call_handler: impl Fn(&TrapHandlerFn<'static>) -> bool,
837    ) -> bool {
838        unsafe {
839            let ptr = TRAP_HANDLER.with(|ptr| ptr.load(Ordering::Relaxed));
840            if ptr.is_null() {
841                return false;
842            }
843
844            let ctx = &*ptr;
845
846            // Check if this trap is handled by a custom trap handler.
847            if let Some(trap_handler) = ctx.custom_trap
848                && call_handler(&*trap_handler)
849            {
850                return true;
851            }
852
853            (ctx.handle_trap)(
854                ctx.inner,
855                pc,
856                sp,
857                maybe_fault_address,
858                trap_code,
859                &mut update_regs,
860            )
861        }
862    }
863}
864
865impl<T> TrapHandlerContextInner<T> {
866    unsafe fn handle_trap(
867        &self,
868        pc: usize,
869        sp: usize,
870        maybe_fault_address: Option<usize>,
871        trap_code: Option<TrapCode>,
872        update_regs: &mut dyn FnMut(TrapHandlerRegs),
873    ) -> bool {
874        unsafe {
875            // Check if this trap occurred while executing on the Wasm stack. We can
876            // only recover from traps if that is the case.
877            if !self.coro_trap_handler.stack_ptr_in_bounds(sp) {
878                return false;
879            }
880
881            let signal_trap = trap_code.or_else(|| {
882                maybe_fault_address.map(|addr| {
883                    if self.coro_trap_handler.stack_ptr_in_bounds(addr) {
884                        TrapCode::StackOverflow
885                    } else {
886                        TrapCode::HeapAccessOutOfBounds
887                    }
888                })
889            });
890
891            // Don't try to generate a backtrace for stack overflows: unwinding
892            // information is often not precise enough to properly describe what is
893            // happenning during a function prologue, which can lead the unwinder to
894            // read invalid memory addresses.
895            //
896            // See: https://github.com/rust-lang/backtrace-rs/pull/357
897            let backtrace = if signal_trap == Some(TrapCode::StackOverflow) {
898                Backtrace::from(vec![])
899            } else {
900                Backtrace::new_unresolved()
901            };
902
903            // Set up the register state for exception return to force the
904            // coroutine to return to its caller with UnwindReason::WasmTrap.
905            let unwind = UnwindReason::WasmTrap {
906                backtrace,
907                signal_trap,
908                pc,
909            };
910            let regs = self
911                .coro_trap_handler
912                .setup_trap_handler(move || Err(unwind));
913            update_regs(regs);
914            true
915        }
916    }
917}
918
919enum UnwindReason {
920    /// A panic caused by the host
921    Panic(Box<dyn Any + Send>),
922    /// A custom error triggered by the user
923    UserTrap(Box<dyn Error + Send + Sync>),
924    /// A Trap triggered by a wasm libcall
925    LibTrap(Trap),
926    /// A trap caused by the Wasm generated code
927    WasmTrap {
928        backtrace: Backtrace,
929        pc: usize,
930        signal_trap: Option<TrapCode>,
931    },
932}
933
934impl UnwindReason {
935    fn into_trap(self) -> Trap {
936        match self {
937            Self::UserTrap(data) => Trap::User(data),
938            Self::LibTrap(trap) => trap,
939            Self::WasmTrap {
940                backtrace,
941                pc,
942                signal_trap,
943            } => Trap::wasm(pc, backtrace, signal_trap),
944            Self::Panic(panic) => std::panic::resume_unwind(panic),
945        }
946    }
947}
948
949unsafe fn unwind_with(reason: UnwindReason) -> ! {
950    unsafe {
951        let yielder = YIELDER
952            .with(|cell| cell.replace(None))
953            .expect("not running on Wasm stack");
954
955        yielder.as_ref().suspend(reason);
956
957        // on_wasm_stack will forcibly reset the coroutine stack after yielding.
958        unreachable!();
959    }
960}
961
962/// Runs the given function on a separate stack so that its stack usage can be
963/// bounded. Stack overflows and other traps can be caught and execution
964/// returned to the root of the stack.
965fn on_wasm_stack<F: FnOnce() -> T + 'static, T: 'static>(
966    stack_size: usize,
967    trap_handler: Option<*const TrapHandlerFn<'static>>,
968    f: F,
969) -> Result<T, UnwindReason> {
970    // Allocating a new stack is pretty expensive since it involves several
971    // system calls. We therefore keep a cache of pre-allocated stacks which
972    // allows them to be reused multiple times.
973    // FIXME(Amanieu): We should refactor this to avoid the lock.
974    static STACK_POOL: LazyLock<crossbeam_queue::SegQueue<DefaultStack>> =
975        LazyLock::new(crossbeam_queue::SegQueue::new);
976
977    let stack = STACK_POOL
978        .pop()
979        .unwrap_or_else(|| DefaultStack::new(stack_size).unwrap());
980    let mut stack = scopeguard::guard(stack, |stack| STACK_POOL.push(stack));
981
982    // Create a coroutine with a new stack to run the function on.
983    let coro = ScopedCoroutine::with_stack(&mut *stack, move |yielder, ()| {
984        // Save the yielder to TLS so that it can be used later.
985        YIELDER.with(|cell| cell.set(Some(yielder.into())));
986
987        Ok(f())
988    });
989
990    // Ensure that YIELDER is reset on exit even if the coroutine panics,
991    defer! {
992        YIELDER.with(|cell| cell.set(None));
993    }
994
995    coro.scope(|mut coro_ref| {
996        // Set up metadata for the trap handler for the duration of the coroutine
997        // execution. This is restored to its previous value afterwards.
998        TrapHandlerContext::install(trap_handler, coro_ref.trap_handler(), || {
999            match coro_ref.resume(()) {
1000                CoroutineResult::Yield(trap) => {
1001                    // This came from unwind_with which requires that there be only
1002                    // Wasm code on the stack.
1003                    unsafe {
1004                        coro_ref.force_reset();
1005                    }
1006                    Err(trap)
1007                }
1008                CoroutineResult::Return(result) => result,
1009            }
1010        })
1011    })
1012}
1013
1014/// When executing on the Wasm stack, temporarily switch back to the host stack
1015/// to perform an operation that should not be constrainted by the Wasm stack
1016/// limits.
1017///
1018/// This is particularly important since the usage of the Wasm stack is under
1019/// the control of untrusted code. Malicious code could artificially induce a
1020/// stack overflow in the middle of a sensitive host operations (e.g. growing
1021/// a memory) which would be hard to recover from.
1022pub fn on_host_stack<F: FnOnce() -> T, T>(f: F) -> T {
1023    // Reset YIEDER to None for the duration of this call to indicate that we
1024    // are no longer on the Wasm stack.
1025    let yielder_ptr = YIELDER.with(|cell| cell.replace(None));
1026
1027    // If we are already on the host stack, execute the function directly. This
1028    // happens if a host function is called directly from the API.
1029    let yielder = match yielder_ptr {
1030        Some(ptr) => unsafe { ptr.as_ref() },
1031        None => return f(),
1032    };
1033
1034    // Restore YIELDER upon exiting normally or unwinding.
1035    defer! {
1036        YIELDER.with(|cell| cell.set(yielder_ptr));
1037    }
1038
1039    // on_parent_stack requires the closure to be Send so that the Yielder
1040    // cannot be called from the parent stack. This is not a problem for us
1041    // since we don't expose the Yielder.
1042    struct SendWrapper<T>(T);
1043    unsafe impl<T> Send for SendWrapper<T> {}
1044    let wrapped = SendWrapper(f);
1045    yielder.on_parent_stack(move || {
1046        let wrapped = wrapped;
1047        (wrapped.0)()
1048    })
1049}
1050
1051#[cfg(windows)]
1052pub fn lazy_per_thread_init() -> Result<(), Trap> {
1053    // We need additional space on the stack to handle stack overflow
1054    // exceptions. Rust's initialization code sets this to 0x5000 but this
1055    // seems to be insufficient in practice.
1056    use windows_sys::Win32::System::Threading::SetThreadStackGuarantee;
1057    if unsafe { SetThreadStackGuarantee(&mut 0x10000) } == 0 {
1058        panic!("failed to set thread stack guarantee");
1059    }
1060
1061    Ok(())
1062}
1063
1064/// A module for registering a custom alternate signal stack (sigaltstack).
1065///
1066/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
1067/// always large enough for our signal handling code. Override it by creating
1068/// and registering our own alternate stack that is large enough and has a guard
1069/// page.
1070#[cfg(unix)]
1071pub fn lazy_per_thread_init() -> Result<(), Trap> {
1072    use std::ptr::null_mut;
1073
1074    thread_local! {
1075        /// Thread-local state is lazy-initialized on the first time it's used,
1076        /// and dropped when the thread exits.
1077        static TLS: Tls = unsafe { init_sigstack() };
1078    }
1079
1080    /// The size of the sigaltstack (not including the guard, which will be
1081    /// added). Make this large enough to run our signal handlers.
1082    const MIN_STACK_SIZE: usize = 16 * 4096;
1083
1084    enum Tls {
1085        OutOfMemory,
1086        Allocated {
1087            mmap_ptr: *mut libc::c_void,
1088            mmap_size: usize,
1089        },
1090        BigEnough,
1091    }
1092
1093    unsafe fn init_sigstack() -> Tls {
1094        unsafe {
1095            // Check to see if the existing sigaltstack, if it exists, is big
1096            // enough. If so we don't need to allocate our own.
1097            let mut old_stack = mem::zeroed();
1098            let r = libc::sigaltstack(ptr::null(), &mut old_stack);
1099            assert_eq!(r, 0, "learning about sigaltstack failed");
1100            if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
1101                return Tls::BigEnough;
1102            }
1103
1104            // ... but failing that we need to allocate our own, so do all that
1105            // here.
1106            let page_size: usize = region::page::size();
1107            let guard_size = page_size;
1108            let alloc_size = guard_size + MIN_STACK_SIZE;
1109
1110            let ptr = libc::mmap(
1111                null_mut(),
1112                alloc_size,
1113                libc::PROT_NONE,
1114                libc::MAP_PRIVATE | libc::MAP_ANON,
1115                -1,
1116                0,
1117            );
1118            if ptr == libc::MAP_FAILED {
1119                return Tls::OutOfMemory;
1120            }
1121
1122            // Prepare the stack with readable/writable memory and then register it
1123            // with `sigaltstack`.
1124            let stack_ptr = (ptr as usize + guard_size) as *mut libc::c_void;
1125            let r = libc::mprotect(
1126                stack_ptr,
1127                MIN_STACK_SIZE,
1128                libc::PROT_READ | libc::PROT_WRITE,
1129            );
1130            assert_eq!(r, 0, "mprotect to configure memory for sigaltstack failed");
1131            let new_stack = libc::stack_t {
1132                ss_sp: stack_ptr,
1133                ss_flags: 0,
1134                ss_size: MIN_STACK_SIZE,
1135            };
1136            let r = libc::sigaltstack(&new_stack, ptr::null_mut());
1137            assert_eq!(r, 0, "registering new sigaltstack failed");
1138
1139            Tls::Allocated {
1140                mmap_ptr: ptr,
1141                mmap_size: alloc_size,
1142            }
1143        }
1144    }
1145
1146    // Ensure TLS runs its initializer and return an error if it failed to
1147    // set up a separate stack for signal handlers.
1148    return TLS.with(|tls| {
1149        if let Tls::OutOfMemory = tls {
1150            Err(Trap::oom())
1151        } else {
1152            Ok(())
1153        }
1154    });
1155
1156    impl Drop for Tls {
1157        fn drop(&mut self) {
1158            let (ptr, size) = match self {
1159                Self::Allocated {
1160                    mmap_ptr,
1161                    mmap_size,
1162                } => (*mmap_ptr, *mmap_size),
1163                _ => return,
1164            };
1165            unsafe {
1166                // Deallocate the stack memory.
1167                let r = libc::munmap(ptr, size);
1168                debug_assert_eq!(r, 0, "munmap failed during thread shutdown");
1169            }
1170        }
1171    }
1172}