mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-12 08:31:21 +00:00
merge vm_refactor_trap_handling into vm_refactor
This commit is contained in:
@ -1,4 +1,11 @@
|
||||
use crate::{error::CompileResult, module::ModuleInner, types::LocalFuncIndex, vm};
|
||||
use crate::{
|
||||
backing::ImportBacking,
|
||||
error::CompileResult,
|
||||
error::RuntimeResult,
|
||||
module::ModuleInner,
|
||||
types::{FuncIndex, LocalFuncIndex, Value},
|
||||
vm,
|
||||
};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub mod sys {
|
||||
@ -6,12 +13,59 @@ pub mod sys {
|
||||
}
|
||||
pub use crate::sig_registry::SigRegistry;
|
||||
|
||||
/// This type cannot be constructed from
|
||||
/// outside the runtime crate.
|
||||
pub struct Token {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub(crate) fn generate() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Compiler {
|
||||
/// Compiles a `Module` from WebAssembly binary format
|
||||
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner>;
|
||||
/// Compiles a `Module` from WebAssembly binary format.
|
||||
/// The `CompileToken` parameter ensures that this can only
|
||||
/// be called from inside the runtime.
|
||||
fn compile(&self, wasm: &[u8], _: Token) -> CompileResult<ModuleInner>;
|
||||
}
|
||||
|
||||
/// The functionality exposed by this trait is expected to be used
|
||||
/// for calling functions exported by a webassembly module from
|
||||
/// host code only.
|
||||
pub trait ProtectedCaller {
|
||||
/// This calls the exported function designated by `local_func_index`.
|
||||
/// Important to note, this supports calling imported functions that are
|
||||
/// then exported.
|
||||
///
|
||||
/// It's invalid to attempt to call a local function that isn't exported and
|
||||
/// the implementation is expected to check for that. The implementation
|
||||
/// is also expected to check for correct parameter types and correct
|
||||
/// parameter number.
|
||||
///
|
||||
/// The `returns` parameter is filled with dummy values when passed in and upon function
|
||||
/// return, will be filled with the return values of the wasm function, as long as the
|
||||
/// call completed successfully.
|
||||
///
|
||||
/// The existance of the Token parameter ensures that this can only be called from
|
||||
/// within the runtime crate.
|
||||
fn call(
|
||||
&self,
|
||||
module: &ModuleInner,
|
||||
func_index: FuncIndex,
|
||||
params: &[Value],
|
||||
returns: &mut [Value],
|
||||
import_backing: &ImportBacking,
|
||||
vmctx: *mut vm::Ctx,
|
||||
_: Token,
|
||||
) -> RuntimeResult<()>;
|
||||
}
|
||||
|
||||
pub trait FuncResolver {
|
||||
/// This returns a pointer to the function designated by the `local_func_index`
|
||||
/// parameter.
|
||||
fn get(
|
||||
&self,
|
||||
module: &ModuleInner,
|
||||
|
@ -346,6 +346,10 @@ impl ImportBacking {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn imported_func(&self, func_index: ImportedFuncIndex) -> vm::ImportedFunc {
|
||||
self.functions[func_index].clone()
|
||||
}
|
||||
|
||||
pub fn imported_memory(&self, memory_index: ImportedMemoryIndex) -> vm::ImportedMemory {
|
||||
self.memories[memory_index].clone()
|
||||
}
|
||||
@ -408,6 +412,10 @@ fn import_functions(
|
||||
name: name.clone(),
|
||||
});
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,6 +483,10 @@ fn import_memories(
|
||||
name: name.clone(),
|
||||
});
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
@ -540,6 +552,10 @@ fn import_tables(
|
||||
name: name.clone(),
|
||||
});
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,6 +612,10 @@ fn import_globals(
|
||||
name: name.clone(),
|
||||
});
|
||||
}
|
||||
None => Err(LinkError::ImportNotFound {
|
||||
namespace: namespace.clone(),
|
||||
name: name.clone(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,11 @@ use crate::types::{FuncSig, GlobalDesc, Memory, MemoryIndex, Table, TableIndex,
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Box<Error>>;
|
||||
pub type CompileResult<T> = std::result::Result<T, Box<CompileError>>;
|
||||
<<<<<<< HEAD
|
||||
pub type LinkResult<T> = std::result::Result<T, Vec<LinkError>>;
|
||||
=======
|
||||
pub type LinkResult<T> = std::result::Result<T, Box<LinkError>>;
|
||||
>>>>>>> feature/vm_refactor_trap_handler
|
||||
pub type RuntimeResult<T> = std::result::Result<T, Box<RuntimeError>>;
|
||||
pub type CallResult<T> = std::result::Result<T, Box<CallError>>;
|
||||
|
||||
@ -80,8 +84,10 @@ impl PartialEq for LinkError {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RuntimeError {
|
||||
OutOfBoundsAccess { memory: MemoryIndex, addr: u32 },
|
||||
TableOutOfBounds { table: TableIndex },
|
||||
IndirectCallSignature { table: TableIndex },
|
||||
IndirectCallToNull { table: TableIndex },
|
||||
IllegalArithmeticOperation,
|
||||
Unknown { msg: String },
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::recovery::call_protected;
|
||||
use crate::{
|
||||
backend::Token,
|
||||
backing::{ImportBacking, LocalBacking},
|
||||
error::{CallError, CallResult, Result},
|
||||
export::{
|
||||
@ -9,13 +9,12 @@ use crate::{
|
||||
module::{ExportIndex, Module, ModuleInner},
|
||||
types::{
|
||||
FuncIndex, FuncSig, GlobalDesc, GlobalIndex, LocalOrImport, Memory, MemoryIndex, Table,
|
||||
TableIndex, Type, Value,
|
||||
TableIndex, Value,
|
||||
},
|
||||
vm,
|
||||
};
|
||||
use libffi::high::{arg as libffi_arg, call as libffi_call, CodePtr};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::{iter, mem};
|
||||
|
||||
pub(crate) struct InstanceInner {
|
||||
#[allow(dead_code)]
|
||||
@ -69,11 +68,11 @@ impl Instance {
|
||||
|
||||
/// Call an exported webassembly function given the export name.
|
||||
/// Pass arguments by wrapping each one in the `Value` enum.
|
||||
/// The returned value is also returned in a `Value`.
|
||||
/// The returned values are also each wrapped in a `Value`.
|
||||
///
|
||||
/// This will eventually return `Result<Option<Vec<Value>>, String>` in
|
||||
/// order to support multi-value returns.
|
||||
pub fn call(&mut self, name: &str, args: &[Value]) -> CallResult<Option<Value>> {
|
||||
/// This returns `CallResult<Vec<Value>>` in order to support
|
||||
/// the future multi-value returns webassembly feature.
|
||||
pub fn call(&mut self, name: &str, args: &[Value]) -> CallResult<Vec<Value>> {
|
||||
let export_index =
|
||||
self.module
|
||||
.exports
|
||||
@ -104,23 +103,13 @@ impl Instance {
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn call_with_index(
|
||||
&mut self,
|
||||
func_index: FuncIndex,
|
||||
args: &[Value],
|
||||
) -> CallResult<Option<Value>> {
|
||||
let (func_ref, ctx, signature) = self.inner.get_func_from_index(&self.module, func_index);
|
||||
|
||||
let func_ptr = CodePtr::from_ptr(func_ref.inner() as _);
|
||||
let vmctx_ptr = match ctx {
|
||||
Context::External(vmctx) => vmctx,
|
||||
Context::Internal => &mut *self.inner.vmctx,
|
||||
};
|
||||
|
||||
assert!(
|
||||
signature.returns.len() <= 1,
|
||||
"multi-value returns not yet supported"
|
||||
);
|
||||
fn call_with_index(&mut self, func_index: FuncIndex, args: &[Value]) -> CallResult<Vec<Value>> {
|
||||
let sig_index = *self
|
||||
.module
|
||||
.func_assoc
|
||||
.get(func_index)
|
||||
.expect("broken invariant, incorrect func index");
|
||||
let signature = self.module.sig_registry.lookup_func_sig(sig_index);
|
||||
|
||||
if !signature.check_sig(args) {
|
||||
Err(CallError::Signature {
|
||||
@ -129,35 +118,29 @@ impl Instance {
|
||||
})?
|
||||
}
|
||||
|
||||
let libffi_args: Vec<_> = args
|
||||
.iter()
|
||||
.map(|val| match val {
|
||||
Value::I32(ref x) => libffi_arg(x),
|
||||
Value::I64(ref x) => libffi_arg(x),
|
||||
Value::F32(ref x) => libffi_arg(x),
|
||||
Value::F64(ref x) => libffi_arg(x),
|
||||
})
|
||||
.chain(iter::once(libffi_arg(&vmctx_ptr)))
|
||||
.collect();
|
||||
// Create an output vector that's full of dummy values.
|
||||
let mut returns = vec![Value::I32(0); signature.returns.len()];
|
||||
|
||||
Ok(call_protected(|| {
|
||||
signature
|
||||
.returns
|
||||
.first()
|
||||
.map(|ty| match ty {
|
||||
Type::I32 => Value::I32(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
Type::I64 => Value::I64(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
Type::F32 => Value::F32(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
Type::F64 => Value::F64(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
})
|
||||
.or_else(|| {
|
||||
// call with no returns
|
||||
unsafe {
|
||||
libffi_call::<()>(func_ptr, &libffi_args);
|
||||
}
|
||||
None
|
||||
})
|
||||
})?)
|
||||
let vmctx = match func_index.local_or_import(&self.module) {
|
||||
LocalOrImport::Local(_) => &mut *self.inner.vmctx,
|
||||
LocalOrImport::Import(imported_func_index) => {
|
||||
self.inner.import_backing.functions[imported_func_index].vmctx
|
||||
}
|
||||
};
|
||||
|
||||
let token = Token::generate();
|
||||
|
||||
self.module.protected_caller.call(
|
||||
&self.module,
|
||||
func_index,
|
||||
args,
|
||||
&mut returns,
|
||||
&self.inner.import_backing,
|
||||
vmctx,
|
||||
token,
|
||||
)?;
|
||||
|
||||
Ok(returns)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,7 @@ pub mod import;
|
||||
pub mod instance;
|
||||
pub mod memory;
|
||||
pub mod module;
|
||||
mod recovery;
|
||||
mod sig_registry;
|
||||
mod sighandler;
|
||||
pub mod structures;
|
||||
mod sys;
|
||||
pub mod table;
|
||||
@ -31,9 +29,10 @@ pub use self::instance::Instance;
|
||||
pub use self::module::Module;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Compile a WebAssembly module using the provided compiler.
|
||||
/// Compile a webassembly module using the provided compiler.
|
||||
pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> CompileResult<module::Module> {
|
||||
let token = backend::Token::generate();
|
||||
compiler
|
||||
.compile(wasm)
|
||||
.compile(wasm, token)
|
||||
.map(|inner| module::Module::new(Rc::new(inner)))
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
backend::FuncResolver,
|
||||
backend::{FuncResolver, ProtectedCaller},
|
||||
error::Result,
|
||||
import::Imports,
|
||||
sig_registry::SigRegistry,
|
||||
@ -18,6 +18,8 @@ use std::rc::Rc;
|
||||
#[doc(hidden)]
|
||||
pub struct ModuleInner {
|
||||
pub func_resolver: Box<dyn FuncResolver>,
|
||||
pub protected_caller: Box<dyn ProtectedCaller>,
|
||||
|
||||
// This are strictly local and the typsystem ensures that.
|
||||
pub memories: Map<LocalMemoryIndex, Memory>,
|
||||
pub globals: Map<LocalGlobalIndex, Global>,
|
||||
|
@ -1,79 +0,0 @@
|
||||
//! When a WebAssembly module triggers any traps, we perform recovery here.
|
||||
//!
|
||||
//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling
|
||||
//! 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.
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, RuntimeResult},
|
||||
sighandler::install_sighandler,
|
||||
};
|
||||
use nix::libc::siginfo_t;
|
||||
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::sync::Once;
|
||||
|
||||
extern "C" {
|
||||
pub fn setjmp(env: *mut ::nix::libc::c_void) -> ::nix::libc::c_int;
|
||||
fn longjmp(env: *mut ::nix::libc::c_void, val: ::nix::libc::c_int) -> !;
|
||||
}
|
||||
|
||||
const SETJMP_BUFFER_LEN: usize = 27;
|
||||
pub static SIGHANDLER_INIT: Once = Once::new();
|
||||
|
||||
thread_local! {
|
||||
pub static SETJMP_BUFFER: UnsafeCell<[::nix::libc::c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
|
||||
pub static CAUGHT_ADDRESS: Cell<usize> = Cell::new(0);
|
||||
}
|
||||
|
||||
pub fn call_protected<T>(f: impl FnOnce() -> T) -> RuntimeResult<T> {
|
||||
unsafe {
|
||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||
let prev_jmp_buf = *jmp_buf;
|
||||
|
||||
SIGHANDLER_INIT.call_once(|| {
|
||||
install_sighandler();
|
||||
});
|
||||
|
||||
let signum = setjmp(jmp_buf as *mut ::nix::libc::c_void);
|
||||
if signum != 0 {
|
||||
*jmp_buf = prev_jmp_buf;
|
||||
let addr = CAUGHT_ADDRESS.with(|cell| cell.get());
|
||||
|
||||
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 {:#x} - {}", addr, signal),
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
let ret = f(); // TODO: Switch stack?
|
||||
*jmp_buf = prev_jmp_buf;
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwinds to last protected_call.
|
||||
pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t) -> ! {
|
||||
// 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
|
||||
// temporarily disable the signal handlers to debug it.
|
||||
|
||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
|
||||
::std::process::abort();
|
||||
}
|
||||
// We only target macos at the moment as other ones might not have si_addr field
|
||||
#[cfg(target_os = "macos")]
|
||||
CAUGHT_ADDRESS.with(|cell| cell.set((*siginfo).si_addr as _));
|
||||
|
||||
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//! We install signal handlers to handle WebAssembly traps within
|
||||
//! our Rust code. Otherwise we will have errors that stop the Rust process
|
||||
//! such as `process didn't exit successfully: ... (signal: 8, SIGFPE: erroneous arithmetic operation)`
|
||||
//!
|
||||
//! Please read more about this here: https://github.com/CraneStation/wasmtime/issues/15
|
||||
//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622
|
||||
use crate::recovery;
|
||||
use nix::libc::{c_void, siginfo_t};
|
||||
use nix::sys::signal::{
|
||||
sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
|
||||
};
|
||||
|
||||
pub unsafe fn install_sighandler() {
|
||||
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 {
|
||||
recovery::do_unwind(signum, siginfo);
|
||||
}
|
||||
}
|
@ -159,7 +159,7 @@ pub trait LocalImport {
|
||||
#[rustfmt::skip]
|
||||
macro_rules! define_map_index {
|
||||
($ty:ident) => {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct $ty (u32);
|
||||
impl TypedIndex for $ty {
|
||||
#[doc(hidden)]
|
||||
|
Reference in New Issue
Block a user