Mach ports continued + support aarch64-apple unwinding (#2723)
* Switch macOS to using mach ports for trap handling This commit moves macOS to using mach ports instead of signals for handling traps. The motivation for this is listed in #2456, namely that once mach ports are used in a process that means traditional UNIX signal handlers won't get used. This means that if Wasmtime is integrated with Breakpad, for example, then Wasmtime's trap handler never fires and traps don't work. The `traphandlers` module is refactored as part of this commit to split the platform-specific bits into their own files (it was growing quite a lot for one inline `cfg_if!`). The `unix.rs` and `windows.rs` files remain the same as they were before with a few minor tweaks for some refactored interfaces. The `macos.rs` file is brand new and lifts almost its entire implementation from SpiderMonkey, adapted for Wasmtime though. The main gotcha with mach ports is that a separate thread is what services the exception. Some unsafe magic allows this separate thread to read non-`Send` and temporary state from other threads, but is hoped to be safe in this context. The unfortunate downside is that calling wasm on macOS now involves taking a global lock and modifying a global hash map twice-per-call. I'm not entirely sure how to get out of this cost for now, but hopefully for any embeddings on macOS it's not the end of the world. Closes #2456 * Add a sketch of arm64 apple support * store: maintain CallThreadState mapping when switching fibers * cranelift/aarch64: generate unwind directives to disable pointer auth Aarch64 post ARMv8.3 has a feature called pointer authentication, designed to fight ROP/JOP attacks: some pointers may be signed using new instructions, adding payloads to the high (previously unused) bits of the pointers. More on this here: https://lwn.net/Articles/718888/ Unwinders on aarch64 need to know if some pointers contained on the call frame contain an authentication code or not, to be able to properly authenticate them or use them directly. Since native code may have enabled it by default (as is the case on the Mac M1), and the default is that this configuration value is inherited, we need to explicitly disable it, for the only kind of supported pointers (return addresses). To do so, we set the value of a non-existing dwarf pseudo register (34) to 0, as documented in https://github.com/ARM-software/abi-aa/blob/master/aadwarf64/aadwarf64.rst#note-8. This is done at the function granularity, in the spirit of Cranelift compilation model. Alternatively, a single directive could be generated in the CIE, generating less information per module. * Make exception handling work on Mac aarch64 too * fibers: use a breakpoint instruction after the final call in wasmtime_fiber_start Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
83
crates/runtime/src/traphandlers/windows.rs
Normal file
83
crates/runtime/src/traphandlers/windows.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::traphandlers::{tls, CallThreadState, Trap, Unwind};
|
||||
use std::io;
|
||||
use winapi::um::errhandlingapi::*;
|
||||
use winapi::um::minwinbase::*;
|
||||
use winapi::um::winnt::*;
|
||||
use winapi::vc::excpt::*;
|
||||
|
||||
/// Function which may handle custom signals while processing traps.
|
||||
pub type SignalHandler<'a> = dyn Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool + 'a;
|
||||
|
||||
pub unsafe fn platform_init() {
|
||||
// our trap handler needs to go first, so that we can recover from
|
||||
// wasm faults and continue execution, so pass `1` as a true value
|
||||
// here.
|
||||
if AddVectoredExceptionHandler(1, Some(exception_handler)).is_null() {
|
||||
panic!(
|
||||
"failed to add exception handler: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn exception_handler(exception_info: PEXCEPTION_POINTERS) -> LONG {
|
||||
// Check the kind of exception, since we only handle a subset within
|
||||
// wasm code. If anything else happens we want to defer to whatever
|
||||
// the rest of the system wants to do for this exception.
|
||||
let record = &*(*exception_info).ExceptionRecord;
|
||||
if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION
|
||||
&& record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION
|
||||
&& record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO
|
||||
&& record.ExceptionCode != EXCEPTION_INT_OVERFLOW
|
||||
{
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// FIXME: this is what the previous C++ did to make sure that TLS
|
||||
// works by the time we execute this trap handling code. This isn't
|
||||
// exactly super easy to call from Rust though and it's not clear we
|
||||
// necessarily need to do so. Leaving this here in case we need this
|
||||
// in the future, but for now we can probably wait until we see a
|
||||
// strange fault before figuring out how to reimplement this in
|
||||
// Rust.
|
||||
//
|
||||
// if (!NtCurrentTeb()->Reserved1[sThreadLocalArrayPointerIndex]) {
|
||||
// return EXCEPTION_CONTINUE_SEARCH;
|
||||
// }
|
||||
|
||||
// This is basically the same as the unix version above, only with a
|
||||
// few parameters tweaked here and there.
|
||||
tls::with(|info| {
|
||||
let info = match info {
|
||||
Some(info) => info,
|
||||
None => return EXCEPTION_CONTINUE_SEARCH,
|
||||
};
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "x86_64")] {
|
||||
let ip = (*(*exception_info).ContextRecord).Rip as *const u8;
|
||||
} else if #[cfg(target_arch = "x86")] {
|
||||
let ip = (*(*exception_info).ContextRecord).Eip as *const u8;
|
||||
} else {
|
||||
compile_error!("unsupported platform");
|
||||
}
|
||||
}
|
||||
let jmp_buf = info.jmp_buf_if_trap(ip, |handler| handler(exception_info));
|
||||
if jmp_buf.is_null() {
|
||||
EXCEPTION_CONTINUE_SEARCH
|
||||
} else if jmp_buf as usize == 1 {
|
||||
EXCEPTION_CONTINUE_EXECUTION
|
||||
} else {
|
||||
info.capture_backtrace(ip);
|
||||
Unwind(jmp_buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lazy_per_thread_init() -> Result<(), Trap> {
|
||||
// Unused on Windows
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_tls(_: *const CallThreadState<'static>) {
|
||||
// Unused on Windows
|
||||
}
|
||||
Reference in New Issue
Block a user