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