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.
This commit is contained in:
Lachlan Sneff 2019-03-02 17:00:05 -08:00
parent 57bfa9b0a4
commit caf2205936
6 changed files with 118 additions and 3 deletions

1
Cargo.lock generated
View File

@ -1213,6 +1213,7 @@ dependencies = [
"inkwell 0.1.0 (git+https://github.com/TheDan64/inkwell?branch=llvm7-0)", "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)", "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)", "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)", "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)", "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)", "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -12,6 +12,7 @@ hashbrown = "0.1.8"
smallvec = "0.6.8" smallvec = "0.6.8"
goblin = "0.0.20" goblin = "0.0.20"
libc = "0.2.49" libc = "0.2.49"
nix = "0.13.0"
capstone = "0.5.0" capstone = "0.5.0"
[build-dependencies] [build-dependencies]

View File

@ -115,7 +115,7 @@ public:
} }
uint32_t type_id, value_num; uint32_t type_id, value_num;
uint64_t values[]; uint64_t values[1];
}; };
struct WasmModule { struct WasmModule {
@ -140,7 +140,7 @@ extern "C" {
return RESULT_OK; return RESULT_OK;
} }
void throw_trap(WasmTrap::Type ty) { [[noreturn]] void throw_trap(WasmTrap::Type ty) {
throw WasmTrap(ty); throw WasmTrap(ty);
} }

View File

@ -243,6 +243,10 @@ impl LLVMBackend {
) )
}; };
unsafe {
crate::platform::install_signal_handler();
}
if res != LLVMResult::OK { if res != LLVMResult::OK {
panic!("failed to load object") panic!("failed to load object")
} }

View File

@ -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 /// `__register_frame` and `__deregister_frame` on macos take a single fde as an
/// argument, so we need to parse the fde table here. /// 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)) { pub unsafe fn visit_fde(addr: *mut u8, size: usize, visitor: extern "C" fn(*mut u8)) {
visitor(addr); 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");

View File

@ -4,9 +4,12 @@ use wabt::wat2wasm;
static WAT: &'static str = r#" static WAT: &'static str = r#"
(module (module
(memory 1)
(type (;0;) (func (param i32) (result i32))) (type (;0;) (func (param i32) (result i32)))
(func (;0;) (type 0) (param i32) (result i32) (func (;0;) (type 0) (param i32) (result i32)
unreachable) i32.const 0x20000
i32.load
)
(export "select_trap_l" (func 0)) (export "select_trap_l" (func 0))
) )
"#; "#;