Naive short circuiting implementation for user panics and results. (#167)

* Add panic and result catching

* exit process on panic and user runtime error

* Complete initial implementation
This commit is contained in:
Lachlan Sneff
2019-02-08 13:08:03 -08:00
committed by GitHub
parent 4e1bc483a8
commit 1886b3d3c1
10 changed files with 171 additions and 60 deletions

View File

@ -16,7 +16,7 @@ use wasmer_runtime_core::{
cache::{Cache, Error as CacheError}, cache::{Cache, Error as CacheError},
}; };
use wasmer_runtime_core::{ use wasmer_runtime_core::{
backend::{Backend, FuncResolver, ProtectedCaller, Token}, backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper},
error::{CompileResult, RuntimeResult}, error::{CompileResult, RuntimeResult},
module::{ModuleInfo, ModuleInner, StringTable}, module::{ModuleInfo, ModuleInner, StringTable},
structures::{Map, TypedIndex}, structures::{Map, TypedIndex},
@ -51,6 +51,10 @@ impl ProtectedCaller for Placeholder {
) -> RuntimeResult<Vec<Value>> { ) -> RuntimeResult<Vec<Value>> {
Ok(vec![]) Ok(vec![])
} }
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
unimplemented!()
}
} }
/// This contains all of the items in a `ModuleInner` except the `func_resolver`. /// This contains all of the items in a `ModuleInner` except the `func_resolver`.

View File

