1#![allow(static_mut_refs)]
5
6#[cfg(all(unix, feature = "experimental-host-interrupt"))]
10use crate::interrupt_registry;
11use crate::vmcontext::{VMFunctionContext, VMTrampoline};
12use crate::{Trap, VMContext, VMFunctionBody};
13use backtrace::Backtrace;
14use bytesize::ByteSize;
15use core::ptr::{read, read_unaligned};
16use corosensei::stack::{DefaultStack, Stack};
17use corosensei::trap::{CoroutineTrapHandler, TrapHandlerRegs};
18use corosensei::{CoroutineResult, ScopedCoroutine, Yielder};
19use scopeguard::defer;
20use std::any::Any;
21use std::cell::Cell;
22use std::error::Error;
23use std::io;
24use std::mem;
25#[cfg(unix)]
26use std::mem::MaybeUninit;
27use std::ptr::{self, NonNull};
28use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering, compiler_fence};
29use std::sync::{LazyLock, Once};
30use wasmer_types::TrapCode;
31
32trait StackExt: Stack {
34 fn size(&self) -> usize {
36 self.base().get() - self.limit().get()
37 }
38}
39impl<T: Stack> StackExt for T {}
40
41pub struct VMConfig {
44 pub wasm_stack_size: Option<usize>,
46}
47
48static MAGIC: u8 = 0xc0;
52
53static DEFAULT_STACK_SIZE: AtomicUsize = AtomicUsize::new(ByteSize::mib(1).as_u64() as usize);
54
55pub const MAX_STACK_SIZE: usize = ByteSize::mib(100).as_u64() as usize;
58
59#[repr(C)]
62#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
63#[allow(non_camel_case_types)]
64struct ucontext_t {
65 uc_onstack: libc::c_int,
66 uc_sigmask: libc::sigset_t,
67 uc_stack: libc::stack_t,
68 uc_link: *mut libc::ucontext_t,
69 uc_mcsize: usize,
70 uc_mcontext: libc::mcontext_t,
71}
72
73#[cfg(all(unix, not(all(target_arch = "aarch64", target_os = "macos"))))]
74use libc::ucontext_t;
75
76pub fn set_stack_size(size: usize) {
79 DEFAULT_STACK_SIZE.store(
80 size.clamp(ByteSize::kib(8).as_u64() as usize, MAX_STACK_SIZE),
81 Ordering::Relaxed,
82 );
83}
84
85pub fn get_stack_size() -> usize {
87 DEFAULT_STACK_SIZE.load(Ordering::Relaxed)
88}
89
90static STACK_POOL: LazyLock<crossbeam_queue::SegQueue<DefaultStack>> =
92 LazyLock::new(crossbeam_queue::SegQueue::new);
93
94pub fn drain_stack_pool() {
107 while STACK_POOL.pop().is_some() {}
108}
109
110cfg_if::cfg_if! {
111 if #[cfg(unix)] {
112 pub type TrapHandlerFn<'a> = dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
114 } else if #[cfg(target_os = "windows")] {
115 pub type TrapHandlerFn<'a> = dyn Fn(*mut windows_sys::Win32::System::Diagnostics::Debug::EXCEPTION_POINTERS) -> bool + Send + Sync + 'a;
117 }
118}
119
120unsafe fn process_illegal_op(addr: usize) -> Option<TrapCode> {
122 let mut val: Option<u8> = None;
123 unsafe {
124 if cfg!(target_arch = "x86_64") {
125 val = if read(addr as *mut u8) & 0xf0 == 0x40
126 && read((addr + 1) as *mut u8) == 0x0f
127 && read((addr + 2) as *mut u8) == 0xb9
128 {
129 Some(read((addr + 3) as *mut u8))
130 } else if read(addr as *mut u8) == 0x0f && read((addr + 1) as *mut u8) == 0xb9 {
131 Some(read((addr + 2) as *mut u8))
132 } else {
133 None
134 }
135 }
136 if cfg!(target_arch = "aarch64") {
137 val = if read_unaligned(addr as *mut u32) & 0xffff0000 == 0 {
138 Some(read(addr as *mut u8))
139 } else {
140 None
141 }
142 }
143 if cfg!(target_arch = "riscv64") {
144 let addr = addr as *mut u32;
145 val = if read(addr) == 0xc0001073 {
147 let prev_insn = read(addr.sub(1));
150 if (prev_insn & 0xffff) == 0x0513 {
151 Some((prev_insn >> 20) as u8)
152 } else {
153 None
154 }
155 } else {
156 None
157 };
158 }
159 }
160
161 if cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64") {
163 val = val.and_then(|val| {
164 if val & MAGIC == MAGIC {
165 Some(val & 0xf)
166 } else {
167 None
168 }
169 });
170 }
171
172 match val {
173 None => None,
174 Some(val) => match val {
175 0 => Some(TrapCode::StackOverflow),
176 1 => Some(TrapCode::HeapAccessOutOfBounds),
177 2 => Some(TrapCode::HeapMisaligned),
178 3 => Some(TrapCode::TableAccessOutOfBounds),
179 4 => Some(TrapCode::IndirectCallToNull),
180 5 => Some(TrapCode::BadSignature),
181 6 => Some(TrapCode::IntegerOverflow),
182 7 => Some(TrapCode::IntegerDivisionByZero),
183 8 => Some(TrapCode::BadConversionToInteger),
184 9 => Some(TrapCode::UnreachableCodeReached),
185 10 => Some(TrapCode::UnalignedAtomic),
186 _ => None,
187 },
188 }
189}
190
191cfg_if::cfg_if! {
192 if #[cfg(unix)] {
193 static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
194 static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
195 static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
196 static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
197
198 #[cfg(feature = "experimental-host-interrupt")]
199 static mut PREV_SIGUSR1: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
200
201 unsafe fn platform_init() { unsafe {
202 let register = |slot: &mut MaybeUninit<libc::sigaction>, signal: i32, nodefer: bool| {
203 let mut handler: libc::sigaction = mem::zeroed();
204 handler.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
218 if nodefer {
219 handler.sa_flags |= libc::SA_NODEFER;
220 }
221 handler.sa_sigaction = trap_handler as *const () as usize;
222 libc::sigemptyset(&mut handler.sa_mask);
223 if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 {
224 panic!(
225 "unable to install signal handler: {}",
226 io::Error::last_os_error(),
227 );
228 }
229 };
230
231 register(&mut PREV_SIGSEGV, libc::SIGSEGV, true);
233
234 register(&mut PREV_SIGILL, libc::SIGILL, true);
236
237 #[cfg(feature = "experimental-host-interrupt")]
242 register(&mut PREV_SIGUSR1, libc::SIGUSR1, false);
243
244 if cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") {
246 register(&mut PREV_SIGFPE, libc::SIGFPE, true);
247 }
248
249 if cfg!(target_arch = "arm") || cfg!(target_vendor = "apple") {
252 register(&mut PREV_SIGBUS, libc::SIGBUS, true);
253 }
254
255 #[cfg(target_vendor = "apple")]
258 {
259 use mach2::exception_types::*;
260 use mach2::kern_return::*;
261 use mach2::port::*;
262 use mach2::thread_status::*;
263 use mach2::traps::*;
264 use mach2::mach_types::*;
265
266 unsafe extern "C" {
267 fn task_set_exception_ports(
268 task: task_t,
269 exception_mask: exception_mask_t,
270 new_port: mach_port_t,
271 behavior: exception_behavior_t,
272 new_flavor: thread_state_flavor_t,
273 ) -> kern_return_t;
274 }
275
276 #[allow(non_snake_case)]
277 #[cfg(target_arch = "x86_64")]
278 let MACHINE_THREAD_STATE = x86_THREAD_STATE64;
279 #[allow(non_snake_case)]
280 #[cfg(target_arch = "aarch64")]
281 let MACHINE_THREAD_STATE = 6;
282
283 task_set_exception_ports(
284 mach_task_self(),
285 EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC | EXC_MASK_BAD_INSTRUCTION,
286 MACH_PORT_NULL,
287 EXCEPTION_STATE_IDENTITY as exception_behavior_t,
288 MACHINE_THREAD_STATE,
289 );
290 }
291 }}
292
293 unsafe extern "C" fn trap_handler(
294 signum: libc::c_int,
295 siginfo: *mut libc::siginfo_t,
296 context: *mut libc::c_void,
297 ) { unsafe {
298 let previous = match signum {
299 libc::SIGSEGV => &PREV_SIGSEGV,
300 libc::SIGBUS => &PREV_SIGBUS,
301 libc::SIGFPE => &PREV_SIGFPE,
302 libc::SIGILL => &PREV_SIGILL,
303 #[cfg(feature = "experimental-host-interrupt")]
304 libc::SIGUSR1 => &PREV_SIGUSR1,
305 _ => panic!("unknown signal: {signum}"),
306 };
307 let maybe_fault_address = match signum {
309 libc::SIGSEGV | libc::SIGBUS => {
310 Some((*siginfo).si_addr() as usize)
311 }
312 _ => None,
313 };
314 let trap_code = match signum {
315 libc::SIGILL => {
317 let addr = (*siginfo).si_addr() as usize;
318 process_illegal_op(addr)
319 }
320 #[cfg(feature = "experimental-host-interrupt")]
321 libc::SIGUSR1 => {
322 if !interrupt_registry::on_interrupted() {
325 return;
326 }
327 Some(TrapCode::HostInterrupt)
328 }
329 _ => None,
330 };
331 let ucontext = &mut *(context as *mut ucontext_t);
332 let (pc, sp) = get_pc_sp(ucontext);
333 let handled = TrapHandlerContext::handle_trap(
334 pc,
335 sp,
336 maybe_fault_address,
337 trap_code,
338 |regs| update_context(ucontext, regs),
339 |handler| handler(signum, siginfo, context),
340 );
341
342 if handled {
343 return;
344 }
345
346 #[cfg(feature = "experimental-host-interrupt")]
349 if signum == libc::SIGUSR1 {
350 return;
351 }
352
353 let previous = &*previous.as_ptr();
363 if previous.sa_flags & libc::SA_SIGINFO != 0 {
364 mem::transmute::<
365 usize,
366 extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
367 >(previous.sa_sigaction)(signum, siginfo, context)
368 } else if previous.sa_sigaction == libc::SIG_DFL
369 {
370 libc::sigaction(signum, previous, ptr::null_mut());
371 } else if previous.sa_sigaction != libc::SIG_IGN {
372 mem::transmute::<usize, extern "C" fn(libc::c_int)>(
373 previous.sa_sigaction
374 )(signum)
375 }
376 }}
377
378 unsafe fn get_pc_sp(context: &ucontext_t) -> (usize, usize) {
379 let (pc, sp);
380 cfg_if::cfg_if! {
381 if #[cfg(all(
382 any(target_os = "linux", target_os = "android"),
383 target_arch = "x86_64",
384 ))] {
385 pc = context.uc_mcontext.gregs[libc::REG_RIP as usize] as usize;
386 sp = context.uc_mcontext.gregs[libc::REG_RSP as usize] as usize;
387 } else if #[cfg(all(
388 any(target_os = "linux", target_os = "android"),
389 target_arch = "x86",
390 ))] {
391 pc = context.uc_mcontext.gregs[libc::REG_EIP as usize] as usize;
392 sp = context.uc_mcontext.gregs[libc::REG_ESP as usize] as usize;
393 } else if #[cfg(all(target_os = "freebsd", target_arch = "x86"))] {
394 pc = context.uc_mcontext.mc_eip as usize;
395 sp = context.uc_mcontext.mc_esp as usize;
396 } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
397 pc = context.uc_mcontext.mc_rip as usize;
398 sp = context.uc_mcontext.mc_rsp as usize;
399 } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
400 let mcontext = unsafe { &*context.uc_mcontext };
401 pc = mcontext.__ss.__rip as usize;
402 sp = mcontext.__ss.__rsp as usize;
403 } else if #[cfg(all(
404 any(target_os = "linux", target_os = "android"),
405 target_arch = "aarch64",
406 ))] {
407 pc = context.uc_mcontext.pc as usize;
408 sp = context.uc_mcontext.sp as usize;
409 } else if #[cfg(all(
410 any(target_os = "linux", target_os = "android"),
411 target_arch = "arm",
412 ))] {
413 pc = context.uc_mcontext.arm_pc as usize;
414 sp = context.uc_mcontext.arm_sp as usize;
415 } else if #[cfg(all(
416 any(target_os = "linux", target_os = "android"),
417 any(target_arch = "riscv64", target_arch = "riscv32"),
418 ))] {
419 pc = context.uc_mcontext.__gregs[libc::REG_PC] as usize;
420 sp = context.uc_mcontext.__gregs[libc::REG_SP] as usize;
421 } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
422 let mcontext = unsafe { &*context.uc_mcontext };
423 pc = mcontext.__ss.__pc as usize;
424 sp = mcontext.__ss.__sp as usize;
425 } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
426 pc = context.uc_mcontext.mc_gpregs.gp_elr as usize;
427 sp = context.uc_mcontext.mc_gpregs.gp_sp as usize;
428 } else if #[cfg(all(target_os = "linux", target_arch = "loongarch64"))] {
429 pc = context.uc_mcontext.__gregs[1] as usize;
430 sp = context.uc_mcontext.__gregs[3] as usize;
431 } else if #[cfg(all(target_os = "linux", target_arch = "powerpc64"))] {
432 pc = (*context.uc_mcontext.regs).nip as usize;
433 sp = (*context.uc_mcontext.regs).gpr[1] as usize;
434 } else {
435 compile_error!("Unsupported platform");
436 }
437 };
438 (pc, sp)
439 }
440
441 unsafe fn update_context(context: &mut ucontext_t, regs: TrapHandlerRegs) {
442 cfg_if::cfg_if! {
443 if #[cfg(all(
444 any(target_os = "linux", target_os = "android"),
445 target_arch = "x86_64",
446 ))] {
447 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
448 context.uc_mcontext.gregs[libc::REG_RIP as usize] = rip as i64;
449 context.uc_mcontext.gregs[libc::REG_RSP as usize] = rsp as i64;
450 context.uc_mcontext.gregs[libc::REG_RBP as usize] = rbp as i64;
451 context.uc_mcontext.gregs[libc::REG_RDI as usize] = rdi as i64;
452 context.uc_mcontext.gregs[libc::REG_RSI as usize] = rsi as i64;
453 } else if #[cfg(all(
454 any(target_os = "linux", target_os = "android"),
455 target_arch = "x86",
456 ))] {
457 let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
458 context.uc_mcontext.gregs[libc::REG_EIP as usize] = eip as i32;
459 context.uc_mcontext.gregs[libc::REG_ESP as usize] = esp as i32;
460 context.uc_mcontext.gregs[libc::REG_EBP as usize] = ebp as i32;
461 context.uc_mcontext.gregs[libc::REG_ECX as usize] = ecx as i32;
462 context.uc_mcontext.gregs[libc::REG_EDX as usize] = edx as i32;
463 } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
464 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
465 let mcontext = unsafe { &mut *context.uc_mcontext };
466 mcontext.__ss.__rip = rip;
467 mcontext.__ss.__rsp = rsp;
468 mcontext.__ss.__rbp = rbp;
469 mcontext.__ss.__rdi = rdi;
470 mcontext.__ss.__rsi = rsi;
471 } else if #[cfg(all(target_os = "freebsd", target_arch = "x86"))] {
472 let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
473 context.uc_mcontext.mc_eip = eip as libc::register_t;
474 context.uc_mcontext.mc_esp = esp as libc::register_t;
475 context.uc_mcontext.mc_ebp = ebp as libc::register_t;
476 context.uc_mcontext.mc_ecx = ecx as libc::register_t;
477 context.uc_mcontext.mc_edx = edx as libc::register_t;
478 } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
479 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
480 context.uc_mcontext.mc_rip = rip as libc::register_t;
481 context.uc_mcontext.mc_rsp = rsp as libc::register_t;
482 context.uc_mcontext.mc_rbp = rbp as libc::register_t;
483 context.uc_mcontext.mc_rdi = rdi as libc::register_t;
484 context.uc_mcontext.mc_rsi = rsi as libc::register_t;
485 } else if #[cfg(all(
486 any(target_os = "linux", target_os = "android"),
487 target_arch = "aarch64",
488 ))] {
489 let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
490 context.uc_mcontext.pc = pc;
491 context.uc_mcontext.sp = sp;
492 context.uc_mcontext.regs[0] = x0;
493 context.uc_mcontext.regs[1] = x1;
494 context.uc_mcontext.regs[29] = x29;
495 context.uc_mcontext.regs[30] = lr;
496 } else if #[cfg(all(
497 any(target_os = "linux", target_os = "android"),
498 target_arch = "arm",
499 ))] {
500 let TrapHandlerRegs {
501 pc,
502 r0,
503 r1,
504 r7,
505 r11,
506 r13,
507 r14,
508 cpsr_thumb,
509 cpsr_endian,
510 } = regs;
511 context.uc_mcontext.arm_pc = pc;
512 context.uc_mcontext.arm_r0 = r0;
513 context.uc_mcontext.arm_r1 = r1;
514 context.uc_mcontext.arm_r7 = r7;
515 context.uc_mcontext.arm_fp = r11;
516 context.uc_mcontext.arm_sp = r13;
517 context.uc_mcontext.arm_lr = r14;
518 if cpsr_thumb {
519 context.uc_mcontext.arm_cpsr |= 0x20;
520 } else {
521 context.uc_mcontext.arm_cpsr &= !0x20;
522 }
523 if cpsr_endian {
524 context.uc_mcontext.arm_cpsr |= 0x200;
525 } else {
526 context.uc_mcontext.arm_cpsr &= !0x200;
527 }
528 } else if #[cfg(all(
529 any(target_os = "linux", target_os = "android"),
530 any(target_arch = "riscv64", target_arch = "riscv32"),
531 ))] {
532 let TrapHandlerRegs { pc, ra, sp, a0, a1, s0 } = regs;
533 context.uc_mcontext.__gregs[libc::REG_PC] = pc as libc::c_ulong;
534 context.uc_mcontext.__gregs[libc::REG_RA] = ra as libc::c_ulong;
535 context.uc_mcontext.__gregs[libc::REG_SP] = sp as libc::c_ulong;
536 context.uc_mcontext.__gregs[libc::REG_A0] = a0 as libc::c_ulong;
537 context.uc_mcontext.__gregs[libc::REG_A0 + 1] = a1 as libc::c_ulong;
538 context.uc_mcontext.__gregs[libc::REG_S0] = s0 as libc::c_ulong;
539 } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
540 let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
541 let mcontext = unsafe { &mut *context.uc_mcontext };
542 mcontext.__ss.__pc = pc;
543 mcontext.__ss.__sp = sp;
544 mcontext.__ss.__x[0] = x0;
545 mcontext.__ss.__x[1] = x1;
546 mcontext.__ss.__fp = x29;
547 mcontext.__ss.__lr = lr;
548 } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
549 let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
550 context.uc_mcontext.mc_gpregs.gp_elr = pc as libc::register_t;
551 context.uc_mcontext.mc_gpregs.gp_sp = sp as libc::register_t;
552 context.uc_mcontext.mc_gpregs.gp_x[0] = x0 as libc::register_t;
553 context.uc_mcontext.mc_gpregs.gp_x[1] = x1 as libc::register_t;
554 context.uc_mcontext.mc_gpregs.gp_x[29] = x29 as libc::register_t;
555 context.uc_mcontext.mc_gpregs.gp_lr = lr as libc::register_t;
556 } else if #[cfg(all(target_os = "linux", target_arch = "loongarch64"))] {
557 let TrapHandlerRegs { pc, sp, a0, a1, fp, ra } = regs;
558 context.uc_mcontext.__pc = pc;
559 context.uc_mcontext.__gregs[1] = ra;
560 context.uc_mcontext.__gregs[3] = sp;
561 context.uc_mcontext.__gregs[4] = a0;
562 context.uc_mcontext.__gregs[5] = a1;
563 context.uc_mcontext.__gregs[22] = fp;
564 } else if #[cfg(all(target_os = "linux", target_arch = "powerpc64"))] {
565 let TrapHandlerRegs { pc, sp, r3, r4, r31, lr } = regs;
566 (*context.uc_mcontext.regs).nip = pc;
567 (*context.uc_mcontext.regs).gpr[1] = sp;
568 (*context.uc_mcontext.regs).gpr[3] = r3;
569 (*context.uc_mcontext.regs).gpr[4] = r4;
570 (*context.uc_mcontext.regs).gpr[31] = r31;
571 (*context.uc_mcontext.regs).link = lr;
572 } else {
573 compile_error!("Unsupported platform");
574 }
575 };
576 }
577 } else if #[cfg(target_os = "windows")] {
578 use windows_sys::Win32::System::Diagnostics::Debug::{
579 AddVectoredExceptionHandler,
580 CONTEXT,
581 EXCEPTION_CONTINUE_EXECUTION,
582 EXCEPTION_CONTINUE_SEARCH,
583 EXCEPTION_POINTERS,
584 };
585 use windows_sys::Win32::Foundation::{
586 EXCEPTION_ACCESS_VIOLATION,
587 EXCEPTION_ILLEGAL_INSTRUCTION,
588 EXCEPTION_INT_DIVIDE_BY_ZERO,
589 EXCEPTION_INT_OVERFLOW,
590 EXCEPTION_STACK_OVERFLOW,
591 };
592
593 unsafe fn platform_init() {
594 unsafe {
595 let handler = AddVectoredExceptionHandler(1, Some(exception_handler));
599 if handler.is_null() {
600 panic!("failed to add exception handler: {}", io::Error::last_os_error());
601 }
602 }
603 }
604
605 unsafe extern "system" fn exception_handler(
606 exception_info: *mut EXCEPTION_POINTERS
607 ) -> i32 {
608 unsafe {
609 let record = &*(*exception_info).ExceptionRecord;
613 if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION &&
614 record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION &&
615 record.ExceptionCode != EXCEPTION_STACK_OVERFLOW &&
616 record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO &&
617 record.ExceptionCode != EXCEPTION_INT_OVERFLOW
618 {
619 return EXCEPTION_CONTINUE_SEARCH;
620 }
621
622 let context = &mut *(*exception_info).ContextRecord;
635 let (pc, sp) = get_pc_sp(context);
636
637 let maybe_fault_address = match record.ExceptionCode {
639 EXCEPTION_ACCESS_VIOLATION => Some(record.ExceptionInformation[1]),
640 EXCEPTION_STACK_OVERFLOW => Some(sp),
641 _ => None,
642 };
643 let trap_code = match record.ExceptionCode {
644 EXCEPTION_ILLEGAL_INSTRUCTION => {
646 process_illegal_op(pc)
647 }
648 _ => None,
649 };
650 let handled = TrapHandlerContext::handle_trap(
653 pc,
654 sp,
655 maybe_fault_address,
656 trap_code,
657 |regs| update_context(context, regs),
658 |handler| handler(exception_info),
659 );
660
661 if handled {
662 EXCEPTION_CONTINUE_EXECUTION
663 } else {
664 EXCEPTION_CONTINUE_SEARCH
665 }
666 }
667 }
668
669 unsafe fn get_pc_sp(context: &CONTEXT) -> (usize, usize) {
670 let (pc, sp);
671 cfg_if::cfg_if! {
672 if #[cfg(target_arch = "x86_64")] {
673 pc = context.Rip as usize;
674 sp = context.Rsp as usize;
675 } else if #[cfg(target_arch = "x86")] {
676 pc = context.Eip as usize;
677 sp = context.Esp as usize;
678 } else {
679 compile_error!("Unsupported platform");
680 }
681 };
682 (pc, sp)
683 }
684
685 unsafe fn update_context(context: &mut CONTEXT, regs: TrapHandlerRegs) {
686 cfg_if::cfg_if! {
687 if #[cfg(target_arch = "x86_64")] {
688 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
689 context.Rip = rip;
690 context.Rsp = rsp;
691 context.Rbp = rbp;
692 context.Rdi = rdi;
693 context.Rsi = rsi;
694 } else if #[cfg(target_arch = "x86")] {
695 let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
696 context.Eip = eip;
697 context.Esp = esp;
698 context.Ebp = ebp;
699 context.Ecx = ecx;
700 context.Edx = edx;
701 } else {
702 compile_error!("Unsupported platform");
703 }
704 };
705 }
706 }
707}
708
709pub fn init_traps() {
718 static INIT: Once = Once::new();
719 INIT.call_once(|| unsafe {
720 platform_init();
721 });
722}
723
724pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
737 unsafe { unwind_with(UnwindReason::UserTrap(data)) }
738}
739
740pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
752 unsafe { unwind_with(UnwindReason::LibTrap(trap)) }
753}
754
755pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
764 unsafe { unwind_with(UnwindReason::Panic(payload)) }
765}
766
767pub unsafe fn wasmer_call_trampoline(
783 trap_handler: Option<*const TrapHandlerFn<'static>>,
784 config: &VMConfig,
785 vmctx: VMFunctionContext,
786 trampoline: VMTrampoline,
787 callee: *const VMFunctionBody,
788 values_vec: *mut u8,
789) -> Result<(), Trap> {
790 unsafe {
791 catch_traps(trap_handler, config, move || {
792 mem::transmute::<
793 unsafe extern "C" fn(
794 *mut VMContext,
795 *const VMFunctionBody,
796 *mut wasmer_types::RawValue,
797 ),
798 extern "C" fn(VMFunctionContext, *const VMFunctionBody, *mut u8),
799 >(trampoline)(vmctx, callee, values_vec);
800 })
801 }
802}
803
804pub unsafe fn catch_traps<F, R: 'static>(
811 trap_handler: Option<*const TrapHandlerFn<'static>>,
812 config: &VMConfig,
813 closure: F,
814) -> Result<R, Trap>
815where
816 F: FnOnce() -> R + 'static,
817{
818 lazy_per_thread_init()?;
820 let stack_size = config
821 .wasm_stack_size
822 .unwrap_or_else(|| DEFAULT_STACK_SIZE.load(Ordering::Relaxed));
823 on_wasm_stack(stack_size, trap_handler, closure).map_err(UnwindReason::into_trap)
824}
825
826thread_local! {
835 static YIELDER: Cell<Option<NonNull<Yielder<(), UnwindReason>>>> = const { Cell::new(None) };
836 static TRAP_HANDLER: AtomicPtr<TrapHandlerContext> = const { AtomicPtr::new(ptr::null_mut()) };
837}
838
839#[allow(clippy::type_complexity)]
842struct TrapHandlerContext {
843 inner: *const u8,
844 handle_trap: fn(
845 *const u8,
846 usize,
847 usize,
848 Option<usize>,
849 Option<TrapCode>,
850 &mut dyn FnMut(TrapHandlerRegs),
851 ) -> bool,
852 custom_trap: Option<*const TrapHandlerFn<'static>>,
853}
854struct TrapHandlerContextInner<T> {
855 coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
858}
859
860impl TrapHandlerContext {
861 fn install<T, R>(
864 custom_trap: Option<*const TrapHandlerFn<'static>>,
865 coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
866 f: impl FnOnce() -> R,
867 ) -> R {
868 fn func<T>(
870 ptr: *const u8,
871 pc: usize,
872 sp: usize,
873 maybe_fault_address: Option<usize>,
874 trap_code: Option<TrapCode>,
875 update_regs: &mut dyn FnMut(TrapHandlerRegs),
876 ) -> bool {
877 unsafe {
878 (*(ptr as *const TrapHandlerContextInner<T>)).handle_trap(
879 pc,
880 sp,
881 maybe_fault_address,
882 trap_code,
883 update_regs,
884 )
885 }
886 }
887 let inner = TrapHandlerContextInner { coro_trap_handler };
888 let ctx = Self {
889 inner: &inner as *const _ as *const u8,
890 handle_trap: func::<T>,
891 custom_trap,
892 };
893
894 compiler_fence(Ordering::Release);
895 let prev = TRAP_HANDLER.with(|ptr| {
896 let prev = ptr.load(Ordering::Relaxed);
897 ptr.store(&ctx as *const Self as *mut Self, Ordering::Relaxed);
898 prev
899 });
900
901 defer! {
902 TRAP_HANDLER.with(|ptr| ptr.store(prev, Ordering::Relaxed));
903 compiler_fence(Ordering::Acquire);
904 }
905
906 f()
907 }
908
909 unsafe fn handle_trap(
911 pc: usize,
912 sp: usize,
913 maybe_fault_address: Option<usize>,
914 trap_code: Option<TrapCode>,
915 mut update_regs: impl FnMut(TrapHandlerRegs),
916 call_handler: impl Fn(&TrapHandlerFn<'static>) -> bool,
917 ) -> bool {
918 unsafe {
919 let ptr = TRAP_HANDLER.with(|ptr| ptr.load(Ordering::Relaxed));
920 if ptr.is_null() {
921 return false;
922 }
923
924 let ctx = &*ptr;
925
926 if let Some(trap_handler) = ctx.custom_trap
928 && call_handler(&*trap_handler)
929 {
930 return true;
931 }
932
933 (ctx.handle_trap)(
934 ctx.inner,
935 pc,
936 sp,
937 maybe_fault_address,
938 trap_code,
939 &mut update_regs,
940 )
941 }
942 }
943}
944
945impl<T> TrapHandlerContextInner<T> {
946 unsafe fn handle_trap(
947 &self,
948 pc: usize,
949 sp: usize,
950 maybe_fault_address: Option<usize>,
951 trap_code: Option<TrapCode>,
952 update_regs: &mut dyn FnMut(TrapHandlerRegs),
953 ) -> bool {
954 unsafe {
955 if !self.coro_trap_handler.stack_ptr_in_bounds(sp) {
958 return false;
959 }
960
961 let signal_trap = trap_code.or_else(|| {
962 maybe_fault_address.map(|addr| {
963 if self.coro_trap_handler.stack_ptr_in_bounds(addr) {
964 TrapCode::StackOverflow
965 } else {
966 TrapCode::HeapAccessOutOfBounds
967 }
968 })
969 });
970
971 let backtrace = if signal_trap == Some(TrapCode::StackOverflow) {
978 Backtrace::from(vec![])
979 } else {
980 Backtrace::new_unresolved()
981 };
982
983 let unwind = UnwindReason::WasmTrap {
986 backtrace,
987 signal_trap,
988 pc,
989 };
990 let regs = self
991 .coro_trap_handler
992 .setup_trap_handler(move || Err(unwind));
993 update_regs(regs);
994 true
995 }
996 }
997}
998
999enum UnwindReason {
1000 Panic(Box<dyn Any + Send>),
1002 UserTrap(Box<dyn Error + Send + Sync>),
1004 LibTrap(Trap),
1006 WasmTrap {
1008 backtrace: Backtrace,
1009 pc: usize,
1010 signal_trap: Option<TrapCode>,
1011 },
1012}
1013
1014impl UnwindReason {
1015 fn into_trap(self) -> Trap {
1016 match self {
1017 Self::UserTrap(data) => Trap::User(data),
1018 Self::LibTrap(trap) => trap,
1019 Self::WasmTrap {
1020 backtrace,
1021 pc,
1022 signal_trap,
1023 } => Trap::wasm(pc, backtrace, signal_trap),
1024 Self::Panic(panic) => std::panic::resume_unwind(panic),
1025 }
1026 }
1027}
1028
1029unsafe fn unwind_with(reason: UnwindReason) -> ! {
1030 unsafe {
1031 let yielder = YIELDER
1032 .with(|cell| cell.replace(None))
1033 .expect("not running on Wasm stack");
1034
1035 yielder.as_ref().suspend(reason);
1036
1037 unreachable!();
1039 }
1040}
1041
1042fn on_wasm_stack<F: FnOnce() -> T + 'static, T: 'static>(
1046 stack_size: usize,
1047 trap_handler: Option<*const TrapHandlerFn<'static>>,
1048 f: F,
1049) -> Result<T, UnwindReason> {
1050 let stack = STACK_POOL
1057 .pop()
1058 .filter(|s| s.size() >= stack_size)
1059 .unwrap_or_else(|| DefaultStack::new(stack_size).unwrap());
1060 let mut stack = scopeguard::guard(stack, |stack| STACK_POOL.push(stack));
1061
1062 let coro = ScopedCoroutine::with_stack(&mut *stack, move |yielder, ()| {
1064 YIELDER.with(|cell| cell.set(Some(yielder.into())));
1066
1067 Ok(f())
1068 });
1069
1070 defer! {
1072 YIELDER.with(|cell| cell.set(None));
1073 }
1074
1075 coro.scope(|mut coro_ref| {
1076 TrapHandlerContext::install(trap_handler, coro_ref.trap_handler(), || {
1079 match coro_ref.resume(()) {
1080 CoroutineResult::Yield(trap) => {
1081 unsafe {
1084 coro_ref.force_reset();
1085 }
1086 Err(trap)
1087 }
1088 CoroutineResult::Return(result) => result,
1089 }
1090 })
1091 })
1092}
1093
1094pub fn on_host_stack<F: FnOnce() -> T, T>(f: F) -> T {
1103 let yielder_ptr = YIELDER.with(|cell| cell.replace(None));
1106
1107 let yielder = match yielder_ptr {
1110 Some(ptr) => unsafe { ptr.as_ref() },
1111 None => return f(),
1112 };
1113
1114 defer! {
1116 YIELDER.with(|cell| cell.set(yielder_ptr));
1117 }
1118
1119 struct SendWrapper<T>(T);
1123 unsafe impl<T> Send for SendWrapper<T> {}
1124 let wrapped = SendWrapper(f);
1125 yielder.on_parent_stack(move || {
1126 let wrapped = wrapped;
1127 (wrapped.0)()
1128 })
1129}
1130
1131#[cfg(windows)]
1132pub fn lazy_per_thread_init() -> Result<(), Trap> {
1133 use windows_sys::Win32::System::Threading::SetThreadStackGuarantee;
1137 if unsafe { SetThreadStackGuarantee(&mut 0x10000) } == 0 {
1138 panic!("failed to set thread stack guarantee");
1139 }
1140
1141 Ok(())
1142}
1143
1144#[cfg(unix)]
1151pub fn lazy_per_thread_init() -> Result<(), Trap> {
1152 use std::ptr::null_mut;
1153
1154 thread_local! {
1155 static TLS: Tls = unsafe { init_sigstack() };
1158 }
1159
1160 const MIN_STACK_SIZE: usize = ByteSize::kib(64).as_u64() as usize;
1163
1164 enum Tls {
1165 OutOfMemory,
1166 Allocated {
1167 mmap_ptr: *mut libc::c_void,
1168 mmap_size: usize,
1169 },
1170 BigEnough,
1171 }
1172
1173 unsafe fn init_sigstack() -> Tls {
1174 unsafe {
1175 let mut old_stack = mem::zeroed();
1178 let r = libc::sigaltstack(ptr::null(), &mut old_stack);
1179 assert_eq!(r, 0, "learning about sigaltstack failed");
1180 if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
1181 return Tls::BigEnough;
1182 }
1183
1184 let page_size: usize = region::page::size();
1187 let guard_size = page_size;
1188 let alloc_size = guard_size + MIN_STACK_SIZE;
1189
1190 let ptr = libc::mmap(
1191 null_mut(),
1192 alloc_size,
1193 libc::PROT_NONE,
1194 libc::MAP_PRIVATE | libc::MAP_ANON,
1195 -1,
1196 0,
1197 );
1198 if ptr == libc::MAP_FAILED {
1199 return Tls::OutOfMemory;
1200 }
1201
1202 let stack_ptr = (ptr as usize + guard_size) as *mut libc::c_void;
1205 let r = libc::mprotect(
1206 stack_ptr,
1207 MIN_STACK_SIZE,
1208 libc::PROT_READ | libc::PROT_WRITE,
1209 );
1210 assert_eq!(r, 0, "mprotect to configure memory for sigaltstack failed");
1211 let new_stack = libc::stack_t {
1212 ss_sp: stack_ptr,
1213 ss_flags: 0,
1214 ss_size: MIN_STACK_SIZE,
1215 };
1216 let r = libc::sigaltstack(&new_stack, ptr::null_mut());
1217 assert_eq!(r, 0, "registering new sigaltstack failed");
1218
1219 Tls::Allocated {
1220 mmap_ptr: ptr,
1221 mmap_size: alloc_size,
1222 }
1223 }
1224 }
1225
1226 return TLS.with(|tls| {
1229 if let Tls::OutOfMemory = tls {
1230 Err(Trap::oom())
1231 } else {
1232 Ok(())
1233 }
1234 });
1235
1236 impl Drop for Tls {
1237 fn drop(&mut self) {
1238 let (ptr, size) = match self {
1239 Self::Allocated {
1240 mmap_ptr,
1241 mmap_size,
1242 } => (*mmap_ptr, *mmap_size),
1243 _ => return,
1244 };
1245 unsafe {
1246 let r = libc::munmap(ptr, size);
1248 debug_assert_eq!(r, 0, "munmap failed during thread shutdown");
1249 }
1250 }
1251 }
1252}
1253
1254#[cfg(test)]
1255mod tests {
1256 use super::*;
1257 use std::sync::Mutex;
1258
1259 static GLOBAL_STATE: Mutex<()> = Mutex::new(());
1263
1264 struct RestoreStackSize(usize);
1266 impl Drop for RestoreStackSize {
1267 fn drop(&mut self) {
1268 set_stack_size(self.0);
1269 }
1270 }
1271
1272 #[test]
1273 fn max_stack_size_is_100mb() {
1274 assert_eq!(MAX_STACK_SIZE, ByteSize::mib(100).as_u64() as usize);
1275 }
1276
1277 #[test]
1278 fn get_set_stack_size_roundtrip() {
1279 let _lock = GLOBAL_STATE.lock().unwrap();
1280 let _restore = RestoreStackSize(get_stack_size());
1281 let new_size = ByteSize::mib(4).as_u64() as usize;
1282 set_stack_size(new_size);
1283 assert_eq!(get_stack_size(), new_size);
1284 }
1285
1286 #[test]
1287 fn set_stack_size_clamps_to_min() {
1288 let _lock = GLOBAL_STATE.lock().unwrap();
1289 let _restore = RestoreStackSize(get_stack_size());
1290 set_stack_size(1); assert_eq!(get_stack_size(), ByteSize::kib(8).as_u64() as usize);
1292 }
1293
1294 #[test]
1295 fn set_stack_size_clamps_to_max() {
1296 let _lock = GLOBAL_STATE.lock().unwrap();
1297 let _restore = RestoreStackSize(get_stack_size());
1298 set_stack_size(usize::MAX);
1299 assert_eq!(get_stack_size(), MAX_STACK_SIZE);
1300 }
1301
1302 #[test]
1303 fn drain_stack_pool_empties_pool() {
1304 let _lock = GLOBAL_STATE.lock().unwrap();
1305 let stack = DefaultStack::new(ByteSize::mib(1).as_u64() as usize).unwrap();
1306 STACK_POOL.push(stack);
1307 assert!(!STACK_POOL.is_empty());
1308 drain_stack_pool();
1309 assert!(STACK_POOL.is_empty());
1310 }
1311
1312 #[test]
1313 fn drain_stack_pool_is_idempotent() {
1314 let _lock = GLOBAL_STATE.lock().unwrap();
1315 drain_stack_pool();
1316 drain_stack_pool(); assert!(STACK_POOL.is_empty());
1318 }
1319
1320 #[test]
1330 fn pool_returns_stale_stack_without_drain() {
1331 let _lock = GLOBAL_STATE.lock().unwrap();
1332 let _restore = RestoreStackSize(get_stack_size());
1333 drain_stack_pool();
1334
1335 let small_size = ByteSize::kib(500).as_u64() as usize;
1337 let small_stack = DefaultStack::new(small_size).unwrap();
1338 STACK_POOL.push(small_stack);
1339
1340 let big_size = ByteSize::mib(1).as_u64() as usize;
1342 set_stack_size(big_size);
1343 assert_eq!(get_stack_size(), big_size);
1344
1345 let stale = STACK_POOL.pop();
1349 assert!(
1350 stale.is_some(),
1351 "pool should still contain the old stack (the bug scenario)"
1352 );
1353
1354 STACK_POOL.push(stale.unwrap());
1356 drain_stack_pool();
1357 assert!(
1358 STACK_POOL.pop().is_none(),
1359 "after drain, pool must be empty so a fresh stack is allocated at the new size"
1360 );
1361 }
1362
1363 #[test]
1366 fn on_wasm_stack_discards_undersized_stack() {
1367 let _lock = GLOBAL_STATE.lock().unwrap();
1368 let _restore = RestoreStackSize(get_stack_size());
1369 drain_stack_pool();
1370
1371 let small_size = ByteSize::kib(500).as_u64() as usize;
1373 let small_stack = DefaultStack::new(small_size).unwrap();
1374 STACK_POOL.push(small_stack);
1375
1376 let big_size = ByteSize::mib(1).as_u64() as usize;
1378 let result = on_wasm_stack(big_size, None, || 42);
1379
1380 assert_eq!(result.ok().expect("on_wasm_stack should succeed"), 42);
1381 let returned = STACK_POOL
1384 .pop()
1385 .expect("stack should have been returned to pool");
1386 assert!(
1387 returned.size() >= big_size,
1388 "returned stack must be at least as large as the requested size"
1389 );
1390 }
1391}