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 }
|
||||
anyhow = "1.0.19"
|
||||
region = "2.0.0"
|
||||
libc = "0.2"
|
||||
cfg-if = "0.1.9"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
|
||||
[dev-dependencies]
|
||||
# for wasmtime.rs
|
||||
|
||||
@@ -151,11 +151,13 @@ impl WrappedCallable for WasmtimeFn {
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
self.instance.with_signals_on(|| {
|
||||
wasmtime_runtime::wasmtime_call_trampoline(
|
||||
vmctx,
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
)
|
||||
})
|
||||
} {
|
||||
let trap =
|
||||
take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message)));
|
||||
|
||||
@@ -161,3 +161,37 @@ impl Instance {
|
||||
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.
|
||||
if let Err(message) = unsafe {
|
||||
instance.with_signals_on(|| {
|
||||
wasmtime_call_trampoline(
|
||||
callee_vmctx,
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
)
|
||||
})
|
||||
} {
|
||||
return Ok(ActionOutcome::Trapped { message });
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ memoffset = "0.5.3"
|
||||
indexmap = "1.0.2"
|
||||
thiserror = "1.0.4"
|
||||
more-asserts = "0.2.1"
|
||||
cfg-if = "0.1.9"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] }
|
||||
|
||||
@@ -467,10 +467,14 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exception)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool handled = InstanceSignalHandler(exception);
|
||||
|
||||
if (!handled) {
|
||||
if (!HandleTrap(exception->ContextRecord,
|
||||
record->ExceptionCode == EXCEPTION_STACK_OVERFLOW)) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
@@ -633,11 +637,17 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context)
|
||||
if (!sAlreadyHandlingTrap) {
|
||||
AutoHandlingTrap aht;
|
||||
assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL);
|
||||
|
||||
if (InstanceSignalHandler(signum, info, (ucontext_t*) context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (HandleTrap(static_cast<CONTEXT*>(context), false)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct sigaction* previousSignal = nullptr;
|
||||
switch (signum) {
|
||||
case SIGSEGV: previousSignal = &sPrevSIGSEGVHandler; break;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -15,6 +17,17 @@ int8_t CheckIfTrapAtAddress(const uint8_t* pc);
|
||||
// Record the Trap code and wasm bytecode offset in TLS somewhere
|
||||
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 LeaveScope(void*);
|
||||
void* GetScope(void);
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::{mem, ptr, slice};
|
||||
use thiserror::Error;
|
||||
@@ -32,6 +33,11 @@ use wasmtime_environ::wasm::{
|
||||
};
|
||||
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(
|
||||
vmctx: &VMContext,
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
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
|
||||
/// represents a dynamically-sized array that extends beyond the nominal
|
||||
/// end of the struct (similar to a flexible array member).
|
||||
@@ -647,6 +747,28 @@ pub struct 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`.
|
||||
pub fn new(
|
||||
module: Rc<Module>,
|
||||
@@ -699,6 +821,7 @@ impl InstanceHandle {
|
||||
finished_functions,
|
||||
dbg_jit_registration,
|
||||
host_state,
|
||||
signal_handler: Box::new(signal_handler_none) as Box<SignalHandler>,
|
||||
vmctx: VMContext {},
|
||||
};
|
||||
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