Fix runtime error catching (#157)

This commit is contained in:
Lachlan Sneff
2019-02-07 14:44:28 -08:00
committed by GitHub
parent ea2bd80089
commit 8d2c1956d7
6 changed files with 127 additions and 25 deletions

16
Cargo.lock generated
View File

@ -348,6 +348,18 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "nix"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
version = "0.2.2" version = "0.2.2"
@ -798,7 +810,8 @@ dependencies = [
"cranelift-native 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)", "cranelift-native 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cranelift-wasm 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)", "cranelift-wasm 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
"serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -948,6 +961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
"checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" "checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f"
"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b"
"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409"
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
"checksum page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f89ef58b3d32420dbd1a43d2f38ae92f6239ef12bb556ab09ca55445f5a67242" "checksum page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f89ef58b3d32420dbd1a43d2f38ae92f6239ef12bb556ab09ca55445f5a67242"

View File

@ -17,7 +17,8 @@ hashbrown = "0.1"
target-lexicon = "0.2.0" target-lexicon = "0.2.0"
wasmparser = "0.23.0" wasmparser = "0.23.0"
byteorder = "1" byteorder = "1"
nix = "0.12.0" nix = "0.13.0"
libc = "0.2.48"
# Dependencies for caching. # Dependencies for caching.
[dependencies.serde] [dependencies.serde]

View File

@ -6,7 +6,7 @@
use crate::call::sighandler::install_sighandler; use crate::call::sighandler::install_sighandler;
use crate::relocation::{TrapCode, TrapData, TrapSink}; use crate::relocation::{TrapCode, TrapData, TrapSink};
use nix::libc::{c_void, siginfo_t}; use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV}; use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
use std::cell::{Cell, UnsafeCell}; use std::cell::{Cell, UnsafeCell};
use std::ptr; use std::ptr;
@ -18,15 +18,15 @@ use wasmer_runtime_core::{
}; };
extern "C" { extern "C" {
pub fn setjmp(env: *mut ::nix::libc::c_void) -> ::nix::libc::c_int; pub fn setjmp(env: *mut c_void) -> c_int;
fn longjmp(env: *mut ::nix::libc::c_void, val: ::nix::libc::c_int) -> !; fn longjmp(env: *mut c_void, val: c_int) -> !;
} }
const SETJMP_BUFFER_LEN: usize = 27; const SETJMP_BUFFER_LEN: usize = 27;
pub static SIGHANDLER_INIT: Once = Once::new(); pub static SIGHANDLER_INIT: Once = Once::new();
thread_local! { thread_local! {
pub static SETJMP_BUFFER: UnsafeCell<[::nix::libc::c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]); pub static SETJMP_BUFFER: UnsafeCell<[c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
pub static CAUGHT_ADDRESSES: Cell<(*const c_void, *const c_void)> = Cell::new((ptr::null(), ptr::null())); pub static CAUGHT_ADDRESSES: Cell<(*const c_void, *const c_void)> = Cell::new((ptr::null(), ptr::null()));
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null()); pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
} }
@ -75,15 +75,15 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
install_sighandler(); install_sighandler();
}); });
let signum = setjmp(jmp_buf as *mut ::nix::libc::c_void); let signum = setjmp(jmp_buf as *mut _);
if signum != 0 { if signum != 0 {
*jmp_buf = prev_jmp_buf; *jmp_buf = prev_jmp_buf;
let (faulting_addr, _) = CAUGHT_ADDRESSES.with(|cell| cell.get()); let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
if let Some(TrapData { if let Some(TrapData {
trapcode, trapcode,
srcloc: _, srcloc: _,
}) = handler_data.lookup(faulting_addr) }) = handler_data.lookup(inst_ptr)
{ {
Err(match Signal::from_c_int(signum) { Err(match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode { Ok(SIGILL) => match trapcode {
@ -95,7 +95,7 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
}, },
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess { TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0), memory: MemoryIndex::new(0),
addr: 0, addr: None,
}, },
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds { TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
table: TableIndex::new(0), table: TableIndex::new(0),
@ -106,7 +106,7 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
}, },
Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess { Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0), memory: MemoryIndex::new(0),
addr: 0, addr: None,
}, },
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation, Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
_ => unimplemented!(), _ => unimplemented!(),
@ -136,7 +136,7 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
} }
/// Unwinds to last protected_call. /// Unwinds to last protected_call.
pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t, ucontext: *const c_void) -> ! { pub unsafe fn do_unwind(signum: i32, siginfo: *const c_void, ucontext: *const c_void) -> ! {
// Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.) // Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.)
// itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should // itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should
// temporarily disable the signal handlers to debug it. // temporarily disable the signal handlers to debug it.
@ -153,18 +153,91 @@ pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t, ucontext: *const c
#[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip( unsafe fn get_faulting_addr_and_ip(
siginfo: *mut siginfo_t, siginfo: *const c_void,
_ucontext: *const c_void, ucontext: *const c_void,
) -> (*const c_void, *const c_void) { ) -> (*const c_void, *const c_void) {
(ptr::null(), ptr::null()) use libc::{ucontext_t, RIP};
#[allow(dead_code)]
#[repr(C)]
struct siginfo_t {
si_signo: i32,
si_errno: i32,
si_code: i32,
si_addr: u64,
// ...
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*ucontext).uc_mcontext.gregs[RIP as usize];
(si_addr as _, rip as _)
} }
#[cfg(all(target_os = "macos", target_arch = "x86_64"))] #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip( unsafe fn get_faulting_addr_and_ip(
siginfo: *mut siginfo_t, siginfo: *const c_void,
_ucontext: *const c_void, ucontext: *const c_void,
) -> (*const c_void, *const c_void) { ) -> (*const c_void, *const c_void) {
((*siginfo).si_addr, ptr::null()) #[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: *const 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 siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*(*ucontext).uc_mcontext).ss.rip;
(si_addr, rip as _)
} }
#[cfg(not(any( #[cfg(not(any(

