#![allow(
// FFI bindings here for C/etc don't follow Rust's naming conventions.
non_snake_case,
// Platform-specific code has a lot of false positives with these lints so
// like Unix disable the lints for this module.
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
use crate::runtime::module::lookup_code;
use crate::runtime::vm::traphandlers::{TrapRegisters, tls};
use mach2::exc::*;
use mach2::exception_types::*;
use mach2::kern_return::*;
use mach2::mach_init::*;
use mach2::mach_port::*;
use mach2::message::*;
use mach2::ndr::*;
use mach2::port::*;
use mach2::thread_act::*;
use mach2::thread_status::*;
use mach2::traps::*;
use std::io;
use std::mem;
use std::thread;
use wasmtime_environ::Trap;
static mut WASMTIME_PORT: mach_port_name_t = MACH_PORT_NULL;
static mut CHILD_OF_FORKED_PROCESS: bool = false;
pub struct TrapHandler {
thread: Option<thread::JoinHandle<()>>,
}
static mut PREV_SIGBUS: libc::sigaction = unsafe { mem::zeroed() };
impl TrapHandler {
pub unsafe fn new() -> TrapHandler {
unsafe extern "C" fn child() {
unsafe {
CHILD_OF_FORKED_PROCESS = true;
}
}
let thread;
unsafe {
let rc = libc::pthread_atfork(None, None, Some(child));
assert_eq!(rc, 0, "failed to configure `pthread_atfork` handler");
let me = mach_task_self();
let kret = mach_port_allocate(me, MACH_PORT_RIGHT_RECEIVE, &raw mut WASMTIME_PORT);
assert_eq!(kret, KERN_SUCCESS, "failed to allocate port");
let kret =
mach_port_insert_right(me, WASMTIME_PORT, WASMTIME_PORT, MACH_MSG_TYPE_MAKE_SEND);
assert_eq!(kret, KERN_SUCCESS, "failed to insert right");
thread = thread::spawn(|| handler_thread());
let mut handler: libc::sigaction = mem::zeroed();
handler.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
handler.sa_sigaction = (sigbus_handler as *const ()).addr();
libc::sigemptyset(&mut handler.sa_mask);
if libc::sigaction(libc::SIGBUS, &handler, &raw mut PREV_SIGBUS) != 0 {
panic!(
"unable to install signal handler: {}",
io::Error::last_os_error(),
);
}
}
TrapHandler {
thread: Some(thread),
}
}
}
impl Drop for TrapHandler {
fn drop(&mut self) {
unsafe {
let kret = mach_port_destroy(mach_task_self(), WASMTIME_PORT);
assert_eq!(kret, KERN_SUCCESS, "failed to destroy port");
self.thread.take().unwrap().join().unwrap();
}
}
}
unsafe extern "C" fn sigbus_handler(
signum: libc::c_int,
siginfo: *mut libc::siginfo_t,
context: *mut libc::c_void,
) {
tls::with(|info| {
let info = match info {
Some(info) => info,
None => return,
};
unsafe {
let faulting_addr = (*siginfo).si_addr() as usize;
let range = &info.vm_store_context.get().as_ref().async_guard_range;
if range.start.addr() <= faulting_addr && faulting_addr < range.end.addr() {
super::signals::abort_stack_overflow();
}
}
});
unsafe {
super::signals::delegate_signal_to_previous_handler(
&raw mut PREV_SIGBUS,
signum,
siginfo,
context,
)
}
}
#[repr(C, packed(4))]
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
struct __Request__exception_raise_t {
Head: mach_msg_header_t,
msgh_body: mach_msg_body_t,
thread: mach_msg_port_descriptor_t,
task: mach_msg_port_descriptor_t,
NDR: NDR_record_t,
exception: exception_type_t,
codeCnt: mach_msg_type_number_t,
code: [i64; 2],
}
#[repr(C)]
#[allow(dead_code)]
#[derive(Debug)]
struct ExceptionRequest {
body: __Request__exception_raise_t,
trailer: mach_msg_trailer_t,
}
unsafe fn handler_thread() {
const EXCEPTION_MSG_ID: mach_msg_id_t = 2405;
loop {
let mut request: ExceptionRequest = unsafe { mem::zeroed() };
let kret = unsafe {
mach_msg(
&mut request.body.Head,
MACH_RCV_MSG | MACH_RCV_INTERRUPT,
0,
mem::size_of_val(&request) as u32,
WASMTIME_PORT,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL,
)
};
if kret == MACH_RCV_PORT_CHANGED {
break;
}
if kret != KERN_SUCCESS {
eprintln!("mach_msg failed with {kret} ({kret:x})");
unsafe {
libc::abort();
}
}
if request.body.Head.msgh_id != EXCEPTION_MSG_ID {
eprintln!("unexpected msg header id {}", request.body.Head.msgh_id);
unsafe {
libc::abort();
}
}
let reply_code = if unsafe { handle_exception(&mut request) } {
KERN_SUCCESS
} else {
KERN_FAILURE
};
let mut reply: __Reply__exception_raise_t = unsafe { mem::zeroed() };
reply.Head.msgh_bits =
MACH_MSGH_BITS(request.body.Head.msgh_bits & MACH_MSGH_BITS_REMOTE_MASK, 0);
reply.Head.msgh_size = mem::size_of_val(&reply) as u32;
reply.Head.msgh_remote_port = request.body.Head.msgh_remote_port;
reply.Head.msgh_local_port = MACH_PORT_NULL;
reply.Head.msgh_id = request.body.Head.msgh_id + 100;
reply.NDR = unsafe { NDR_record };
reply.RetCode = reply_code;
unsafe {
mach_msg(
&mut reply.Head,
MACH_SEND_MSG,
mem::size_of_val(&reply) as u32,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL,
);
}
}
}
unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
match request.body.exception as u32 {
EXC_BAD_ACCESS | EXC_BAD_INSTRUCTION | EXC_ARITHMETIC => {}
_ => return false,
}
let faulting_addr = if request.body.exception as u32 == EXC_BAD_ACCESS {
Some(request.body.code[1] as usize)
} else {
None
};
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
use mach2::structs::x86_thread_state64_t;
use mach2::thread_status::x86_THREAD_STATE64;
type ThreadState = x86_thread_state64_t;
let thread_state_flavor = x86_THREAD_STATE64;
let get_pc_and_fp = |state: &ThreadState| (
state.__rip as *const u8,
state.__rbp as usize,
);
let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
if state.__rsp % 16 == 0 {
state.__rsp -= 8;
unsafe {
*(state.__rsp as *mut u64) = state.__rip;
}
}
state.__rip = (unwind as *const ()).addr() as u64;
state.__rdi = pc as u64;
state.__rsi = fp as u64;
state.__rdx = fault1 as u64;
state.__rcx = fault2 as u64;
state.__r8 = trap as u64;
};
let mut thread_state = ThreadState::new();
} else if #[cfg(target_arch = "aarch64")] {
type ThreadState = mach2::structs::arm_thread_state64_t;
let thread_state_flavor = ARM_THREAD_STATE64;
let get_pc_and_fp = |state: &ThreadState| (
state.__pc as *const u8,
state.__fp as usize,
);
let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
state.__lr = pc as u64;
state.__x[0] = pc as u64;
state.__x[1] = fp as u64;
state.__x[2] = fault1 as u64;
state.__x[3] = fault2 as u64;
state.__x[4] = trap as u64;
state.__pc = (unwind as *const ()).addr() as u64;
};
let mut thread_state = unsafe { mem::zeroed::<ThreadState>() };
} else {
compile_error!("unsupported target architecture");
}
}
let origin_thread = request.body.thread.name;
let mut thread_state_count = ThreadState::count();
let kret = unsafe {
thread_get_state(
origin_thread,
thread_state_flavor,
&mut thread_state as *mut ThreadState as *mut u32,
&mut thread_state_count,
)
};
if kret != KERN_SUCCESS {
return false;
}
let (pc, fp) = get_pc_and_fp(&thread_state);
let Some((code, text_offset)) = lookup_code(pc as usize) else {
return false;
};
let Some(trap) = code.lookup_trap_code(text_offset) else {
return false;
};
let (fault1, fault2) = match faulting_addr {
None => (0, 0),
Some(addr) => (1, addr),
};
resume(&mut thread_state, pc as usize, fp, fault1, fault2, trap);
let kret = unsafe {
thread_set_state(
origin_thread,
thread_state_flavor,
&mut thread_state as *mut ThreadState as *mut u32,
thread_state_count,
)
};
kret == KERN_SUCCESS
}
unsafe extern "C" fn unwind(pc: usize, fp: usize, fault1: usize, fault2: usize, trap: u8) -> ! {
let handler = tls::with(|state| {
let state = state.unwrap();
let regs = TrapRegisters { pc, fp };
let faulting_addr = match fault1 {
0 => None,
_ => Some(fault2),
};
let trap = Trap::from_u8(trap).unwrap();
state.set_jit_trap(regs, faulting_addr, trap);
state.entry_trap_handler()
});
unsafe {
handler.resume_tailcc(0, 0);
}
}
#[cold]
pub fn lazy_per_thread_init() {
unsafe {
assert!(
!CHILD_OF_FORKED_PROCESS,
"cannot use Wasmtime in a forked process when mach ports are \
configured, see `Config::macos_use_mach_ports`"
);
assert!(WASMTIME_PORT != MACH_PORT_NULL);
let this_thread = mach_thread_self();
let kret = thread_set_exception_ports(
this_thread,
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC,
WASMTIME_PORT,
(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES) as exception_behavior_t,
THREAD_STATE_NONE,
);
mach_port_deallocate(mach_task_self(), this_thread);
assert_eq!(kret, KERN_SUCCESS, "failed to set thread exception port");
}
}