Browse Source

Configure Mach ports vs signals via `Config` (#6807)

* Configure Mach ports vs signals via `Config`

This commit adds a `Config::macos_use_mach_ports` configuration option
to replace the old `posix-signals-on-macos` compile-time Cargo feature.
This'll make Wasmtime a tad larger on macOS but likely negligibly so.
Otherwise this is intended to provide a resolution to #6785 where
embedders will be able to use any build of Wasmtime and configure at
runtime how trap handling should happen.

Functionally this commit additionally registers a `pthread_atfork`
handler to cause any usage of Wasmtime in the child to panic. This
should help head off a known-invalid state in case anyone runs into it
in the future.

* Fix build on non-macOS
pull/6831/head
Alex Crichton 1 year ago
committed by GitHub
parent
commit
959c648040
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Cargo.toml
  2. 5
      crates/runtime/Cargo.toml
  3. 47
      crates/runtime/src/traphandlers.rs
  4. 22
      crates/runtime/src/traphandlers/macos.rs
  5. 5
      crates/wasmtime/Cargo.toml
  6. 37
      crates/wasmtime/src/config.rs
  7. 2
      crates/wasmtime/src/engine.rs

1
Cargo.toml

@ -265,7 +265,6 @@ wasi-threads = ["dep:wasmtime-wasi-threads"]
wasi-http = ["dep:wasmtime-wasi-http"]
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
all-arch = ["wasmtime/all-arch"]
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
component-model = [
"wasmtime/component-model",
"wasmtime-wast/component-model",

5
crates/runtime/Cargo.toml

@ -60,11 +60,6 @@ async = ["wasmtime-fiber"]
# Enables support for the pooling instance allocator
pooling-allocator = []
# 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 = []
component-model = [
"wasmtime-environ/component-model",
"dep:encoding_rs",

47
crates/runtime/src/traphandlers.rs

@ -71,10 +71,7 @@ cfg_if::cfg_if! {
}
cfg_if::cfg_if! {
if #[cfg(all(target_os = "macos", not(feature = "posix-signals-on-macos")))] {
mod macos;
use macos as sys;
} else if #[cfg(unix)] {
if #[cfg(unix)] {
mod unix;
use unix as sys;
} else if #[cfg(target_os = "windows")] {
@ -83,6 +80,9 @@ cfg_if::cfg_if! {
}
}
#[cfg(target_os = "macos")]
mod macos;
pub use sys::SignalHandler;
/// Globally-set callback to determine whether a program counter is actually a
@ -92,6 +92,10 @@ pub use sys::SignalHandler;
/// `wasmtime` currently.
static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
/// Whether or not macOS is using mach ports.
#[cfg(target_os = "macos")]
static mut MACOS_USE_MACH_PORTS: bool = false;
/// This function is required to be called before any WebAssembly is entered.
/// This will configure global state such as signal handlers to prepare the
/// process to receive wasm traps.
@ -105,12 +109,41 @@ static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
/// program counter is the pc of an actual wasm trap or not. This is then used
/// to disambiguate faults that happen due to wasm and faults that happen due to
/// bugs in Rust or elsewhere.
pub fn init_traps(is_wasm_pc: fn(usize) -> bool) {
pub fn init_traps(is_wasm_pc: fn(usize) -> bool, macos_use_mach_ports: bool) {
static INIT: Once = Once::new();
// only used on macos, so squelch warnings about this not being used on
// other platform.
let _ = macos_use_mach_ports;
INIT.call_once(|| unsafe {
IS_WASM_PC = is_wasm_pc;
#[cfg(target_os = "macos")]
if macos_use_mach_ports {
MACOS_USE_MACH_PORTS = macos_use_mach_ports;
return macos::platform_init();
}
sys::platform_init();
});
#[cfg(target_os = "macos")]
unsafe {
assert_eq!(
MACOS_USE_MACH_PORTS, macos_use_mach_ports,
"cannot configure two different methods of signal handling in the same process"
);
}
}
fn lazy_per_thread_init() {
#[cfg(target_os = "macos")]
unsafe {
if MACOS_USE_MACH_PORTS {
return macos::lazy_per_thread_init();
}
}
sys::lazy_per_thread_init();
}
/// Raises a trap immediately.
@ -592,7 +625,7 @@ mod tls {
// performed per-thread initialization for traps.
let (prev, initialized) = p.get();
if !initialized {
super::super::sys::lazy_per_thread_init();
super::super::lazy_per_thread_init();
}
p.set((val, true));
prev
@ -609,7 +642,7 @@ mod tls {
if initialized {
return;
}
super::super::sys::lazy_per_thread_init();
super::super::lazy_per_thread_init();
p.set((state, true));
})
}

22
crates/runtime/src/traphandlers/macos.rs

@ -161,16 +161,21 @@ mod mach_addons {
use mach_addons::*;
/// Just used below
pub enum Void {}
/// For now this is basically unused, we don't expose this any more for
/// Wasmtime on macOS.
pub type SignalHandler<'a> = dyn Fn(Void) -> bool + Send + Sync + 'a;
/// Process-global port that we use to route thread-level exceptions to.
static mut WASMTIME_PORT: mach_port_name_t = MACH_PORT_NULL;
static mut CHILD_OF_FORKED_PROCESS: bool = false;
pub unsafe fn platform_init() {
// Mach ports do not currently work across forks, so configure Wasmtime to
// panic in `lazy_per_thread_init` if the child attempts to invoke
// WebAssembly.
unsafe extern "C" fn child() {
CHILD_OF_FORKED_PROCESS = true;
}
let rc = libc::pthread_atfork(None, None, Some(child));
assert_eq!(rc, 0, "failed to configure `pthread_atfork` handler");
// Allocate our WASMTIME_PORT and make sure that it can be sent to so we
// can receive exceptions.
let me = mach_task_self();
@ -461,6 +466,11 @@ unsafe extern "C" fn unwind(
#[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(

5
crates/wasmtime/Cargo.toml

@ -118,11 +118,6 @@ all-arch = [
"wasmtime-winch?/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"]
# Enables in-progress support for the component model. Note that this feature is
# in-progress, buggy, and incomplete. This is primarily here for internal
# testing purposes.

37
crates/wasmtime/src/config.rs

@ -111,6 +111,7 @@ pub struct Config {
pub(crate) memory_guaranteed_dense_image_size: u64,
pub(crate) force_memory_init_memfd: bool,
pub(crate) coredump_on_trap: bool,
pub(crate) macos_use_mach_ports: bool,
}
/// User-provided configuration for the compiler.
@ -201,6 +202,7 @@ impl Config {
memory_guaranteed_dense_image_size: 16 << 20,
force_memory_init_memfd: false,
coredump_on_trap: false,
macos_use_mach_ports: true,
};
#[cfg(any(feature = "cranelift", feature = "winch"))]
{
@ -1689,8 +1691,41 @@ impl Config {
/// Enables clif output when compiling a WebAssembly module.
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn emit_clif(&mut self, path: &Path) {
pub fn emit_clif(&mut self, path: &Path) -> &mut Self {
self.compiler_config.clif_dir = Some(path.to_path_buf());
self
}
/// Configures whether, when on macOS, Mach ports are used for exception
/// handling instead of traditional Unix-based signal handling.
///
/// WebAssembly traps in Wasmtime are implemented with native faults, for
/// example a `SIGSEGV` will occur when a WebAssembly guest accesses
/// out-of-bounds memory. Handling this can be configured to either use Unix
/// signals or Mach ports on macOS. By default Mach ports are used.
///
/// Mach ports enable Wasmtime to work by default with foreign
/// error-handling systems such as breakpad which also use Mach ports to
/// handle signals. In this situation Wasmtime will continue to handle guest
/// faults gracefully while any non-guest faults will get forwarded to
/// process-level handlers such as breakpad. Some more background on this
/// can be found in #2456.
///
/// A downside of using mach ports, however, is that they don't interact
/// well with `fork()`. Forking a Wasmtime process on macOS will produce a
/// child process that cannot successfully run WebAssembly. In this
/// situation traditional Unix signal handling should be used as that's
/// inherited and works across forks.
///
/// If your embedding wants to use a custom error handler which leverages
/// Mach ports and you additionally wish to `fork()` the process and use
/// Wasmtime in the child process that's not currently possible. Please
/// reach out to us if you're in this bucket!
///
/// This option defaults to `true`, using Mach ports by default.
pub fn macos_use_mach_ports(&mut self, mach_ports: bool) -> &mut Self {
self.macos_use_mach_ports = mach_ports;
self
}
}

2
crates/wasmtime/src/engine.rs

@ -77,7 +77,7 @@ impl Engine {
// Ensure that wasmtime_runtime's signal handlers are configured. This
// is the per-program initialization required for handling traps, such
// as configuring signals, vectored exception handlers, etc.
wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc);
wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc, config.macos_use_mach_ports);
debug_builtins::ensure_exported();
let registry = SignatureRegistry::new();

Loading…
Cancel
Save