use crate::prelude::*;
use crate::runtime::vm::traphandlers::{TrapRegisters, TrapTest, tls};
use std::cell::RefCell;
use std::io;
use std::mem;
use std::ptr::{self, null_mut};
use wasmtime_unwinder::Handler;
pub type SignalHandler =
Box<dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync>;
const UNINIT_SIGACTION: libc::sigaction = unsafe { mem::zeroed() };
static mut PREV_SIGSEGV: libc::sigaction = UNINIT_SIGACTION;
static mut PREV_SIGBUS: libc::sigaction = UNINIT_SIGACTION;
static mut PREV_SIGILL: libc::sigaction = UNINIT_SIGACTION;
static mut PREV_SIGFPE: libc::sigaction = UNINIT_SIGACTION;
pub struct TrapHandler;
impl TrapHandler {
pub unsafe fn new(macos_use_mach_ports: bool) -> TrapHandler {
assert!(!macos_use_mach_ports || !cfg!(target_vendor = "apple"));
foreach_handler(|slot, signal| {
let mut handler: libc::sigaction = unsafe { mem::zeroed() };
handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
handler.sa_sigaction = (trap_handler as *const ()).addr();
unsafe {
libc::sigemptyset(&mut handler.sa_mask);
if libc::sigaction(signal, &handler, slot) != 0 {
panic!(
"unable to install signal handler: {}",
io::Error::last_os_error(),
);
}
}
});
TrapHandler
}
pub fn validate_config(&self, macos_use_mach_ports: bool) {
assert!(!macos_use_mach_ports || !cfg!(target_vendor = "apple"));
}
}
fn foreach_handler(mut f: impl FnMut(*mut libc::sigaction, i32)) {
f(&raw mut PREV_SIGSEGV, libc::SIGSEGV);
f(&raw mut PREV_SIGILL, libc::SIGILL);
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
f(&raw mut PREV_SIGFPE, libc::SIGFPE);
}
if cfg!(target_vendor = "apple") || cfg!(target_os = "freebsd") {
f(&raw mut PREV_SIGBUS, libc::SIGBUS);
}
}
impl Drop for TrapHandler {
fn drop(&mut self) {
unsafe {
foreach_handler(|slot, signal| {
let mut prev: libc::sigaction = mem::zeroed();
if libc::sigaction(signal, slot, &mut prev) != 0 {
eprintln!(
"unable to reinstall signal handler: {}",
io::Error::last_os_error(),
);
libc::abort();
}
if prev.sa_sigaction != (trap_handler as *const ()).addr() {
eprintln!(
"
Wasmtime's signal handler was not the last signal handler to be installed
in the process so it's not certain how to unload signal handlers. In this
situation the Engine::unload_process_handlers API is not applicable and requires
perhaps initializing libraries in a different order. The process will be aborted
now.
"
);
libc::abort();
}
});
}
}
}
unsafe extern "C" fn trap_handler(
signum: libc::c_int,
siginfo: *mut libc::siginfo_t,
context: *mut libc::c_void,
) {
let previous = match signum {
libc::SIGSEGV => &raw const PREV_SIGSEGV,
libc::SIGBUS => &raw const PREV_SIGBUS,
libc::SIGFPE => &raw const PREV_SIGFPE,
libc::SIGILL => &raw const PREV_SIGILL,
_ => panic!("unknown signal: {signum}"),
};
let handled = tls::with(|info| {
let info = match info {
Some(info) => info,
None => return false,
};
let faulting_addr = match signum {
libc::SIGSEGV | libc::SIGBUS => unsafe { Some((*siginfo).si_addr() as usize) },
_ => None,
};
let regs = unsafe { get_trap_registers(context, signum) };
let test = info.test_if_trap(regs, faulting_addr, |handler| {
handler(signum, siginfo, context)
});
match test {
TrapTest::NotWasm => {
if let Some(faulting_addr) = faulting_addr {
let range = unsafe { &info.vm_store_context.get().as_ref().async_guard_range };
if range.start.addr() <= faulting_addr && faulting_addr < range.end.addr() {
abort_stack_overflow();
}
}
false
}
TrapTest::HandledByEmbedder => true,
TrapTest::Trap(handler) => {
unsafe {
store_handler_in_ucontext(context, &handler);
}
true
}
}
});
if handled {
return;
}
unsafe { delegate_signal_to_previous_handler(previous, signum, siginfo, context) }
}
pub unsafe fn delegate_signal_to_previous_handler(
previous: *const libc::sigaction,
signum: libc::c_int,
siginfo: *mut libc::siginfo_t,
context: *mut libc::c_void,
) {
unsafe {
let previous = *previous;
if previous.sa_flags & libc::SA_SIGINFO != 0 {
mem::transmute::<
usize,
extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
>(previous.sa_sigaction)(signum, siginfo, context)
} else if previous.sa_sigaction == libc::SIG_DFL || previous.sa_sigaction == libc::SIG_IGN {
libc::sigaction(signum, &previous as *const _, ptr::null_mut());
} else {
mem::transmute::<usize, extern "C" fn(libc::c_int)>(previous.sa_sigaction)(signum)
}
}
}
pub fn abort_stack_overflow() -> ! {
unsafe {
let msg = "execution on async fiber has overflowed its stack";
libc::write(libc::STDERR_FILENO, msg.as_ptr().cast(), msg.len());
libc::abort();
}
}
#[allow(
clippy::cast_possible_truncation,
reason = "too fiddly to handle and wouldn't help much anyway"
)]
unsafe fn get_trap_registers(cx: *mut libc::c_void, _signum: libc::c_int) -> TrapRegisters {
cfg_if::cfg_if! {
if #[cfg(all(any(target_os = "linux", target_os = "android", target_os = "illumos"), target_arch = "x86_64"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.uc_mcontext.gregs[libc::REG_RIP as usize] as usize,
fp: cx.uc_mcontext.gregs[libc::REG_RBP as usize] as usize,
}
} else if #[cfg(all(target_os = "linux", target_arch = "x86"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.uc_mcontext.gregs[libc::REG_EIP as usize] as usize,
fp: cx.uc_mcontext.gregs[libc::REG_EBP as usize] as usize,
}
} else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.uc_mcontext.pc as usize,
fp: cx.uc_mcontext.regs[29] as usize,
}
} else if #[cfg(all(target_os = "linux", target_arch = "s390x"))] {
let trap_offset = match _signum {
libc::SIGILL | libc::SIGFPE => 1,
_ => 0,
};
unsafe {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: (cx.uc_mcontext.psw.addr - trap_offset) as usize,
fp: *(cx.uc_mcontext.gregs[15] as *const usize),
}
}
} else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
unsafe {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: (*cx.uc_mcontext).__ss.__rip as usize,
fp: (*cx.uc_mcontext).__ss.__rbp as usize,
}
}
} else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
unsafe {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: (*cx.uc_mcontext).__ss.__pc as usize,
fp: (*cx.uc_mcontext).__ss.__fp as usize,
}
}
} else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.uc_mcontext.mc_rip as usize,
fp: cx.uc_mcontext.mc_rbp as usize,
}
} else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.uc_mcontext.__gregs[libc::REG_PC] as usize,
fp: cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
}
} else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
let cx = unsafe { &*(cx as *const libc::mcontext_t) };
TrapRegisters {
pc: cx.mc_gpregs.gp_elr as usize,
fp: cx.mc_gpregs.gp_x[29] as usize,
}
} else if #[cfg(all(target_os = "openbsd", target_arch = "x86_64"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.sc_rip as usize,
fp: cx.sc_rbp as usize,
}
} else if #[cfg(all(target_os = "linux", target_arch = "arm"))] {
let cx = unsafe { &*(cx as *const libc::ucontext_t) };
TrapRegisters {
pc: cx.uc_mcontext.arm_pc as usize,
fp: cx.uc_mcontext.arm_fp as usize,
}
} else {
compile_error!("unsupported platform");
panic!();
}
}
}
unsafe fn store_handler_in_ucontext(cx: *mut libc::c_void, handler: &Handler) {
cfg_if::cfg_if! {
if #[cfg(all(any(target_os = "linux", target_os = "android", target_os = "illumos"), target_arch = "x86_64"))] {
let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
cx.uc_mcontext.gregs[libc::REG_RIP as usize] = handler.pc as _;
cx.uc_mcontext.gregs[libc::REG_RSP as usize] = handler.sp as _;
cx.uc_mcontext.gregs[libc::REG_RBP as usize] = handler.fp as _;
cx.uc_mcontext.gregs[libc::REG_RAX as usize] = 0;
cx.uc_mcontext.gregs[libc::REG_RDX as usize] = 0;
} else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
cx.uc_mcontext.pc = handler.pc as _;
cx.uc_mcontext.sp = handler.sp as _;
cx.uc_mcontext.regs[29] = handler.fp as _;
cx.uc_mcontext.regs[0] = 0;
cx.uc_mcontext.regs[1] = 0;
} else if #[cfg(all(target_os = "linux", target_arch = "s390x"))] {
let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
cx.uc_mcontext.psw.addr = handler.pc as _;
cx.uc_mcontext.gregs[15] = handler.sp as _;
cx.uc_mcontext.gregs[6] = 0;
cx.uc_mcontext.gregs[7] = 0;
} else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
unsafe {
let cx = cx.cast::<libc::ucontext_t>().as_mut().unwrap();
let cx = cx.uc_mcontext.as_mut().unwrap();
cx.__ss.__rip = handler.pc as _;
cx.__ss.__rsp = handler.sp as _;
cx.__ss.__rbp = handler.fp as _;
cx.__ss.__rax = 0;
cx.__ss.__rdx = 0;
}
} else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
unsafe {
let cx = cx.cast::<libc::ucontext_t>().as_mut().unwrap();
let cx = cx.uc_mcontext.as_mut().unwrap();
cx.__ss.__pc = handler.pc as _;
cx.__ss.__sp = handler.sp as _;
cx.__ss.__fp = handler.fp as _;
cx.__ss.__x[0] = 0;
cx.__ss.__x[1] = 0;
}
} else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
cx.uc_mcontext.mc_rip = handler.pc as _;
cx.uc_mcontext.mc_rbp = handler.fp as _;
cx.uc_mcontext.mc_rsp = handler.sp as _;
cx.uc_mcontext.mc_rax = 0;
cx.uc_mcontext.mc_rdx = 0;
} else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
let cx = unsafe { cx.cast::<libc::mcontext_t>().as_mut().unwrap() };
cx.mc_gpregs.gp_elr = handler.pc as _;
cx.mc_gpregs.gp_sp = handler.sp as _;
cx.mc_gpregs.gp_x[29] = handler.fp as _;
cx.mc_gpregs.gp_x[0] = 0;
cx.mc_gpregs.gp_x[1] = 0;
} else if #[cfg(all(target_os = "openbsd", target_arch = "x86_64"))] {
let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
cx.sc_rip = handler.pc as _;
cx.sc_rbp = handler.fp as _;
cx.sc_rsp = handler.sp as _;
cx.sc_rax = 0;
cx.sc_rdx = 0;
} else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
cx.uc_mcontext.__gregs[libc::REG_PC] = handler.pc as _;
cx.uc_mcontext.__gregs[libc::REG_S0] = handler.fp as _;
cx.uc_mcontext.__gregs[libc::REG_SP] = handler.sp as _;
cx.uc_mcontext.__gregs[libc::REG_A0] = 0;
cx.uc_mcontext.__gregs[libc::REG_A0 + 1] = 0;
} else {
compile_error!("unsupported platform");
panic!();
}
}
}
#[cold]
pub fn lazy_per_thread_init() {
if cfg!(asan) {
return;
}
std::thread_local! {
static STACK: RefCell<Option<Stack>> = const { RefCell::new(None) };
}
const MIN_STACK_SIZE: usize = 64 * 4096;
struct Stack {
mmap_ptr: *mut libc::c_void,
mmap_size: usize,
}
return STACK.with(|s| {
*s.borrow_mut() = unsafe { allocate_sigaltstack() };
});
unsafe fn allocate_sigaltstack() -> Option<Stack> {
let mut old_stack = unsafe { mem::zeroed() };
let r = unsafe { libc::sigaltstack(ptr::null(), &mut old_stack) };
assert_eq!(
r,
0,
"learning about sigaltstack failed: {}",
io::Error::last_os_error()
);
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
return None;
}
let page_size = crate::runtime::vm::host_page_size();
let guard_size = page_size;
let alloc_size = guard_size + MIN_STACK_SIZE;
let ptr = unsafe {
rustix::mm::mmap_anonymous(
null_mut(),
alloc_size,
rustix::mm::ProtFlags::empty(),
rustix::mm::MapFlags::PRIVATE,
)
.expect("failed to allocate memory for sigaltstack")
};
let stack_ptr = (ptr as usize + guard_size) as *mut std::ffi::c_void;
unsafe {
rustix::mm::mprotect(
stack_ptr,
MIN_STACK_SIZE,
rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
)
.expect("mprotect to configure memory for sigaltstack failed");
}
let new_stack = libc::stack_t {
ss_sp: stack_ptr,
ss_flags: 0,
ss_size: MIN_STACK_SIZE,
};
let r = unsafe { libc::sigaltstack(&new_stack, ptr::null_mut()) };
assert_eq!(
r,
0,
"registering new sigaltstack failed: {}",
io::Error::last_os_error()
);
Some(Stack {
mmap_ptr: ptr,
mmap_size: alloc_size,
})
}
impl Drop for Stack {
fn drop(&mut self) {
unsafe {
let r = rustix::mm::munmap(self.mmap_ptr, self.mmap_size);
debug_assert!(r.is_ok(), "munmap failed during thread shutdown");
}
}
}
}