@ -2,9 +2,9 @@ use crate::relocation::{TrapData, TrapSink};
use crate::trampoline::Trampolines; use crate::trampoline::Trampolines;
use hashbrown::HashSet; use hashbrown::HashSet;
use libc::c_void; use libc::c_void;
use std::sync::Arc; use std::{cell::Cell, sync::Arc};
use wasmer_runtime_core::{ use wasmer_runtime_core::{
backend::{ProtectedCaller, Token}, backend::{ProtectedCaller, Token, UserTrapper},
error::RuntimeResult, error::RuntimeResult,
export::Context, export::Context,
module::{ExportIndex, ModuleInfo, ModuleInner}, module::{ExportIndex, ModuleInfo, ModuleInner},
@ -24,6 +24,19 @@ pub use self::unix::*;
#[cfg(windows)] #[cfg(windows)]
pub use self::windows::*; pub use self::windows::*;
thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<String>> = Cell::new(None);
}
pub struct Trapper;
impl UserTrapper for Trapper {
unsafe fn do_early_trap(&self, msg: String) -> ! {
TRAP_EARLY_DATA.with(|cell| cell.set(Some(msg)));
trigger_trap()
}
}
pub struct Caller { pub struct Caller {
func_export_set: HashSet<FuncIndex>, func_export_set: HashSet<FuncIndex>,
handler_data: HandlerData, handler_data: HandlerData,
@ -118,6 +131,10 @@ impl ProtectedCaller for Caller {
}) })
.collect()) .collect())
} }
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
Box::new(Trapper)
}
} }
fn get_func_from_index( fn get_func_from_index(

View File

@ -9,7 +9,7 @@
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here //! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
//! unless you have memory unsafety elsewhere in your code. //! unless you have memory unsafety elsewhere in your code.
//! //!
use crate::relocation::{TrapCode, TrapData, TrapSink}; use crate::relocation::{TrapCode, TrapData};
use crate::signal::HandlerData; use crate::signal::HandlerData;
use libc::{c_int, c_void, siginfo_t}; use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{ use nix::sys::signal::{
@ -60,6 +60,12 @@ thread_local! {
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());
} }
pub unsafe fn trigger_trap() -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
longjmp(jmp_buf as *mut c_void, 0)
}
pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult<T> { pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult<T> {
unsafe { unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
@ -72,54 +78,59 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
let signum = setjmp(jmp_buf as *mut _); 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, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
if let Some(TrapData { if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
trapcode, Err(RuntimeError::User { msg })
srcloc: _, } else {
}) = handler_data.lookup(inst_ptr) let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
{
Err(match Signal::from_c_int(signum) { if let Some(TrapData {
Ok(SIGILL) => match trapcode { trapcode,
TrapCode::BadSignature => RuntimeError::IndirectCallSignature { srcloc: _,
table: TableIndex::new(0), }) = handler_data.lookup(inst_ptr)
{
Err(match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode {
TrapCode::BadSignature => RuntimeError::IndirectCallSignature {
table: TableIndex::new(0),
},
TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull {
table: TableIndex::new(0),
},
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0),
addr: None,
},
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
table: TableIndex::new(0),
},
_ => RuntimeError::Unknown {
msg: "unknown trap".to_string(),
},
}, },
TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull { Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess {
table: TableIndex::new(0),
},
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0), memory: MemoryIndex::new(0),
addr: None, addr: None,
}, },
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds { Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
table: TableIndex::new(0), _ => unimplemented!(),
}, }
_ => RuntimeError::Unknown { .into())
msg: "unknown trap".to_string(), } else {
}, let signal = match Signal::from_c_int(signum) {
}, Ok(SIGFPE) => "floating-point exception",
Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess { Ok(SIGILL) => "illegal instruction",
memory: MemoryIndex::new(0), Ok(SIGSEGV) => "segmentation violation",
addr: None, Ok(SIGBUS) => "bus error",
}, Err(_) => "error while getting the Signal",
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation, _ => "unkown trapped signal",
_ => unimplemented!(), };
// When the trap-handler is fully implemented, this will return more information.
Err(RuntimeError::Unknown {
msg: format!("trap at {:p} - {}", faulting_addr, signal),
}
.into())
} }
.into())
} else {
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
Ok(SIGILL) => "illegal instruction",
Ok(SIGSEGV) => "segmentation violation",
Ok(SIGBUS) => "bus error",
Err(_) => "error while getting the Signal",
_ => "unkown trapped signal",
};
// When the trap-handler is fully implemented, this will return more information.
Err(RuntimeError::Unknown {
msg: format!("trap at {:p} - {}", faulting_addr, signal),
}
.into())
} }
} else { } else {
let ret = f(); // TODO: Switch stack? let ret = f(); // TODO: Switch stack?

View File

@ -82,6 +82,12 @@ pub trait ProtectedCaller: Send + Sync {
vmctx: *mut vm::Ctx, vmctx: *mut vm::Ctx,
_: Token, _: Token,
) -> RuntimeResult<Vec<Value>>; ) -> RuntimeResult<Vec<Value>>;
fn get_early_trapper(&self) -> Box<dyn UserTrapper>;
}
pub trait UserTrapper {
unsafe fn do_early_trap(&self, msg: String) -> !;
} }
pub trait FuncResolver: Send + Sync { pub trait FuncResolver: Send + Sync {

View File

@ -97,6 +97,9 @@ pub enum RuntimeError {
table: TableIndex, table: TableIndex,
}, },
IllegalArithmeticOperation, IllegalArithmeticOperation,
User {
msg: String,
},
Unknown { Unknown {
msg: String, msg: String,
}, },

View File

@ -7,7 +7,6 @@ use crate::{
import::{ImportObject, LikeNamespace}, import::{ImportObject, LikeNamespace},
memory::Memory, memory::Memory,
module::{ExportIndex, Module, ModuleInner}, module::{ExportIndex, Module, ModuleInner},
sig_registry::SigRegistry,
table::Table, table::Table,
typed_func::{Func, Safe, WasmTypeList}, typed_func::{Func, Safe, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value}, types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},

View File

@ -3,6 +3,7 @@ use crate::{
error::Result, error::Result,
import::ImportObject, import::ImportObject,
structures::{Map, TypedIndex}, structures::{Map, TypedIndex},
typed_func::EARLY_TRAPPER,
types::{ types::{
FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer,
@ -63,6 +64,10 @@ pub struct Module(#[doc(hidden)] pub Arc<ModuleInner>);
impl Module { impl Module {
pub(crate) fn new(inner: Arc<ModuleInner>) -> Self { pub(crate) fn new(inner: Arc<ModuleInner>) -> Self {
unsafe {
EARLY_TRAPPER
.with(|ucell| *ucell.get() = Some(inner.protected_caller.get_early_trapper()));
}
Module(inner) Module(inner)
} }

View File

@ -1,11 +1,16 @@
use crate::{ use crate::{
backend::UserTrapper,
error::RuntimeError, error::RuntimeError,
export::{Context, Export, FuncPointer}, export::{Context, Export, FuncPointer},
import::IsExport, import::IsExport,
types::{FuncSig, Type, WasmExternType}, types::{FuncSig, Type, WasmExternType},
vm::Ctx, vm::Ctx,
}; };
use std::{marker::PhantomData, mem, ptr, sync::Arc}; use std::{cell::UnsafeCell, fmt, marker::PhantomData, mem, panic, ptr, sync::Arc};
thread_local! {
pub static EARLY_TRAPPER: UnsafeCell<Option<Box<dyn UserTrapper>>> = UnsafeCell::new(None);
}
pub trait Safeness {} pub trait Safeness {}
pub struct Safe; pub struct Safe;
@ -28,9 +33,44 @@ where
Args: WasmTypeList, Args: WasmTypeList,
Rets: WasmTypeList, Rets: WasmTypeList,
{ {
fn to_raw(self) -> *const (); fn to_raw(&self) -> *const ();
} }
pub trait TrapEarly<Rets>
where
Rets: WasmTypeList,
{
fn report(self) -> Result<Rets, String>;
}
impl<Rets> TrapEarly<Rets> for Rets
where
Rets: WasmTypeList,
{
fn report(self) -> Result<Rets, String> {
Ok(self)
}
}
impl<Rets, E> TrapEarly<Rets> for Result<Rets, E>
where
Rets: WasmTypeList,
E: fmt::Debug,
{
fn report(self) -> Result<Rets, String> {
self.map_err(|err| format!("Error: {:?}", err))
}
}
// pub fn Func<'a, Args, Rets, F>(f: F) -> Func<'a, Args, Rets, Unsafe>
// where
// Args: WasmTypeList,
// Rets: WasmTypeList,
// F: ExternalFunction<Args, Rets>
// {
// Func::new(f)
// }
pub struct Func<'a, Args = (), Rets = (), Safety: Safeness = Safe> { pub struct Func<'a, Args = (), Rets = (), Safety: Safeness = Safe> {
f: *const (), f: *const (),
ctx: *mut Ctx, ctx: *mut Ctx,
@ -143,18 +183,41 @@ macro_rules! impl_traits {
} }
} }
impl< $( $x: WasmExternType, )* Rets: WasmTypeList, FN: Fn( $( $x, )* &mut Ctx) -> Rets> ExternalFunction<($( $x ),*), Rets> for FN { impl< $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( $( $x, )* &mut Ctx) -> Trap> ExternalFunction<($( $x ),*), Rets> for FN {
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn to_raw(self) -> *const () { fn to_raw(&self) -> *const () {
assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`."); assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`.");
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, FN: Fn( $( $x, )* &mut Ctx) -> Rets>( $( $x: $x, )* ctx: &mut Ctx) -> Rets::CStruct { extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( $( $x, )* &mut Ctx) -> Trap>( $( $x: $x, )* ctx: &mut Ctx) -> Rets::CStruct {
let f: FN = unsafe { mem::transmute_copy(&()) }; let f: FN = unsafe { mem::transmute_copy(&()) };
let rets = f( $( $x, )* ctx);
rets.into_c_struct() let msg = match panic::catch_unwind(panic::AssertUnwindSafe(|| {
f( $( $x, )* ctx).report()
})) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => err,
Err(err) => {
if let Some(s) = err.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = err.downcast_ref::<String>() {
s.clone()
} else {
"a panic occurred, but no additional information is available".to_string()
}
},
};
unsafe {
if let Some(early_trapper) = &*EARLY_TRAPPER.with(|ucell| ucell.get()) {
early_trapper.do_early_trap(msg)
} else {
eprintln!("panic handling not setup");
std::process::exit(1)
}
}
} }
wrap::<$( $x, )* Rets, Self> as *const () wrap::<$( $x, )* Rets, Trap, Self> as *const ()
} }
} }

View File

@ -493,7 +493,7 @@ mod vm_ctx_tests {
fn generate_module() -> ModuleInner { fn generate_module() -> ModuleInner {
use super::Func; use super::Func;
use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token}; use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper};
use crate::error::RuntimeResult; use crate::error::RuntimeResult;
use crate::types::{FuncIndex, LocalFuncIndex, Value}; use crate::types::{FuncIndex, LocalFuncIndex, Value};
use hashbrown::HashMap; use hashbrown::HashMap;
@ -520,6 +520,9 @@ mod vm_ctx_tests {
) -> RuntimeResult<Vec<Value>> { ) -> RuntimeResult<Vec<Value>> {
Ok(vec![]) Ok(vec![])
} }
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
unimplemented!()
}
} }
ModuleInner { ModuleInner {

View File

@ -3,7 +3,7 @@ use wabt::wat2wasm;
use wasmer_clif_backend::CraneliftCompiler; use wasmer_clif_backend::CraneliftCompiler;
use wasmer_runtime_core::{ use wasmer_runtime_core::{
cache::Cache, cache::Cache,
error::Result, error,
global::Global, global::Global,
memory::Memory, memory::Memory,
prelude::*, prelude::*,
@ -14,7 +14,7 @@ use wasmer_runtime_core::{
static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm"); static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm");
fn main() -> Result<()> { fn main() -> error::Result<()> {
let compiler = CraneliftCompiler::new(); let compiler = CraneliftCompiler::new();
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed"); let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
@ -61,14 +61,14 @@ fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn print_num(n: i32, ctx: &mut vm::Ctx) -> i32 { fn print_num(n: i32, ctx: &mut vm::Ctx) -> Result<i32, ()> {
println!("print_num({})", n); println!("print_num({})", n);
let memory: &Memory = ctx.memory(0); let memory: &Memory = ctx.memory(0);
let a: i32 = memory.view()[0].get(); let a: i32 = memory.view()[0].get();
a + n + 1 Ok(a + n + 1)
} }
static IMPORT_MODULE: &str = r#" static IMPORT_MODULE: &str = r#"