Add support for a custom, per-instance signal handler (#620)
* Per Instance signal handler * add custom signal handler test * add instance signal handling to callable.rs * extend signal handler test to test callable.rs * test multiple instances, multiple signal handlers * support more than one current instance import_calling_export.rs is a good example of why this is needed: execution switches from one instance to another before the first one has finished running * add another custom signal handler test case * move and update custom signal handler tests * fmt * fix libc version to 0.2 * call the correct instance signal handler We keep a stack of instances so should call last() not first(). * move custom signal handler test to top level dir * windows/mac signal handling wip * os-specific signal handling wip * disable custom signal handler test on windows * fmt * unify signal handling on mac and linux
This commit is contained in:
@@ -20,6 +20,11 @@ wasmparser = { version = "0.45.1", default-features = false }
|
|||||||
target-lexicon = { version = "0.9.0", default-features = false }
|
target-lexicon = { version = "0.9.0", default-features = false }
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
region = "2.0.0"
|
region = "2.0.0"
|
||||||
|
libc = "0.2"
|
||||||
|
cfg-if = "0.1.9"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
winapi = "0.3.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# for wasmtime.rs
|
# for wasmtime.rs
|
||||||
|
|||||||
@@ -151,11 +151,13 @@ impl WrappedCallable for WasmtimeFn {
|
|||||||
|
|
||||||
// Call the trampoline.
|
// Call the trampoline.
|
||||||
if let Err(message) = unsafe {
|
if let Err(message) = unsafe {
|
||||||
|
self.instance.with_signals_on(|| {
|
||||||
wasmtime_runtime::wasmtime_call_trampoline(
|
wasmtime_runtime::wasmtime_call_trampoline(
|
||||||
vmctx,
|
vmctx,
|
||||||
exec_code_buf,
|
exec_code_buf,
|
||||||
values_vec.as_mut_ptr() as *mut u8,
|
values_vec.as_mut_ptr() as *mut u8,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
} {
|
} {
|
||||||
let trap =
|
let trap =
|
||||||
take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message)));
|
take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message)));
|
||||||
|
|||||||
@@ -161,3 +161,37 @@ impl Instance {
|
|||||||
instance_handle.lookup("memory")
|
instance_handle.lookup("memory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OS-specific signal handling
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
impl Instance {
|
||||||
|
/// The signal handler must be
|
||||||
|
/// [async-signal-safe](http://man7.org/linux/man-pages/man7/signal-safety.7.html).
|
||||||
|
pub fn set_signal_handler<H>(&mut self, handler: H)
|
||||||
|
where
|
||||||
|
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
|
||||||
|
{
|
||||||
|
self.instance_handle.set_signal_handler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if #[cfg(target_os = "windows")] {
|
||||||
|
impl Instance {
|
||||||
|
pub fn set_signal_handler<H>(&mut self, handler: H)
|
||||||
|
where
|
||||||
|
H: 'static + Fn(winapi::um::winnt::EXCEPTION_POINTERS) -> bool,
|
||||||
|
{
|
||||||
|
self.instance_handle.set_signal_handler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if #[cfg(target_os = "macos")] {
|
||||||
|
impl Instance {
|
||||||
|
pub fn set_signal_handler<H>(&mut self, handler: H)
|
||||||
|
where
|
||||||
|
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
|
||||||
|
{
|
||||||
|
self.instance_handle.set_signal_handler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -192,11 +192,13 @@ pub fn invoke(
|
|||||||
|
|
||||||
// Call the trampoline.
|
// Call the trampoline.
|
||||||
if let Err(message) = unsafe {
|
if let Err(message) = unsafe {
|
||||||
|
instance.with_signals_on(|| {
|
||||||
wasmtime_call_trampoline(
|
wasmtime_call_trampoline(
|
||||||
callee_vmctx,
|
callee_vmctx,
|
||||||
exec_code_buf,
|
exec_code_buf,
|
||||||
values_vec.as_mut_ptr() as *mut u8,
|
values_vec.as_mut_ptr() as *mut u8,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
} {
|
} {
|
||||||
return Ok(ActionOutcome::Trapped { message });
|
return Ok(ActionOutcome::Trapped { message });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ memoffset = "0.5.3"
|
|||||||
indexmap = "1.0.2"
|
indexmap = "1.0.2"
|
||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
more-asserts = "0.2.1"
|
more-asserts = "0.2.1"
|
||||||
|
cfg-if = "0.1.9"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] }
|
winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] }
|
||||||
|
|||||||
@@ -467,10 +467,14 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exception)
|
|||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool handled = InstanceSignalHandler(exception);
|
||||||
|
|
||||||
|
if (!handled) {
|
||||||
if (!HandleTrap(exception->ContextRecord,
|
if (!HandleTrap(exception->ContextRecord,
|
||||||
record->ExceptionCode == EXCEPTION_STACK_OVERFLOW)) {
|
record->ExceptionCode == EXCEPTION_STACK_OVERFLOW)) {
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
}
|
}
|
||||||
@@ -633,11 +637,17 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context)
|
|||||||
if (!sAlreadyHandlingTrap) {
|
if (!sAlreadyHandlingTrap) {
|
||||||
AutoHandlingTrap aht;
|
AutoHandlingTrap aht;
|
||||||
assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL);
|
assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL);
|
||||||
|
|
||||||
|
if (InstanceSignalHandler(signum, info, (ucontext_t*) context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (HandleTrap(static_cast<CONTEXT*>(context), false)) {
|
if (HandleTrap(static_cast<CONTEXT*>(context), false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct sigaction* previousSignal = nullptr;
|
struct sigaction* previousSignal = nullptr;
|
||||||
switch (signum) {
|
switch (signum) {
|
||||||
case SIGSEGV: previousSignal = &sPrevSIGSEGVHandler; break;
|
case SIGSEGV: previousSignal = &sPrevSIGSEGVHandler; break;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@@ -15,6 +17,17 @@ int8_t CheckIfTrapAtAddress(const uint8_t* pc);
|
|||||||
// Record the Trap code and wasm bytecode offset in TLS somewhere
|
// Record the Trap code and wasm bytecode offset in TLS somewhere
|
||||||
void RecordTrap(const uint8_t* pc, bool reset_guard_page);
|
void RecordTrap(const uint8_t* pc, bool reset_guard_page);
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
bool InstanceSignalHandler(LPEXCEPTION_POINTERS);
|
||||||
|
#elif defined(USE_APPLE_MACH_PORTS)
|
||||||
|
bool InstanceSignalHandler(int, siginfo_t *, void *);
|
||||||
|
#else
|
||||||
|
#include <sys/ucontext.h>
|
||||||
|
bool InstanceSignalHandler(int, siginfo_t *, ucontext_t *);
|
||||||
|
#endif
|
||||||
|
|
||||||
void* EnterScope(void*);
|
void* EnterScope(void*);
|
||||||
void LeaveScope(void*);
|
void LeaveScope(void*);
|
||||||
void* GetScope(void);
|
void* GetScope(void);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use std::borrow::Borrow;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::ptr::NonNull;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{mem, ptr, slice};
|
use std::{mem, ptr, slice};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -32,6 +33,11 @@ use wasmtime_environ::wasm::{
|
|||||||
};
|
};
|
||||||
use wasmtime_environ::{DataInitializer, Module, TableElements, VMOffsets};
|
use wasmtime_environ::{DataInitializer, Module, TableElements, VMOffsets};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// A stack of currently-running `Instance`s, if any.
|
||||||
|
pub(crate) static CURRENT_INSTANCE: RefCell<Vec<NonNull<Instance>>> = RefCell::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
fn signature_id(
|
fn signature_id(
|
||||||
vmctx: &VMContext,
|
vmctx: &VMContext,
|
||||||
offsets: &VMOffsets,
|
offsets: &VMOffsets,
|
||||||
@@ -175,6 +181,97 @@ fn global_mut<'vmctx>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||||
|
pub type SignalHandler = dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool;
|
||||||
|
|
||||||
|
pub fn signal_handler_none(
|
||||||
|
_signum: libc::c_int,
|
||||||
|
_siginfo: *const libc::siginfo_t,
|
||||||
|
_context: *const libc::c_void,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn InstanceSignalHandler(
|
||||||
|
signum: libc::c_int,
|
||||||
|
siginfo: *mut libc::siginfo_t,
|
||||||
|
context: *mut libc::c_void,
|
||||||
|
) -> bool {
|
||||||
|
CURRENT_INSTANCE.with(|current_instance| {
|
||||||
|
let current_instance = current_instance
|
||||||
|
.try_borrow()
|
||||||
|
.expect("borrow current instance");
|
||||||
|
if current_instance.is_empty() {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
let f = ¤t_instance
|
||||||
|
.last()
|
||||||
|
.expect("current instance not none")
|
||||||
|
.as_ref()
|
||||||
|
.signal_handler;
|
||||||
|
f(signum, siginfo, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl InstanceHandle {
|
||||||
|
/// Set a custom signal handler
|
||||||
|
pub fn set_signal_handler<H>(&mut self, handler: H)
|
||||||
|
where
|
||||||
|
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
|
||||||
|
{
|
||||||
|
self.instance_mut().signal_handler = Box::new(handler) as Box<SignalHandler>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if #[cfg(target_os = "windows")] {
|
||||||
|
pub type SignalHandler = dyn Fn(winapi::um::winnt::EXCEPTION_POINTERS) -> bool;
|
||||||
|
|
||||||
|
pub fn signal_handler_none(
|
||||||
|
_exception_info: winapi::um::winnt::EXCEPTION_POINTERS
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn InstanceSignalHandler(
|
||||||
|
exception_info: winapi::um::winnt::EXCEPTION_POINTERS
|
||||||
|
) -> bool {
|
||||||
|
CURRENT_INSTANCE.with(|current_instance| {
|
||||||
|
let current_instance = current_instance
|
||||||
|
.try_borrow()
|
||||||
|
.expect("borrow current instance");
|
||||||
|
if current_instance.is_empty() {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
let f = ¤t_instance
|
||||||
|
.last()
|
||||||
|
.expect("current instance not none")
|
||||||
|
.as_ref()
|
||||||
|
.signal_handler;
|
||||||
|
f(exception_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceHandle {
|
||||||
|
/// Set a custom signal handler
|
||||||
|
pub fn set_signal_handler<H>(&mut self, handler: H)
|
||||||
|
where
|
||||||
|
H: 'static + Fn(winapi::um::winnt::EXCEPTION_POINTERS) -> bool,
|
||||||
|
{
|
||||||
|
self.instance_mut().signal_handler = Box::new(handler) as Box<SignalHandler>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A WebAssembly instance.
|
/// A WebAssembly instance.
|
||||||
///
|
///
|
||||||
/// This is repr(C) to ensure that the vmctx field is last.
|
/// This is repr(C) to ensure that the vmctx field is last.
|
||||||
@@ -217,6 +314,9 @@ pub(crate) struct Instance {
|
|||||||
/// Optional image of JIT'ed code for debugger registration.
|
/// Optional image of JIT'ed code for debugger registration.
|
||||||
dbg_jit_registration: Option<Rc<GdbJitImageRegistration>>,
|
dbg_jit_registration: Option<Rc<GdbJitImageRegistration>>,
|
||||||
|
|
||||||
|
/// Handler run when `SIGBUS`, `SIGFPE`, `SIGILL`, or `SIGSEGV` are caught by the instance thread.
|
||||||
|
signal_handler: Box<SignalHandler>,
|
||||||
|
|
||||||
/// Additional context used by compiled wasm code. This field is last, and
|
/// Additional context used by compiled wasm code. This field is last, and
|
||||||
/// represents a dynamically-sized array that extends beyond the nominal
|
/// represents a dynamically-sized array that extends beyond the nominal
|
||||||
/// end of the struct (similar to a flexible array member).
|
/// end of the struct (similar to a flexible array member).
|
||||||
@@ -647,6 +747,28 @@ pub struct InstanceHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceHandle {
|
impl InstanceHandle {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn with_signals_on<F, R>(&self, action: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
{
|
||||||
|
CURRENT_INSTANCE.with(|current_instance| {
|
||||||
|
current_instance
|
||||||
|
.borrow_mut()
|
||||||
|
.push(unsafe { NonNull::new_unchecked(self.instance) });
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = action();
|
||||||
|
|
||||||
|
CURRENT_INSTANCE.with(|current_instance| {
|
||||||
|
let mut current_instance = current_instance.borrow_mut();
|
||||||
|
assert!(!current_instance.is_empty());
|
||||||
|
current_instance.pop();
|
||||||
|
});
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new `InstanceHandle` pointing at a new `Instance`.
|
/// Create a new `InstanceHandle` pointing at a new `Instance`.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
module: Rc<Module>,
|
module: Rc<Module>,
|
||||||
@@ -699,6 +821,7 @@ impl InstanceHandle {
|
|||||||
finished_functions,
|
finished_functions,
|
||||||
dbg_jit_registration,
|
dbg_jit_registration,
|
||||||
host_state,
|
host_state,
|
||||||
|
signal_handler: Box::new(signal_handler_none) as Box<SignalHandler>,
|
||||||
vmctx: VMContext {},
|
vmctx: VMContext {},
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
282
tests/custom_signal_handler.rs
Normal file
282
tests/custom_signal_handler.rs
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
mod tests {
|
||||||
|
use core::cell::Ref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use wasmtime::*;
|
||||||
|
use wasmtime_interface_types::{ModuleData, Value};
|
||||||
|
|
||||||
|
fn invoke_export(
|
||||||
|
instance: &HostRef<Instance>,
|
||||||
|
data: &[u8],
|
||||||
|
func_name: &str,
|
||||||
|
) -> Result<Vec<Value>, anyhow::Error> {
|
||||||
|
ModuleData::new(&data)
|
||||||
|
.expect("module data")
|
||||||
|
.invoke_export(instance, func_name, &[])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate "memory" export, get base address and size and set memory protection to PROT_NONE
|
||||||
|
fn set_up_memory(instance: &HostRef<Instance>) -> (*mut u8, usize) {
|
||||||
|
let mem_export = instance.borrow().get_wasmtime_memory().expect("memory");
|
||||||
|
|
||||||
|
let (base, length) = if let wasmtime_runtime::Export::Memory {
|
||||||
|
definition,
|
||||||
|
vmctx: _,
|
||||||
|
memory: _,
|
||||||
|
} = mem_export
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let definition = std::ptr::read(definition);
|
||||||
|
(definition.base, definition.current_length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("expected memory");
|
||||||
|
};
|
||||||
|
|
||||||
|
// So we can later trigger SIGSEGV by performing a read
|
||||||
|
unsafe {
|
||||||
|
libc::mprotect(base as *mut libc::c_void, length, libc::PROT_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("memory: base={:?}, length={}", base, length);
|
||||||
|
|
||||||
|
(base, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_sigsegv(
|
||||||
|
base: *mut u8,
|
||||||
|
length: usize,
|
||||||
|
signum: libc::c_int,
|
||||||
|
siginfo: *const libc::siginfo_t,
|
||||||
|
) -> bool {
|
||||||
|
println!("Hello from instance signal handler!");
|
||||||
|
// SIGSEGV on Linux, SIGBUS on Mac
|
||||||
|
if libc::SIGSEGV == signum || libc::SIGBUS == signum {
|
||||||
|
let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
|
||||||
|
// Any signal from within module's memory we handle ourselves
|
||||||
|
let result = (si_addr as u64) < (base as u64) + (length as u64);
|
||||||
|
// Remove protections so the execution may resume
|
||||||
|
unsafe {
|
||||||
|
libc::mprotect(
|
||||||
|
base as *mut libc::c_void,
|
||||||
|
length,
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("signal handled: {}", result);
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
// Otherwise, we forward to wasmtime's signal handler.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_signal_handler_single_instance() {
|
||||||
|
let engine = HostRef::new(Engine::new(&Config::default()));
|
||||||
|
let store = HostRef::new(Store::new(&engine));
|
||||||
|
let data =
|
||||||
|
std::fs::read("tests/custom_signal_handler.wasm").expect("failed to read wasm file");
|
||||||
|
let module = HostRef::new(Module::new(&store, &data).expect("failed to create module"));
|
||||||
|
let instance = HostRef::new(
|
||||||
|
Instance::new(&store, &module, &[]).expect("failed to instantiate module"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (base, length) = set_up_memory(&instance);
|
||||||
|
instance
|
||||||
|
.borrow_mut()
|
||||||
|
.set_signal_handler(move |signum, siginfo, _| {
|
||||||
|
handle_sigsegv(base, length, signum, siginfo)
|
||||||
|
});
|
||||||
|
|
||||||
|
let exports = Ref::map(instance.borrow(), |instance| instance.exports());
|
||||||
|
assert!(!exports.is_empty());
|
||||||
|
|
||||||
|
// these invoke wasmtime_call_trampoline from action.rs
|
||||||
|
{
|
||||||
|
println!("calling read...");
|
||||||
|
let result = invoke_export(&instance, &data, "read").expect("read succeeded");
|
||||||
|
assert_eq!("123", result[0].clone().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
println!("calling read_out_of_bounds...");
|
||||||
|
let trap = invoke_export(&instance, &data, "read_out_of_bounds").unwrap_err();
|
||||||
|
assert!(trap.root_cause().to_string().starts_with(
|
||||||
|
"trapped: Ref(Trap { message: \"wasm trap: out of bounds memory access"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// these invoke wasmtime_call_trampoline from callable.rs
|
||||||
|
{
|
||||||
|
let read_func = exports[0]
|
||||||
|
.func()
|
||||||
|
.expect("expected a 'read' func in the module");
|
||||||
|
println!("calling read...");
|
||||||
|
let result = read_func
|
||||||
|
.borrow()
|
||||||
|
.call(&[])
|
||||||
|
.expect("expected function not to trap");
|
||||||
|
assert_eq!(123i32, result[0].clone().unwrap_i32());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let read_out_of_bounds_func = exports[1]
|
||||||
|
.func()
|
||||||
|
.expect("expected a 'read_out_of_bounds' func in the module");
|
||||||
|
println!("calling read_out_of_bounds...");
|
||||||
|
let trap = read_out_of_bounds_func.borrow().call(&[]).unwrap_err();
|
||||||
|
assert!(trap
|
||||||
|
.borrow()
|
||||||
|
.message()
|
||||||
|
.starts_with("wasm trap: out of bounds memory access"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_signal_handler_multiple_instances() {
|
||||||
|
let engine = HostRef::new(Engine::new(&Config::default()));
|
||||||
|
let store = HostRef::new(Store::new(&engine));
|
||||||
|
let data =
|
||||||
|
std::fs::read("tests/custom_signal_handler.wasm").expect("failed to read wasm file");
|
||||||
|
let module = HostRef::new(Module::new(&store, &data).expect("failed to create module"));
|
||||||
|
|
||||||
|
// Set up multiple instances
|
||||||
|
|
||||||
|
let instance1 = HostRef::new(
|
||||||
|
Instance::new(&store, &module, &[]).expect("failed to instantiate module"),
|
||||||
|
);
|
||||||
|
let instance1_handler_triggered = Rc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
{
|
||||||
|
let (base1, length1) = set_up_memory(&instance1);
|
||||||
|
|
||||||
|
instance1.borrow_mut().set_signal_handler({
|
||||||
|
let instance1_handler_triggered = instance1_handler_triggered.clone();
|
||||||
|
move |_signum, _siginfo, _context| {
|
||||||
|
// Remove protections so the execution may resume
|
||||||
|
unsafe {
|
||||||
|
libc::mprotect(
|
||||||
|
base1 as *mut libc::c_void,
|
||||||
|
length1,
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
instance1_handler_triggered.store(true, Ordering::SeqCst);
|
||||||
|
println!(
|
||||||
|
"Hello from instance1 signal handler! {}",
|
||||||
|
instance1_handler_triggered.load(Ordering::SeqCst)
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance2 = HostRef::new(
|
||||||
|
Instance::new(&store, &module, &[]).expect("failed to instantiate module"),
|
||||||
|
);
|
||||||
|
let instance2_handler_triggered = Rc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
{
|
||||||
|
let (base2, length2) = set_up_memory(&instance2);
|
||||||
|
|
||||||
|
instance2.borrow_mut().set_signal_handler({
|
||||||
|
let instance2_handler_triggered = instance2_handler_triggered.clone();
|
||||||
|
move |_signum, _siginfo, _context| {
|
||||||
|
// Remove protections so the execution may resume
|
||||||
|
unsafe {
|
||||||
|
libc::mprotect(
|
||||||
|
base2 as *mut libc::c_void,
|
||||||
|
length2,
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
instance2_handler_triggered.store(true, Ordering::SeqCst);
|
||||||
|
println!(
|
||||||
|
"Hello from instance2 signal handler! {}",
|
||||||
|
instance2_handler_triggered.load(Ordering::SeqCst)
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke both instances and trigger both signal handlers
|
||||||
|
|
||||||
|
// First instance1
|
||||||
|
{
|
||||||
|
let exports1 = Ref::map(instance1.borrow(), |i| i.exports());
|
||||||
|
assert!(!exports1.is_empty());
|
||||||
|
|
||||||
|
println!("calling instance1.read...");
|
||||||
|
let result = invoke_export(&instance1, &data, "read").expect("read succeeded");
|
||||||
|
assert_eq!("123", result[0].clone().to_string());
|
||||||
|
assert_eq!(
|
||||||
|
instance1_handler_triggered.load(Ordering::SeqCst),
|
||||||
|
true,
|
||||||
|
"instance1 signal handler has been triggered"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then instance2
|
||||||
|
{
|
||||||
|
let exports2 = Ref::map(instance2.borrow(), |i| i.exports());
|
||||||
|
assert!(!exports2.is_empty());
|
||||||
|
|
||||||
|
println!("calling instance2.read...");
|
||||||
|
let result = invoke_export(&instance2, &data, "read").expect("read succeeded");
|
||||||
|
assert_eq!("123", result[0].clone().to_string());
|
||||||
|
assert_eq!(
|
||||||
|
instance2_handler_triggered.load(Ordering::SeqCst),
|
||||||
|
true,
|
||||||
|
"instance1 signal handler has been triggered"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_signal_handler_instance_calling_another_instance() {
|
||||||
|
let engine = HostRef::new(Engine::new(&Config::default()));
|
||||||
|
let store = HostRef::new(Store::new(&engine));
|
||||||
|
|
||||||
|
// instance1 which defines 'read'
|
||||||
|
let data1 =
|
||||||
|
std::fs::read("tests/custom_signal_handler.wasm").expect("failed to read wasm file");
|
||||||
|
let module1 = HostRef::new(Module::new(&store, &data1).expect("failed to create module"));
|
||||||
|
let instance1: HostRef<Instance> = HostRef::new(
|
||||||
|
Instance::new(&store, &module1, &[]).expect("failed to instantiate module"),
|
||||||
|
);
|
||||||
|
let (base1, length1) = set_up_memory(&instance1);
|
||||||
|
instance1
|
||||||
|
.borrow_mut()
|
||||||
|
.set_signal_handler(move |signum, siginfo, _| {
|
||||||
|
println!("instance1");
|
||||||
|
handle_sigsegv(base1, length1, signum, siginfo)
|
||||||
|
});
|
||||||
|
|
||||||
|
let instance1_exports = Ref::map(instance1.borrow(), |i| i.exports());
|
||||||
|
assert!(!instance1_exports.is_empty());
|
||||||
|
let instance1_read = instance1_exports[0].clone();
|
||||||
|
|
||||||
|
// instance2 wich calls 'instance1.read'
|
||||||
|
let data2 =
|
||||||
|
std::fs::read("tests/custom_signal_handler_2.wasm").expect("failed to read wasm file");
|
||||||
|
let module2 = HostRef::new(Module::new(&store, &data2).expect("failed to create module"));
|
||||||
|
let instance2 = HostRef::new(
|
||||||
|
Instance::new(&store, &module2, &[instance1_read])
|
||||||
|
.expect("failed to instantiate module"),
|
||||||
|
);
|
||||||
|
// since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
|
||||||
|
// SIGSEGV originating from within the memory of instance1
|
||||||
|
instance2
|
||||||
|
.borrow_mut()
|
||||||
|
.set_signal_handler(move |signum, siginfo, _| {
|
||||||
|
handle_sigsegv(base1, length1, signum, siginfo)
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("calling instance2.run");
|
||||||
|
let result = invoke_export(&instance2, &data2, "run").expect("instance2.run succeeded");
|
||||||
|
assert_eq!("123", result[0].clone().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tests/custom_signal_handler.wasm
Normal file
BIN
tests/custom_signal_handler.wasm
Normal file
Binary file not shown.
20
tests/custom_signal_handler.wat
Normal file
20
tests/custom_signal_handler.wat
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
(module
|
||||||
|
(func $read (export "read") (result i32)
|
||||||
|
(i32.load (i32.const 0))
|
||||||
|
)
|
||||||
|
(func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
|
||||||
|
(i32.load
|
||||||
|
(i32.mul
|
||||||
|
;; memory size in Wasm pages
|
||||||
|
(memory.size)
|
||||||
|
;; Wasm page size
|
||||||
|
(i32.const 65536)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(func $start
|
||||||
|
(i32.store (i32.const 0) (i32.const 123))
|
||||||
|
)
|
||||||
|
(start $start)
|
||||||
|
(memory (export "memory") 1 4)
|
||||||
|
)
|
||||||
BIN
tests/custom_signal_handler_2.wasm
Normal file
BIN
tests/custom_signal_handler_2.wasm
Normal file
Binary file not shown.
5
tests/custom_signal_handler_2.wat
Normal file
5
tests/custom_signal_handler_2.wat
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
(module
|
||||||
|
(import "other_module" "read" (func $other_module.read (result i32)))
|
||||||
|
(func $run (export "run") (result i32)
|
||||||
|
call $other_module.read)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user