View File

@ -26,6 +26,6 @@ extern "C" fn signal_trap_handler(
ucontext: *mut c_void, ucontext: *mut c_void,
) { ) {
unsafe { unsafe {
recovery::do_unwind(signum, siginfo, ucontext); recovery::do_unwind(signum, siginfo as _, ucontext);
} }
} }

View File

@ -266,7 +266,10 @@ impl TrapSink {
} }
pub fn lookup(&self, offset: usize) -> Option<TrapData> { pub fn lookup(&self, offset: usize) -> Option<TrapData> {
self.trap_datas.get(offset).map(|(_, trap_data)| *trap_data) self.trap_datas
.iter()
.find(|(trap_offset, _)| *trap_offset == offset)
.map(|(_, trap_data)| *trap_data)
} }
pub fn drain_local(&mut self, current_func_offset: usize, local: &mut LocalTrapSink) { pub fn drain_local(&mut self, current_func_offset: usize, local: &mut LocalTrapSink) {

View File

@ -83,12 +83,23 @@ impl PartialEq for LinkError {
/// Comparing two `RuntimeError`s always evaluates to false. /// Comparing two `RuntimeError`s always evaluates to false.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum RuntimeError { pub enum RuntimeError {
OutOfBoundsAccess { memory: MemoryIndex, addr: u32 }, OutOfBoundsAccess {
TableOutOfBounds { table: TableIndex }, memory: MemoryIndex,
IndirectCallSignature { table: TableIndex }, addr: Option<u32>,
IndirectCallToNull { table: TableIndex }, },
TableOutOfBounds {
table: TableIndex,
},
IndirectCallSignature {
table: TableIndex,
},
IndirectCallToNull {
table: TableIndex,
},
IllegalArithmeticOperation, IllegalArithmeticOperation,
Unknown { msg: String }, Unknown {
msg: String,
},
} }
impl PartialEq for RuntimeError { impl PartialEq for RuntimeError {