use crate::traphandlers::{tls, wasmtime_longjmp};
use std::cell::RefCell;
use std::io;
use std::mem::{self, MaybeUninit};
use std::ptr::{self, null_mut};
pub type SignalHandler<'a> =
dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
pub unsafe fn platform_init() {
if cfg!(miri) {
return;
}
let register = |slot: &mut MaybeUninit<libc::sigaction>, signal: i32| {
let mut handler: libc::sigaction = mem::zeroed();
handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
handler.sa_sigaction = trap_handler as usize;
libc::sigemptyset(&mut handler.sa_mask);
if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 {
panic!(
"unable to install signal handler: {}",
io::Error::last_os_error(),
);
}
};
register(&mut PREV_SIGSEGV, libc::SIGSEGV);
register(&mut PREV_SIGILL, libc::SIGILL);
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
register(&mut PREV_SIGFPE, libc::SIGFPE);
}
if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
register(&mut PREV_SIGBUS, libc::SIGBUS);
}
}
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 => &PREV_SIGSEGV,
libc::SIGBUS => &PREV_SIGBUS,
libc::SIGFPE => &PREV_SIGFPE,
libc::SIGILL => &PREV_SIGILL,
_ => panic!("unknown signal: {}", signum),
};
let handled = tls::with(|info| {
let info = match info {
Some(info) => info,
None => return false,
};
let (pc, fp) = get_pc_and_fp(context, signum);
let jmp_buf = info.take_jmp_buf_if_trap(pc, |handler| handler(signum, siginfo, context));
if jmp_buf.is_null() {
return false;
}
if jmp_buf as usize == 1 {
return true;
}
let faulting_addr = match signum {
libc::SIGSEGV | libc::SIGBUS => Some((*siginfo).si_addr() as usize),
_ => None,
};
info.set_jit_trap(pc, fp, faulting_addr);
if cfg!(target_os = "macos") {
unsafe extern "C" fn wasmtime_longjmp_shim(jmp_buf: *const u8) {
wasmtime_longjmp(jmp_buf)
}
set_pc(context, wasmtime_longjmp_shim as usize, jmp_buf as usize);
return true;
}
wasmtime_longjmp(jmp_buf)
});
if handled {
return;
}
let previous = &*previous.as_ptr();
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, ptr::null_mut());
} else {
mem::transmute::<usize, extern "C" fn(libc::c_int)>(previous.sa_sigaction)(signum)
}
}
#[repr(C)]
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
struct ucontext_t {
uc_onstack: libc::c_int,
uc_sigmask: libc::sigset_t,
uc_stack: libc::stack_t,
uc_link: *mut libc::ucontext_t,
uc_mcsize: usize,
uc_mcontext: libc::mcontext_t,
}
unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const u8, usize) {
cfg_if::cfg_if! {
if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(
cx.uc_mcontext.gregs[libc::REG_RIP as usize] as *const u8,
cx.uc_mcontext.gregs[libc::REG_RBP as usize] as usize
)
} else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(
cx.uc_mcontext.pc as *const u8,
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,
};
let cx = &*(cx as *const libc::ucontext_t);
(
(cx.uc_mcontext.psw.addr - trap_offset) as *const u8,
*(cx.uc_mcontext.gregs[15] as *const usize),
)
} else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(
(*cx.uc_mcontext).__ss.__rip as *const u8,
(*cx.uc_mcontext).__ss.__rbp as usize,
)
} else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
let cx = &*(cx as *const ucontext_t);
(
(*cx.uc_mcontext).__ss.__pc as *const u8,
(*cx.uc_mcontext).__ss.__fp as usize,
)
} else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(
cx.uc_mcontext.mc_rip as *const u8,
cx.uc_mcontext.mc_rbp as usize,
)
} else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(
cx.uc_mcontext.__gregs[libc::REG_PC] as *const u8,
cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
)
} else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::mcontext_t);
(
cx.mc_gpregs.gp_elr as *const u8,
cx.mc_gpregs.gp_x[29] as usize,
)
}
else {
compile_error!("unsupported platform");
}
}
}
unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
cfg_if::cfg_if! {
if #[cfg(not(target_os = "macos"))] {
let _ = (cx, pc, arg1);
unreachable!(); } else if #[cfg(target_arch = "x86_64")] {
let cx = &mut *(cx as *mut libc::ucontext_t);
(*cx.uc_mcontext).__ss.__rip = pc as u64;
(*cx.uc_mcontext).__ss.__rdi = arg1 as u64;
if (*cx.uc_mcontext).__ss.__rsp % 16 == 0 {
(*cx.uc_mcontext).__ss.__rsp -= 8;
}
} else if #[cfg(target_arch = "aarch64")] {
let cx = &mut *(cx as *mut ucontext_t);
(*cx.uc_mcontext).__ss.__pc = pc as u64;
(*cx.uc_mcontext).__ss.__x[0] = arg1 as u64;
} else {
compile_error!("unsupported macos target architecture");
}
}
}
#[cold]
pub fn lazy_per_thread_init() {
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> {
if cfg!(miri) {
return None;
}
let mut old_stack = mem::zeroed();
let r = 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::page_size();
let guard_size = page_size;
let alloc_size = guard_size + MIN_STACK_SIZE;
let ptr = 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;
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 = 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");
}
}
}
}