Restore POSIX signal handling on MacOS behind a feature flag (#3063)
* Restore POSIX signal handling on MacOS behind a feature flag As described in Issue #3052, the switch to Mach Exception handling removed `unix::StoreExt` from the public API of crate on MacOS. That is a breaking change and makes it difficult for some application to upgrade to the current stable Wasmtime. As a workaround this PR introduces a feature flag called `posix-signals-on-macos` that restores the old behaviour on MacOS. The flag is disabled by default. * Fix test guard * Fix formatting in the test
This commit is contained in:
@@ -90,6 +90,7 @@ wasi-crypto = ["wasmtime-wasi-crypto"]
|
||||
wasi-nn = ["wasmtime-wasi-nn"]
|
||||
uffd = ["wasmtime/uffd"]
|
||||
all-arch = ["wasmtime/all-arch"]
|
||||
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
|
||||
|
||||
# Stub feature that does nothing, for Cargo-features compatibility: the new
|
||||
# backend is the default now.
|
||||
|
||||
@@ -48,3 +48,8 @@ async = ["wasmtime-fiber"]
|
||||
|
||||
# Enables support for userfaultfd in the pooling allocator when building on Linux
|
||||
uffd = ["userfaultfd"]
|
||||
|
||||
# Enables trap handling using POSIX signals instead of Mach exceptions on MacOS.
|
||||
# It is useful for applications that do not bind their own exception ports and
|
||||
# need portable signal handling.
|
||||
posix-signals-on-macos = []
|
||||
|
||||
@@ -27,7 +27,7 @@ extern "C" {
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
if #[cfg(all(target_os = "macos", not(feature = "posix-signals-on-macos")))] {
|
||||
mod macos;
|
||||
use macos as sys;
|
||||
} else if #[cfg(unix)] {
|
||||
|
||||
@@ -53,7 +53,8 @@ pub unsafe fn platform_init() {
|
||||
}
|
||||
|
||||
// On ARM, handle Unaligned Accesses.
|
||||
if cfg!(target_arch = "arm") || cfg!(target_os = "freebsd") {
|
||||
// On Darwin, guard page accesses are raised as SIGBUS.
|
||||
if cfg!(target_arch = "arm") || cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
|
||||
register(&mut PREV_SIGBUS, libc::SIGBUS);
|
||||
}
|
||||
}
|
||||
@@ -99,6 +100,42 @@ unsafe extern "C" fn trap_handler(
|
||||
return true;
|
||||
}
|
||||
info.capture_backtrace(pc);
|
||||
// On macOS this is a bit special, unfortunately. If we were to
|
||||
// `siglongjmp` out of the signal handler that notably does
|
||||
// *not* reset the sigaltstack state of our signal handler. This
|
||||
// seems to trick the kernel into thinking that the sigaltstack
|
||||
// is still in use upon delivery of the next signal, meaning
|
||||
// that the sigaltstack is not ever used again if we immediately
|
||||
// call `wasmtime_longjmp` here.
|
||||
//
|
||||
// Note that if we use `longjmp` instead of `siglongjmp` then
|
||||
// the problem is fixed. The problem with that, however, is that
|
||||
// `setjmp` is much slower than `sigsetjmp` due to the
|
||||
// preservation of the proceses signal mask. The reason
|
||||
// `longjmp` appears to work is that it seems to call a function
|
||||
// (according to published macOS sources) called
|
||||
// `_sigunaltstack` which updates the kernel to say the
|
||||
// sigaltstack is no longer in use. We ideally want to call that
|
||||
// here but I don't think there's a stable way for us to call
|
||||
// that.
|
||||
//
|
||||
// Given all that, on macOS only, we do the next best thing. We
|
||||
// return from the signal handler after updating the register
|
||||
// context. This will cause control to return to our shim
|
||||
// function defined here which will perform the
|
||||
// `wasmtime_longjmp` (`siglongjmp`) for us. The reason this
|
||||
// works is that by returning from the signal handler we'll
|
||||
// trigger all the normal machinery for "the signal handler is
|
||||
// done running" which will clear the sigaltstack flag and allow
|
||||
// reusing it for the next signal. Then upon resuming in our custom
|
||||
// code we blow away the stack anyway with a longjmp.
|
||||
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)
|
||||
});
|
||||
|
||||
@@ -155,6 +192,15 @@ unsafe fn get_pc(cx: *mut libc::c_void, _signum: libc::c_int) -> *const u8 {
|
||||
};
|
||||
let cx = &*(cx as *const libc::ucontext_t);
|
||||
(cx.uc_mcontext.psw.addr - trap_offset) as *const u8
|
||||
} 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
|
||||
} else if #[cfg(all(target_os = "macos", target_arch = "x86"))] {
|
||||
let cx = &*(cx as *const libc::ucontext_t);
|
||||
(*cx.uc_mcontext).__ss.__eip as *const u8
|
||||
} else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
|
||||
let cx = &*(cx as *const libc::ucontext_t);
|
||||
(*cx.uc_mcontext).__ss.__pc as *const u8
|
||||
} 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
|
||||
@@ -164,6 +210,41 @@ unsafe fn get_pc(cx: *mut libc::c_void, _signum: libc::c_int) -> *const u8 {
|
||||
}
|
||||
}
|
||||
|
||||
// This is only used on macOS targets for calling an unwinding shim
|
||||
// function to ensure that we return from the signal handler.
|
||||
//
|
||||
// See more comments above where this is called for what it's doing.
|
||||
unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(target_os = "macos"))] {
|
||||
drop((cx, pc, arg1));
|
||||
unreachable!(); // not used on these platforms
|
||||
} 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;
|
||||
// We're simulating a "pseudo-call" so we need to ensure
|
||||
// stack alignment is properly respected, notably that on a
|
||||
// `call` instruction the stack is 8/16-byte aligned, then
|
||||
// the function adjusts itself to be 16-byte aligned.
|
||||
//
|
||||
// Most of the time the stack pointer is 16-byte aligned at
|
||||
// the time of the trap but for more robust-ness with JIT
|
||||
// code where it may ud2 in a prologue check before the
|
||||
// stack is aligned we double-check here.
|
||||
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 libc::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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A function for registering a custom alternate signal stack (sigaltstack).
|
||||
///
|
||||
/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
|
||||
|
||||
@@ -81,3 +81,8 @@ uffd = ["wasmtime-runtime/uffd"]
|
||||
|
||||
# Enables support for all architectures in JIT and the `wasmtime compile` CLI command.
|
||||
all-arch = ["wasmtime-jit/all-arch"]
|
||||
|
||||
# Enables trap handling using POSIX signals instead of Mach exceptions on MacOS.
|
||||
# It is useful for applications that do not bind their own exception ports and
|
||||
# need portable signal handling.
|
||||
posix-signals-on-macos = ["wasmtime-runtime/posix-signals-on-macos"]
|
||||
|
||||
@@ -401,7 +401,7 @@ pub use crate::types::*;
|
||||
pub use crate::values::*;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
if #[cfg(all(target_os = "macos", not(feature = "posix-signals-on-macos")))] {
|
||||
// no extensions for macOS at this time
|
||||
} else if #[cfg(unix)] {
|
||||
pub mod unix;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
all(target_os = "macos", feature = "posix-signals-on-macos")
|
||||
))]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
Reference in New Issue
Block a user