From caf2205936f677bca62be83fcf62ed069fecc2c0 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Sat, 2 Mar 2019 17:00:05 -0800 Subject: [PATCH] Add a signal handler for macos and linux. Implementation Notes: - To avoid setjmp, longjmp, and the mess that those create, we instead set the interrupting context of the signal handler to return into the `throw_trap` routine. To my surprise, this actually works. The stack ends up getting unwound normally and the memory-oob error is caught by the trampoline. --- Cargo.lock | 1 + lib/llvm-backend/Cargo.toml | 1 + lib/llvm-backend/cpp/object_loader.hh | 4 +- lib/llvm-backend/src/backend.rs | 4 + lib/llvm-backend/src/platform/unix.rs | 106 ++++++++++++++++++++++++++ lib/runtime/examples/call.rs | 5 +- 6 files changed, 118 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f06c9e940..a31fa0a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,6 +1213,7 @@ dependencies = [ "inkwell 0.1.0 (git+https://github.com/TheDan64/inkwell?branch=llvm7-0)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/lib/llvm-backend/Cargo.toml b/lib/llvm-backend/Cargo.toml index 81301872b..f350b263e 100644 --- a/lib/llvm-backend/Cargo.toml +++ b/lib/llvm-backend/Cargo.toml @@ -12,6 +12,7 @@ hashbrown = "0.1.8" smallvec = "0.6.8" goblin = "0.0.20" libc = "0.2.49" +nix = "0.13.0" capstone = "0.5.0" [build-dependencies] diff --git a/lib/llvm-backend/cpp/object_loader.hh b/lib/llvm-backend/cpp/object_loader.hh index 33724a868..9d51d686c 100644 --- a/lib/llvm-backend/cpp/object_loader.hh +++ b/lib/llvm-backend/cpp/object_loader.hh @@ -115,7 +115,7 @@ public: } uint32_t type_id, value_num; - uint64_t values[]; + uint64_t values[1]; }; struct WasmModule { @@ -140,7 +140,7 @@ extern "C" { return RESULT_OK; } - void throw_trap(WasmTrap::Type ty) { + [[noreturn]] void throw_trap(WasmTrap::Type ty) { throw WasmTrap(ty); } diff --git a/lib/llvm-backend/src/backend.rs b/lib/llvm-backend/src/backend.rs index e7e641aa9..792a10422 100644 --- a/lib/llvm-backend/src/backend.rs +++ b/lib/llvm-backend/src/backend.rs @@ -243,6 +243,10 @@ impl LLVMBackend { ) }; + unsafe { + crate::platform::install_signal_handler(); + } + if res != LLVMResult::OK { panic!("failed to load object") } diff --git a/lib/llvm-backend/src/platform/unix.rs b/lib/llvm-backend/src/platform/unix.rs index ee03ea08f..7db0d0110 100644 --- a/lib/llvm-backend/src/platform/unix.rs +++ b/lib/llvm-backend/src/platform/unix.rs @@ -1,3 +1,8 @@ +use libc::{c_void, siginfo_t}; +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV, +}; + /// `__register_frame` and `__deregister_frame` on macos take a single fde as an /// argument, so we need to parse the fde table here. /// @@ -33,3 +38,104 @@ pub unsafe fn visit_fde(addr: *mut u8, size: usize, visitor: extern "C" fn(*mut pub unsafe fn visit_fde(addr: *mut u8, size: usize, visitor: extern "C" fn(*mut u8)) { visitor(addr); } + +extern "C" { + fn throw_trap(ty: i32) -> !; +} + +pub unsafe fn install_signal_handler() { + let sa = SigAction::new( + SigHandler::SigAction(signal_trap_handler), + SaFlags::SA_ONSTACK, + SigSet::empty(), + ); + sigaction(SIGFPE, &sa).unwrap(); + sigaction(SIGILL, &sa).unwrap(); + sigaction(SIGSEGV, &sa).unwrap(); + sigaction(SIGBUS, &sa).unwrap(); +} + +extern "C" fn signal_trap_handler( + signum: ::nix::libc::c_int, + siginfo: *mut siginfo_t, + ucontext: *mut c_void, +) { + unsafe { + /// By setting the instruction pointer of the interrupted context + /// to `throw_trap` and the register of the first argument + /// to the trap ID, we can approximate throwing an exception + /// from a signal handler. + set_context_to_throw(ucontext); + } +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +unsafe fn set_context_to_throw(ucontext: *mut c_void) { + use libc::{ucontext_t, RDI, RIP}; + + let ucontext = ucontext as *mut ucontext_t; + (*ucontext).uc_mcontext.gregs[RIP as usize] = throw_trap as u64; + (*ucontext).uc_mcontext.gregs[RDI as usize] = 2; // `MemoryOutOfBounds` variant. +} + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +unsafe fn set_context_to_throw(ucontext: *mut c_void) { + #[allow(dead_code)] + #[repr(C)] + struct ucontext_t { + uc_onstack: u32, + uc_sigmask: u32, + uc_stack: libc::stack_t, + uc_link: *const ucontext_t, + uc_mcsize: u64, + uc_mcontext: *mut mcontext_t, + } + #[repr(C)] + struct exception_state { + trapno: u16, + cpu: u16, + err: u32, + faultvaddr: u64, + } + #[repr(C)] + struct regs { + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rsp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rip: u64, + rflags: u64, + cs: u64, + fs: u64, + gs: u64, + } + #[allow(dead_code)] + #[repr(C)] + struct mcontext_t { + es: exception_state, + ss: regs, + // ... + } + + let ucontext = ucontext as *mut ucontext_t; + (*(*ucontext).uc_mcontext).ss.rip = throw_trap as u64; + (*(*ucontext).uc_mcontext).ss.rdi = 2; // `MemoryOutOfBounds` variant. +} + +#[cfg(not(any( + all(target_os = "macos", target_arch = "x86_64"), + all(target_os = "linux", target_arch = "x86_64"), +)))] +compile_error!("This crate doesn't yet support compiling on operating systems other than linux and macos and architectures other than x86_64"); diff --git a/lib/runtime/examples/call.rs b/lib/runtime/examples/call.rs index 0684c1bb7..2a11db9e8 100644 --- a/lib/runtime/examples/call.rs +++ b/lib/runtime/examples/call.rs @@ -4,9 +4,12 @@ use wabt::wat2wasm; static WAT: &'static str = r#" (module + (memory 1) (type (;0;) (func (param i32) (result i32))) (func (;0;) (type 0) (param i32) (result i32) - unreachable) + i32.const 0x20000 + i32.load + ) (export "select_trap_l" (func 0)) ) "#;