mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-13 13:58:38 +00:00
Improved trap handling
This commit is contained in:
1
lib/api/src/externals/function.rs
vendored
1
lib/api/src/externals/function.rs
vendored
@@ -480,6 +480,7 @@ impl Function {
|
||||
// Call the trampoline.
|
||||
if let Err(error) = unsafe {
|
||||
wasmer_call_trampoline(
|
||||
&self.store,
|
||||
self.exported.vm_function.vmctx,
|
||||
trampoline,
|
||||
self.exported.vm_function.address,
|
||||
|
||||
@@ -275,7 +275,8 @@ impl Module {
|
||||
// of this steps traps, we still need to keep the instance alive
|
||||
// as some of the Instance elements may have placed in other
|
||||
// instance tables.
|
||||
self.artifact.finish_instantiation(&instance_handle)?;
|
||||
self.artifact
|
||||
.finish_instantiation(&self.store, &instance_handle)?;
|
||||
|
||||
Ok(instance_handle)
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ macro_rules! impl_native_traits {
|
||||
};
|
||||
unsafe {
|
||||
wasmer_vm::wasmer_call_trampoline(
|
||||
&self.store,
|
||||
self.vmctx(),
|
||||
trampoline,
|
||||
self.address(),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::tunables::BaseTunables;
|
||||
use loupe::MemoryUsage;
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
#[cfg(all(feature = "compiler", feature = "engine"))]
|
||||
use wasmer_compiler::CompilerConfig;
|
||||
use wasmer_engine::{Engine, Tunables};
|
||||
use wasmer_engine::{is_wasm_pc, Engine, Tunables};
|
||||
use wasmer_vm::{init_traps, SignalHandler, TrapInfo};
|
||||
|
||||
/// The store represents all global state that can be manipulated by
|
||||
/// WebAssembly programs. It consists of the runtime representation
|
||||
@@ -28,10 +30,7 @@ impl Store {
|
||||
where
|
||||
E: Engine + ?Sized,
|
||||
{
|
||||
Self {
|
||||
engine: engine.cloned(),
|
||||
tunables: Arc::new(BaseTunables::for_target(engine.target())),
|
||||
}
|
||||
Self::new_with_tunables(engine, BaseTunables::for_target(engine.target()))
|
||||
}
|
||||
|
||||
/// Creates a new `Store` with a specific [`Engine`] and [`Tunables`].
|
||||
@@ -39,6 +38,10 @@ impl Store {
|
||||
where
|
||||
E: Engine + ?Sized,
|
||||
{
|
||||
// Make sure the signal handlers are installed.
|
||||
// This is required for handling traps.
|
||||
init_traps(is_wasm_pc);
|
||||
|
||||
Self {
|
||||
engine: engine.cloned(),
|
||||
tunables: Arc::new(tunables),
|
||||
@@ -69,6 +72,19 @@ impl PartialEq for Store {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl TrapInfo for Store {
|
||||
#[inline]
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn custom_signal_handler(&self, call: &dyn Fn(&SignalHandler) -> bool) -> bool {
|
||||
// if let Some(handler) = &*self.inner.signal_handler.borrow() {
|
||||
// return call(handler);
|
||||
// }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// We only implement default if we have assigned a default compiler and engine
|
||||
#[cfg(all(feature = "default-compiler", feature = "default-engine"))]
|
||||
impl Default for Store {
|
||||
|
||||
@@ -124,8 +124,9 @@ fn translate_ir_trapcode(trap: ir::TrapCode) -> TrapCode {
|
||||
ir::TrapCode::IntegerDivisionByZero => TrapCode::IntegerDivisionByZero,
|
||||
ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
||||
ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
||||
ir::TrapCode::Interrupt => TrapCode::Interrupt,
|
||||
ir::TrapCode::Interrupt => unimplemented!("Interrupts not supported"),
|
||||
ir::TrapCode::User(_user_code) => unimplemented!("User trap code not supported"),
|
||||
// ir::TrapCode::Interrupt => TrapCode::Interrupt,
|
||||
// ir::TrapCode::User(user_code) => TrapCode::User(user_code),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use wasmer_types::{
|
||||
};
|
||||
use wasmer_vm::{
|
||||
FuncDataRegistry, FunctionBodyPtr, InstanceAllocator, InstanceHandle, MemoryStyle, ModuleInfo,
|
||||
TableStyle, VMSharedSignatureIndex, VMTrampoline,
|
||||
TableStyle, TrapInfo, VMSharedSignatureIndex, VMTrampoline,
|
||||
};
|
||||
|
||||
/// An `Artifact` is the product that the `Engine`
|
||||
@@ -161,6 +161,7 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage {
|
||||
/// See [`InstanceHandle::finish_instantiation`].
|
||||
unsafe fn finish_instantiation(
|
||||
&self,
|
||||
trap_info: &dyn TrapInfo,
|
||||
handle: &InstanceHandle,
|
||||
) -> Result<(), InstantiationError> {
|
||||
let data_initializers = self
|
||||
@@ -172,7 +173,7 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
handle
|
||||
.finish_instantiation(&data_initializers)
|
||||
.finish_instantiation(trap_info, &data_initializers)
|
||||
.map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,19 @@ impl RuntimeError {
|
||||
),
|
||||
}
|
||||
}
|
||||
Trap::OOM { backtrace } => {
|
||||
unimplemented!("OOM memory errors are not yet handled");
|
||||
// match error.downcast::<Self>() {
|
||||
// // The error is already a RuntimeError, we return it directly
|
||||
// Ok(runtime_error) => *runtime_error,
|
||||
// Err(e) => Self::new_with_trace(
|
||||
// &info,
|
||||
// None,
|
||||
// RuntimeErrorSource::User(e),
|
||||
// Backtrace::new_unresolved(),
|
||||
// ),
|
||||
// }
|
||||
}
|
||||
// A trap caused by an error on the generated machine code for a Wasm function
|
||||
Trap::Wasm {
|
||||
pc,
|
||||
@@ -92,7 +105,7 @@ impl RuntimeError {
|
||||
Self::new_with_trace(&info, Some(pc), RuntimeErrorSource::Trap(code), backtrace)
|
||||
}
|
||||
// A trap triggered manually from the Wasmer runtime
|
||||
Trap::Runtime {
|
||||
Trap::Lib {
|
||||
trap_code,
|
||||
backtrace,
|
||||
} => Self::new_with_trace(&info, None, RuntimeErrorSource::Trap(trap_code), backtrace),
|
||||
|
||||
@@ -42,6 +42,15 @@ pub struct GlobalFrameInfo {
|
||||
ranges: BTreeMap<usize, ModuleInfoFrameInfo>,
|
||||
}
|
||||
|
||||
/// Returns whether the `pc`, according to globally registered information,
|
||||
/// is a wasm trap or not.
|
||||
pub fn is_wasm_pc(pc: usize) -> bool {
|
||||
let frame_info = FRAME_INFO.read().unwrap();
|
||||
let module_info = frame_info.module_info(pc);
|
||||
let is_wasm_pc = module_info.is_some();
|
||||
is_wasm_pc
|
||||
}
|
||||
|
||||
/// An RAII structure used to unregister a module's frame information when the
|
||||
/// module is destroyed.
|
||||
#[derive(MemoryUsage)]
|
||||
|
||||
@@ -2,6 +2,6 @@ mod error;
|
||||
mod frame_info;
|
||||
pub use error::RuntimeError;
|
||||
pub use frame_info::{
|
||||
register as register_frame_info, FrameInfo, FunctionExtent, GlobalFrameInfoRegistration,
|
||||
FRAME_INFO,
|
||||
is_wasm_pc, register as register_frame_info, FrameInfo, FunctionExtent,
|
||||
GlobalFrameInfoRegistration, FRAME_INFO,
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::global::Global;
|
||||
use crate::imports::Imports;
|
||||
use crate::memory::{Memory, MemoryError};
|
||||
use crate::table::{Table, TableElement};
|
||||
use crate::trap::{catch_traps, init_traps, Trap, TrapCode};
|
||||
use crate::trap::{catch_traps, init_traps, Trap, TrapCode, TrapInfo};
|
||||
use crate::vmcontext::{
|
||||
VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody,
|
||||
VMFunctionEnvironment, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport,
|
||||
@@ -98,10 +98,6 @@ pub(crate) struct Instance {
|
||||
/// Hosts can store arbitrary per-instance information here.
|
||||
host_state: Box<dyn Any>,
|
||||
|
||||
/// Handler run when `SIGBUS`, `SIGFPE`, `SIGILL`, or `SIGSEGV` are caught by the instance thread.
|
||||
#[loupe(skip)]
|
||||
pub(crate) signal_handler: Cell<Option<Box<SignalHandler>>>,
|
||||
|
||||
/// Functions to operate on host environments in the imports
|
||||
/// and pointers to the environments.
|
||||
///
|
||||
@@ -397,7 +393,7 @@ impl Instance {
|
||||
}
|
||||
|
||||
/// Invoke the WebAssembly start function of the instance, if one is present.
|
||||
fn invoke_start_function(&self) -> Result<(), Trap> {
|
||||
fn invoke_start_function(&self, trap_info: &dyn TrapInfo) -> Result<(), Trap> {
|
||||
let start_index = match self.module.start_function {
|
||||
Some(idx) => idx,
|
||||
None => return Ok(()),
|
||||
@@ -426,7 +422,7 @@ impl Instance {
|
||||
|
||||
// Make the call.
|
||||
unsafe {
|
||||
catch_traps(callee_vmctx, || {
|
||||
catch_traps(trap_info, || {
|
||||
mem::transmute::<*const VMFunctionBody, unsafe extern "C" fn(VMFunctionEnvironment)>(
|
||||
callee_address,
|
||||
)(callee_vmctx)
|
||||
@@ -669,7 +665,7 @@ impl Instance {
|
||||
.map_or(true, |n| n as usize > elem.len())
|
||||
|| dst.checked_add(len).map_or(true, |m| m > table.size())
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
|
||||
}
|
||||
|
||||
for (dst, src) in (dst..dst + len).zip(src..src + len) {
|
||||
@@ -702,7 +698,7 @@ impl Instance {
|
||||
.checked_add(len)
|
||||
.map_or(true, |n| n as usize > table_size)
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
|
||||
}
|
||||
|
||||
for i in start_index..(start_index + len) {
|
||||
@@ -821,7 +817,7 @@ impl Instance {
|
||||
.checked_add(len)
|
||||
.map_or(true, |m| m > memory.current_length)
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::HeapAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds));
|
||||
}
|
||||
|
||||
let src_slice = &data[src as usize..(src + len) as usize];
|
||||
@@ -935,7 +931,6 @@ impl InstanceHandle {
|
||||
passive_data,
|
||||
host_state,
|
||||
funcrefs,
|
||||
signal_handler: Cell::new(None),
|
||||
imported_function_envs,
|
||||
vmctx: VMContext {},
|
||||
};
|
||||
@@ -1000,9 +995,6 @@ impl InstanceHandle {
|
||||
VMBuiltinFunctionsArray::initialized(),
|
||||
);
|
||||
|
||||
// Ensure that our signal handlers are ready for action.
|
||||
init_traps();
|
||||
|
||||
// Perform infallible initialization in this constructor, while fallible
|
||||
// initialization is deferred to the `initialize` method.
|
||||
initialize_passive_elements(instance);
|
||||
@@ -1023,6 +1015,7 @@ impl InstanceHandle {
|
||||
/// Only safe to call immediately after instantiation.
|
||||
pub unsafe fn finish_instantiation(
|
||||
&self,
|
||||
trap_info: &dyn TrapInfo,
|
||||
data_initializers: &[DataInitializer<'_>],
|
||||
) -> Result<(), Trap> {
|
||||
let instance = self.instance().as_ref();
|
||||
@@ -1033,7 +1026,7 @@ impl InstanceHandle {
|
||||
|
||||
// The WebAssembly spec specifies that the start function is
|
||||
// invoked automatically at instantiation time.
|
||||
instance.invoke_start_function()?;
|
||||
instance.invoke_start_function(trap_info)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1270,34 +1263,6 @@ impl InstanceHandle {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
pub type SignalHandler = dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool;
|
||||
|
||||
impl InstanceHandle {
|
||||
/// Set a custom signal handler
|
||||
pub fn set_signal_handler<H>(&self, handler: H)
|
||||
where
|
||||
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
|
||||
{
|
||||
self.instance().as_ref().signal_handler.set(Some(Box::new(handler)));
|
||||
}
|
||||
}
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
pub type SignalHandler = dyn Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool;
|
||||
|
||||
impl InstanceHandle {
|
||||
/// Set a custom signal handler
|
||||
pub fn set_signal_handler<H>(&self, handler: H)
|
||||
where
|
||||
H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool,
|
||||
{
|
||||
self.instance().as_ref().signal_handler.set(Some(Box::new(handler)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the offset for a memory data initializer.
|
||||
fn get_memory_init_start(init: &DataInitializer<'_>, instance: &Instance) -> usize {
|
||||
let mut start = init.location.offset;
|
||||
@@ -1363,7 +1328,7 @@ fn initialize_tables(instance: &Instance) -> Result<(), Trap> {
|
||||
.checked_add(init.elements.len())
|
||||
.map_or(true, |end| end > table.size() as usize)
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
|
||||
}
|
||||
|
||||
for (i, func_idx) in init.elements.iter().enumerate() {
|
||||
@@ -1421,7 +1386,7 @@ fn initialize_memories(
|
||||
.checked_add(init.data.len())
|
||||
.map_or(true, |end| end > memory.current_length.try_into().unwrap())
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::HeapAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
|
||||
@@ -339,7 +339,7 @@ pub unsafe extern "C" fn wasmer_vm_table_get(
|
||||
// TODO: type checking, maybe have specialized accessors
|
||||
match instance.table_get(table_index, elem_index) {
|
||||
Some(table_ref) => table_ref.into(),
|
||||
None => raise_lib_trap(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds)),
|
||||
None => raise_lib_trap(Trap::lib(TrapCode::TableAccessOutOfBounds)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ pub unsafe extern "C" fn wasmer_vm_imported_table_get(
|
||||
// TODO: type checking, maybe have specialized accessors
|
||||
match instance.imported_table_get(table_index, elem_index) {
|
||||
Some(table_ref) => table_ref.into(),
|
||||
None => raise_lib_trap(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds)),
|
||||
None => raise_lib_trap(Trap::lib(TrapCode::TableAccessOutOfBounds)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ pub unsafe extern "C" fn wasmer_vm_data_drop(vmctx: *mut VMContext, data_index:
|
||||
/// `wasmer_call_trampoline` must have been previously called.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmer_vm_raise_trap(trap_code: TrapCode) -> ! {
|
||||
let trap = Trap::new_from_runtime(trap_code);
|
||||
let trap = Trap::lib(trap_code);
|
||||
raise_lib_trap(trap)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,11 +83,11 @@ pub trait Table: fmt::Debug + Send + Sync + MemoryUsage {
|
||||
.checked_add(len)
|
||||
.map_or(true, |n| n > src_table.size())
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
|
||||
}
|
||||
|
||||
if dst_index.checked_add(len).map_or(true, |m| m > self.size()) {
|
||||
return Err(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
|
||||
}
|
||||
|
||||
let srcs = src_index..src_index + len;
|
||||
@@ -411,7 +411,7 @@ impl Table for LinearTable {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
None => Err(Trap::new_from_runtime(TrapCode::TableAccessOutOfBounds)),
|
||||
None => Err(Trap::lib(TrapCode::TableAccessOutOfBounds)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
int RegisterSetjmp(
|
||||
int register_setjmp(
|
||||
void **buf_storage,
|
||||
void (*body)(void*),
|
||||
void *payload) {
|
||||
@@ -16,7 +16,7 @@ int RegisterSetjmp(
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Unwind(void *JmpBuf) {
|
||||
void unwind(void *JmpBuf) {
|
||||
jmp_buf *buf = (jmp_buf*) JmpBuf;
|
||||
longjmp(*buf, 1);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ mod traphandlers;
|
||||
pub use trapcode::TrapCode;
|
||||
pub use traphandlers::{
|
||||
catch_traps, catch_traps_with_result, raise_lib_trap, raise_user_trap, wasmer_call_trampoline,
|
||||
Trap,
|
||||
SignalHandler, Trap, TrapInfo,
|
||||
};
|
||||
pub use traphandlers::{init_traps, resume_panic};
|
||||
|
||||
@@ -61,17 +61,8 @@ pub enum TrapCode {
|
||||
/// Code that was supposed to have been unreachable was reached.
|
||||
UnreachableCodeReached = 10,
|
||||
|
||||
/// Execution has potentially run too long and may be interrupted.
|
||||
/// This trap is resumable.
|
||||
Interrupt = 11,
|
||||
|
||||
/// An atomic memory access was attempted with an unaligned pointer.
|
||||
UnalignedAtomic = 12,
|
||||
|
||||
/// A trap indicating that the runtime was unable to allocate sufficient memory.
|
||||
VMOutOfMemory = 13,
|
||||
// /// A user-defined trap code.
|
||||
// User(u16),
|
||||
}
|
||||
|
||||
impl TrapCode {
|
||||
@@ -89,10 +80,7 @@ impl TrapCode {
|
||||
Self::IntegerDivisionByZero => "integer divide by zero",
|
||||
Self::BadConversionToInteger => "invalid conversion to integer",
|
||||
Self::UnreachableCodeReached => "unreachable",
|
||||
Self::Interrupt => "interrupt",
|
||||
Self::UnalignedAtomic => "unaligned atomic access",
|
||||
Self::VMOutOfMemory => "out of memory",
|
||||
// Self::User(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,10 +99,7 @@ impl Display for TrapCode {
|
||||
Self::IntegerDivisionByZero => "int_divz",
|
||||
Self::BadConversionToInteger => "bad_toint",
|
||||
Self::UnreachableCodeReached => "unreachable",
|
||||
Self::Interrupt => "interrupt",
|
||||
Self::UnalignedAtomic => "unalign_atom",
|
||||
Self::VMOutOfMemory => "oom",
|
||||
// User(x) => return write!(f, "user{}", x),
|
||||
};
|
||||
f.write_str(identifier)
|
||||
}
|
||||
@@ -124,23 +109,19 @@ impl FromStr for TrapCode {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::TrapCode::*;
|
||||
match s {
|
||||
"stk_ovf" => Ok(StackOverflow),
|
||||
"heap_get_oob" => Ok(HeapAccessOutOfBounds),
|
||||
"heap_misaligned" => Ok(HeapMisaligned),
|
||||
"table_get_oob" => Ok(TableAccessOutOfBounds),
|
||||
"oob" => Ok(OutOfBounds),
|
||||
"icall_null" => Ok(IndirectCallToNull),
|
||||
"bad_sig" => Ok(BadSignature),
|
||||
"int_ovf" => Ok(IntegerOverflow),
|
||||
"int_divz" => Ok(IntegerDivisionByZero),
|
||||
"bad_toint" => Ok(BadConversionToInteger),
|
||||
"unreachable" => Ok(UnreachableCodeReached),
|
||||
"interrupt" => Ok(Interrupt),
|
||||
"unalign_atom" => Ok(UnalignedAtomic),
|
||||
"oom" => Ok(VMOutOfMemory),
|
||||
// _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()),
|
||||
"stk_ovf" => Ok(TrapCode::StackOverflow),
|
||||
"heap_get_oob" => Ok(TrapCode::HeapAccessOutOfBounds),
|
||||
"heap_misaligned" => Ok(TrapCode::HeapMisaligned),
|
||||
"table_get_oob" => Ok(TrapCode::TableAccessOutOfBounds),
|
||||
"oob" => Ok(TrapCode::OutOfBounds),
|
||||
"icall_null" => Ok(TrapCode::IndirectCallToNull),
|
||||
"bad_sig" => Ok(TrapCode::BadSignature),
|
||||
"int_ovf" => Ok(TrapCode::IntegerOverflow),
|
||||
"int_divz" => Ok(TrapCode::IntegerDivisionByZero),
|
||||
"bad_toint" => Ok(TrapCode::BadConversionToInteger),
|
||||
"unreachable" => Ok(TrapCode::UnreachableCodeReached),
|
||||
"unalign_atom" => Ok(TrapCode::UnalignedAtomic),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
@@ -151,7 +132,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
// Everything but user-defined codes.
|
||||
const CODES: [TrapCode; 13] = [
|
||||
const CODES: [TrapCode; 12] = [
|
||||
TrapCode::StackOverflow,
|
||||
TrapCode::HeapAccessOutOfBounds,
|
||||
TrapCode::HeapMisaligned,
|
||||
@@ -163,7 +144,6 @@ mod tests {
|
||||
TrapCode::IntegerDivisionByZero,
|
||||
TrapCode::BadConversionToInteger,
|
||||
TrapCode::UnreachableCodeReached,
|
||||
TrapCode::Interrupt,
|
||||
TrapCode::UnalignedAtomic,
|
||||
];
|
||||
|
||||
|
||||
@@ -5,24 +5,33 @@
|
||||
//! signalhandling mechanisms.
|
||||
|
||||
use super::trapcode::TrapCode;
|
||||
use crate::instance::{Instance, SignalHandler};
|
||||
use crate::vmcontext::{VMFunctionBody, VMFunctionEnvironment, VMTrampoline};
|
||||
use backtrace::Backtrace;
|
||||
use std::any::Any;
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::sync::Once;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
/// Function which may handle custom signals while processing traps.
|
||||
pub type SignalHandler = dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
/// Function which may handle custom signals while processing traps.
|
||||
pub type SignalHandler = dyn Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn RegisterSetjmp(
|
||||
fn register_setjmp(
|
||||
jmp_buf: *mut *const u8,
|
||||
callback: extern "C" fn(*mut u8),
|
||||
payload: *mut u8,
|
||||
) -> i32;
|
||||
fn Unwind(jmp_buf: *const u8) -> !;
|
||||
fn unwind(jmp_buf: *const u8) -> !;
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@@ -160,7 +169,7 @@ cfg_if::cfg_if! {
|
||||
} else if jmp_buf as usize == 1 {
|
||||
true
|
||||
} else {
|
||||
Unwind(jmp_buf)
|
||||
unwind(jmp_buf)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -338,44 +347,52 @@ cfg_if::cfg_if! {
|
||||
} else if jmp_buf as usize == 1 {
|
||||
EXCEPTION_CONTINUE_EXECUTION
|
||||
} else {
|
||||
Unwind(jmp_buf)
|
||||
unwind(jmp_buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function performs the low-overhead signal handler initialization that
|
||||
/// we want to do eagerly to ensure a more-deterministic global process state.
|
||||
/// Globally-set callback to determine whether a program counter is actually a
|
||||
/// wasm trap.
|
||||
///
|
||||
/// This is especially relevant for signal handlers since handler ordering
|
||||
/// depends on installation order: the wasm signal handler must run *before*
|
||||
/// the other crash handlers and since POSIX signal handlers work LIFO, this
|
||||
/// function needs to be called at the end of the startup process, after other
|
||||
/// handlers have been installed. This function can thus be called multiple
|
||||
/// times, having no effect after the first call.
|
||||
pub fn init_traps() {
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(real_init);
|
||||
}
|
||||
/// This is initialized during `init_traps` below. The definition lives within
|
||||
/// `wasmer` currently.
|
||||
static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
|
||||
|
||||
fn real_init() {
|
||||
unsafe {
|
||||
/// This function is required to be called before any WebAssembly is entered.
|
||||
/// This will configure global state such as signal handlers to prepare the
|
||||
/// process to receive wasm traps.
|
||||
///
|
||||
/// This function must not only be called globally once before entering
|
||||
/// WebAssembly but it must also be called once-per-thread that enters
|
||||
/// WebAssembly. Currently in wasmer's integration this function is called on
|
||||
/// creation of a `Store`.
|
||||
///
|
||||
/// The `is_wasm_pc` argument is used when a trap happens to determine if a
|
||||
/// program counter is the pc of an actual wasm trap or not. This is then used
|
||||
/// to disambiguate faults that happen due to wasm and faults that happen due to
|
||||
/// bugs in Rust or elsewhere.
|
||||
pub fn init_traps(is_wasm_pc: fn(usize) -> bool) {
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| unsafe {
|
||||
IS_WASM_PC = is_wasm_pc;
|
||||
platform_init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Raises a user-defined trap immediately.
|
||||
///
|
||||
/// This function performs as-if a wasm trap was just executed, only the trap
|
||||
/// has a dynamic payload associated with it which is user-provided. This trap
|
||||
/// payload is then returned from `wasmer_call` and `wasmer_call_trampoline`
|
||||
/// below.
|
||||
/// payload is then returned from `catch_traps` below.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to call when wasm code is on the stack, aka `wasmer_call` or
|
||||
/// `wasmer_call_trampoline` must have been previously called.
|
||||
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
|
||||
/// have been previously called. Additionally no Rust destructors can be on the
|
||||
/// stack. They will be skipped and not executed.
|
||||
pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
|
||||
tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
|
||||
}
|
||||
@@ -383,13 +400,13 @@ pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
|
||||
/// Raises a trap from inside library code immediately.
|
||||
///
|
||||
/// This function performs as-if a wasm trap was just executed. This trap
|
||||
/// payload is then returned from `wasmer_call` and `wasmer_call_trampoline`
|
||||
/// below.
|
||||
/// payload is then returned from `catch_traps` below.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to call when wasm code is on the stack, aka `wasmer_call` or
|
||||
/// `wasmer_call_trampoline` must have been previously called.
|
||||
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
|
||||
/// have been previously called. Additionally no Rust destructors can be on the
|
||||
/// stack. They will be skipped and not executed.
|
||||
pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
|
||||
tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap)))
|
||||
}
|
||||
@@ -399,8 +416,9 @@ pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to call when wasm code is on the stack, aka `wasmer_call` or
|
||||
/// `wasmer_call_trampoline` must have been previously called.
|
||||
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
|
||||
/// have been previously called. Additionally no Rust destructors can be on the
|
||||
/// stack. They will be skipped and not executed.
|
||||
pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
|
||||
tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload)))
|
||||
}
|
||||
@@ -427,9 +445,11 @@ pub enum Trap {
|
||||
/// A user-raised trap through `raise_user_trap`.
|
||||
User(Box<dyn Error + Send + Sync>),
|
||||
|
||||
/// A trap raised from machine code generated from Wasm
|
||||
/// A trap raised from the Wasm generated code
|
||||
///
|
||||
/// Note: this trap is deterministic (assuming a deterministic host implementation)
|
||||
Wasm {
|
||||
/// The program counter in generated code where this trap happened.
|
||||
/// The program counter in JIT code where this trap happened.
|
||||
pc: usize,
|
||||
/// Native stack backtrace at the time the trap occurred
|
||||
backtrace: Backtrace,
|
||||
@@ -437,45 +457,54 @@ pub enum Trap {
|
||||
signal_trap: Option<TrapCode>,
|
||||
},
|
||||
|
||||
/// A trap raised manually from the Wasmer VM
|
||||
Runtime {
|
||||
/// A trap raised from a wasm libcall
|
||||
///
|
||||
/// Note: this trap is deterministic (assuming a deterministic host implementation)
|
||||
Lib {
|
||||
/// Code of the trap.
|
||||
trap_code: TrapCode,
|
||||
/// Native stack backtrace at the time the trap occurred
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
/// A trap indicating that the runtime was unable to allocate sufficient memory.
|
||||
///
|
||||
/// Note: this trap is undeterministic, since it depends on the host system.
|
||||
OOM {
|
||||
/// Native stack backtrace at the time the OOM occurred
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
impl Trap {
|
||||
/// Construct a new VM `Trap` with the given the program counter, backtrace and an optional
|
||||
/// trap code associated with the signal received from the kernel.
|
||||
/// Wasm traps are Traps that are triggered by the chip when running generated
|
||||
/// code for a Wasm function.
|
||||
pub fn new_from_wasm(pc: usize, backtrace: Backtrace, signal_trap: Option<TrapCode>) -> Self {
|
||||
Self::Wasm {
|
||||
/// Construct a new Wasm trap with the given source location and backtrace.
|
||||
///
|
||||
/// Internally saves a backtrace when constructed.
|
||||
pub fn wasm(pc: usize, backtrace: Backtrace, signal_trap: Option<TrapCode>) -> Self {
|
||||
Trap::Wasm {
|
||||
pc,
|
||||
backtrace,
|
||||
signal_trap,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new runtime `Trap` with the given trap code.
|
||||
/// Runtime traps are Traps that are triggered manually from the VM.
|
||||
/// Construct a new Wasm trap with the given trap code.
|
||||
///
|
||||
/// Internally saves a backtrace when constructed.
|
||||
pub fn new_from_runtime(trap_code: TrapCode) -> Self {
|
||||
pub fn lib(trap_code: TrapCode) -> Self {
|
||||
let backtrace = Backtrace::new_unresolved();
|
||||
Self::Runtime {
|
||||
Trap::Lib {
|
||||
trap_code,
|
||||
backtrace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new Out of Memory (OOM) `Trap`.
|
||||
/// Construct a new OOM trap with the given source location and trap code.
|
||||
///
|
||||
/// Internally saves a backtrace when constructed.
|
||||
pub fn new_from_user(error: Box<dyn Error + Send + Sync>) -> Self {
|
||||
Self::User(error)
|
||||
pub fn oom() -> Self {
|
||||
let backtrace = Backtrace::new_unresolved();
|
||||
Trap::OOM { backtrace }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,34 +524,29 @@ impl Trap {
|
||||
/// Wildly unsafe because it calls raw function pointers and reads/writes raw
|
||||
/// function pointers.
|
||||
pub unsafe fn wasmer_call_trampoline(
|
||||
trap_info: &impl TrapInfo,
|
||||
vmctx: VMFunctionEnvironment,
|
||||
trampoline: VMTrampoline,
|
||||
callee: *const VMFunctionBody,
|
||||
values_vec: *mut u8,
|
||||
) -> Result<(), Trap> {
|
||||
catch_traps(vmctx, || {
|
||||
catch_traps(trap_info, || {
|
||||
mem::transmute::<_, extern "C" fn(VMFunctionEnvironment, *const VMFunctionBody, *mut u8)>(
|
||||
trampoline,
|
||||
)(vmctx, callee, values_vec)
|
||||
)(vmctx, callee, values_vec);
|
||||
})
|
||||
}
|
||||
|
||||
/// Catches any wasm traps that happen within the execution of `closure`,
|
||||
/// returning them as a `Result`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Highly unsafe since `closure` won't have any destructors run.
|
||||
pub unsafe fn catch_traps<F>(vmctx: VMFunctionEnvironment, mut closure: F) -> Result<(), Trap>
|
||||
/// Highly unsafe since `closure` won't have any dtors run.
|
||||
pub unsafe fn catch_traps<F>(trap_info: &dyn TrapInfo, mut closure: F) -> Result<(), Trap>
|
||||
where
|
||||
F: FnMut(),
|
||||
{
|
||||
// Ensure that we have our sigaltstack installed.
|
||||
#[cfg(unix)]
|
||||
setup_unix_sigaltstack()?;
|
||||
|
||||
return CallThreadState::new(vmctx).with(|cx| {
|
||||
RegisterSetjmp(
|
||||
return CallThreadState::new(trap_info).with(|cx| {
|
||||
register_setjmp(
|
||||
cx.jmp_buf.as_ptr(),
|
||||
call_closure::<F>,
|
||||
&mut closure as *mut F as *mut u8,
|
||||
@@ -547,14 +571,14 @@ where
|
||||
///
|
||||
/// Check [`catch_traps`].
|
||||
pub unsafe fn catch_traps_with_result<F, R>(
|
||||
vmctx: VMFunctionEnvironment,
|
||||
trap_info: &dyn TrapInfo,
|
||||
mut closure: F,
|
||||
) -> Result<R, Trap>
|
||||
where
|
||||
F: FnMut() -> R,
|
||||
{
|
||||
let mut global_results = mem::MaybeUninit::<R>::uninit();
|
||||
catch_traps(vmctx, || {
|
||||
let mut global_results = MaybeUninit::<R>::uninit();
|
||||
catch_traps(trap_info, || {
|
||||
global_results.as_mut_ptr().write(closure());
|
||||
})?;
|
||||
Ok(global_results.assume_init())
|
||||
@@ -562,91 +586,89 @@ where
|
||||
|
||||
/// Temporary state stored on the stack which is registered in the `tls` module
|
||||
/// below for calls into wasm.
|
||||
pub struct CallThreadState {
|
||||
unwind: Cell<UnwindReason>,
|
||||
pub struct CallThreadState<'a> {
|
||||
unwind: UnsafeCell<MaybeUninit<UnwindReason>>,
|
||||
jmp_buf: Cell<*const u8>,
|
||||
reset_guard_page: Cell<bool>,
|
||||
prev: Option<*const CallThreadState>,
|
||||
vmctx: VMFunctionEnvironment,
|
||||
prev: Cell<tls::Ptr>,
|
||||
trap_info: &'a (dyn TrapInfo + 'a),
|
||||
handling_trap: Cell<bool>,
|
||||
}
|
||||
|
||||
/// A package of functionality needed by `catch_traps` to figure out what to do
|
||||
/// when handling a trap.
|
||||
///
|
||||
/// Note that this is an `unsafe` trait at least because it's being run in the
|
||||
/// context of a synchronous signal handler, so it needs to be careful to not
|
||||
/// access too much state in answering these queries.
|
||||
pub unsafe trait TrapInfo {
|
||||
/// Converts this object into an `Any` to dynamically check its type.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Uses `call` to call a custom signal handler, if one is specified.
|
||||
///
|
||||
/// Returns `true` if `call` returns true, otherwise returns `false`.
|
||||
fn custom_signal_handler(&self, call: &dyn Fn(&SignalHandler) -> bool) -> bool;
|
||||
}
|
||||
|
||||
enum UnwindReason {
|
||||
None,
|
||||
/// A panic caused by the host
|
||||
Panic(Box<dyn Any + Send>),
|
||||
/// A custom error triggered by the user
|
||||
UserTrap(Box<dyn Error + Send + Sync>),
|
||||
/// A Trap triggered by a wasm libcall
|
||||
LibTrap(Trap),
|
||||
RuntimeTrap {
|
||||
/// A trap caused by the Wasm generated code
|
||||
WasmTrap {
|
||||
backtrace: Backtrace,
|
||||
pc: usize,
|
||||
signal_trap: Option<TrapCode>,
|
||||
},
|
||||
}
|
||||
|
||||
impl CallThreadState {
|
||||
fn new(vmctx: VMFunctionEnvironment) -> Self {
|
||||
impl<'a> CallThreadState<'a> {
|
||||
#[inline]
|
||||
fn new(trap_info: &'a (dyn TrapInfo + 'a)) -> CallThreadState<'a> {
|
||||
Self {
|
||||
unwind: Cell::new(UnwindReason::None),
|
||||
vmctx,
|
||||
unwind: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
jmp_buf: Cell::new(ptr::null()),
|
||||
reset_guard_page: Cell::new(false),
|
||||
prev: None,
|
||||
prev: Cell::new(ptr::null()),
|
||||
trap_info,
|
||||
handling_trap: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn with(mut self, closure: impl FnOnce(&Self) -> i32) -> Result<(), Trap> {
|
||||
tls::with(|prev| {
|
||||
self.prev = prev.map(|p| p as *const _);
|
||||
let ret = tls::set(&self, || closure(&self));
|
||||
match self.unwind.replace(UnwindReason::None) {
|
||||
UnwindReason::None => {
|
||||
debug_assert_eq!(ret, 1);
|
||||
Ok(())
|
||||
fn with(mut self, closure: impl FnOnce(&CallThreadState) -> i32) -> Result<(), Trap> {
|
||||
let ret = tls::set(&self, || closure(&self))?;
|
||||
if ret != 0 {
|
||||
return Ok(());
|
||||
}
|
||||
match unsafe { (*self.unwind.get()).as_ptr().read() } {
|
||||
UnwindReason::UserTrap(data) => {
|
||||
debug_assert_eq!(ret, 0);
|
||||
Err(Trap::new_from_user(data))
|
||||
Err(Trap::User(data))
|
||||
}
|
||||
UnwindReason::LibTrap(trap) => Err(trap),
|
||||
UnwindReason::RuntimeTrap {
|
||||
UnwindReason::WasmTrap {
|
||||
backtrace,
|
||||
pc,
|
||||
signal_trap,
|
||||
} => {
|
||||
debug_assert_eq!(ret, 0);
|
||||
Err(Trap::new_from_wasm(pc, backtrace, signal_trap))
|
||||
Err(Trap::wasm(pc, backtrace, signal_trap))
|
||||
}
|
||||
UnwindReason::Panic(panic) => {
|
||||
debug_assert_eq!(ret, 0);
|
||||
std::panic::resume_unwind(panic)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn any_instance(&self, func: impl Fn(&Instance) -> bool) -> bool {
|
||||
unsafe {
|
||||
if func(
|
||||
self.vmctx
|
||||
.vmctx
|
||||
.as_ref()
|
||||
.expect("`VMContext` is null in `any_instance`")
|
||||
.instance(),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
match self.prev {
|
||||
Some(prev) => (*prev).any_instance(func),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwind_with(&self, reason: UnwindReason) -> ! {
|
||||
self.unwind.replace(reason);
|
||||
unsafe {
|
||||
Unwind(self.jmp_buf.get());
|
||||
(*self.unwind.get()).as_mut_ptr().write(reason);
|
||||
unwind(self.jmp_buf.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,21 +705,10 @@ impl CallThreadState {
|
||||
return ptr::null();
|
||||
}
|
||||
|
||||
// First up see if any instance registered has a custom trap handler,
|
||||
// in which case run them all. If anything handles the trap then we
|
||||
// First up see if we have a custom trap handler,
|
||||
// in which case run it. If anything handles the trap then we
|
||||
// return that the trap was handled.
|
||||
let any_instance = self.any_instance(|instance: &Instance| {
|
||||
let handler = match instance.signal_handler.replace(None) {
|
||||
Some(handler) => handler,
|
||||
None => return false,
|
||||
};
|
||||
let result = call_handler(&handler);
|
||||
instance.signal_handler.set(Some(handler));
|
||||
result
|
||||
});
|
||||
|
||||
if any_instance {
|
||||
self.handling_trap.set(false);
|
||||
if self.trap_info.custom_signal_handler(&call_handler) {
|
||||
return 1 as *const _;
|
||||
}
|
||||
|
||||
@@ -714,17 +725,21 @@ impl CallThreadState {
|
||||
}
|
||||
let backtrace = Backtrace::new_unresolved();
|
||||
self.reset_guard_page.set(reset_guard_page);
|
||||
self.unwind.replace(UnwindReason::RuntimeTrap {
|
||||
unsafe {
|
||||
(*self.unwind.get())
|
||||
.as_mut_ptr()
|
||||
.write(UnwindReason::WasmTrap {
|
||||
backtrace,
|
||||
signal_trap,
|
||||
pc: pc as usize,
|
||||
});
|
||||
}
|
||||
self.handling_trap.set(false);
|
||||
self.jmp_buf.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CallThreadState {
|
||||
impl<'a> Drop for CallThreadState<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.reset_guard_page.get() {
|
||||
reset_guard_page();
|
||||
@@ -739,39 +754,138 @@ impl Drop for CallThreadState {
|
||||
// the caller to the trap site.
|
||||
mod tls {
|
||||
use super::CallThreadState;
|
||||
use crate::Trap;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
pub use raw::Ptr;
|
||||
|
||||
// An even *more* inner module for dealing with TLS. This actually has the
|
||||
// thread local variable and has functions to access the variable.
|
||||
//
|
||||
// Note that this is specially done to fully encapsulate that the accessors
|
||||
// for tls must not be inlined. Wasmtime's async support employs stack
|
||||
// switching which can resume execution on different OS threads. This means
|
||||
// that borrows of our TLS pointer must never live across accesses because
|
||||
// otherwise the access may be split across two threads and cause unsafety.
|
||||
//
|
||||
// This also means that extra care is taken by the runtime to save/restore
|
||||
// these TLS values when the runtime may have crossed threads.
|
||||
mod raw {
|
||||
use super::CallThreadState;
|
||||
use crate::Trap;
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
|
||||
thread_local!(static PTR: Cell<*const CallThreadState> = Cell::new(ptr::null()));
|
||||
pub type Ptr = *const CallThreadState<'static>;
|
||||
|
||||
// The first entry here is the `Ptr` which is what's used as part of the
|
||||
// public interface of this module. The second entry is a boolean which
|
||||
// allows the runtime to perform per-thread initialization if necessary
|
||||
// for handling traps (e.g. setting up ports on macOS and sigaltstack on
|
||||
// Unix).
|
||||
thread_local!(static PTR: Cell<(Ptr, bool)> = Cell::new((ptr::null(), false)));
|
||||
|
||||
#[inline(never)] // see module docs for why this is here
|
||||
pub fn replace(val: Ptr) -> Result<Ptr, Trap> {
|
||||
PTR.with(|p| {
|
||||
// When a new value is configured that means that we may be
|
||||
// entering WebAssembly so check to see if this thread has
|
||||
// performed per-thread initialization for traps.
|
||||
let (prev, mut initialized) = p.get();
|
||||
if !initialized {
|
||||
super::super::lazy_per_thread_init()?;
|
||||
initialized = true;
|
||||
}
|
||||
p.set((val, initialized));
|
||||
Ok(prev)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(never)] // see module docs for why this is here
|
||||
pub fn get() -> Ptr {
|
||||
PTR.with(|p| p.get().0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque state used to help control TLS state across stack switches for
|
||||
/// async support.
|
||||
pub struct TlsRestore(raw::Ptr);
|
||||
|
||||
impl TlsRestore {
|
||||
/// Takes the TLS state that is currently configured and returns a
|
||||
/// token that is used to replace it later.
|
||||
///
|
||||
/// This is not a safe operation since it's intended to only be used
|
||||
/// with stack switching found with fibers and async wasmer.
|
||||
pub unsafe fn take() -> Result<TlsRestore, Trap> {
|
||||
// Our tls pointer must be set at this time, and it must not be
|
||||
// null. We need to restore the previous pointer since we're
|
||||
// removing ourselves from the call-stack, and in the process we
|
||||
// null out our own previous field for safety in case it's
|
||||
// accidentally used later.
|
||||
let raw = raw::get();
|
||||
assert!(!raw.is_null());
|
||||
let prev = (*raw).prev.replace(ptr::null());
|
||||
raw::replace(prev)?;
|
||||
Ok(TlsRestore(raw))
|
||||
}
|
||||
|
||||
/// Restores a previous tls state back into this thread's TLS.
|
||||
///
|
||||
/// This is unsafe because it's intended to only be used within the
|
||||
/// context of stack switching within wasmtime.
|
||||
pub unsafe fn replace(self) -> Result<(), super::Trap> {
|
||||
// We need to configure our previous TLS pointer to whatever is in
|
||||
// TLS at this time, and then we set the current state to ourselves.
|
||||
let prev = raw::get();
|
||||
assert!((*self.0).prev.get().is_null());
|
||||
(*self.0).prev.set(prev);
|
||||
raw::replace(self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures thread local state such that for the duration of the
|
||||
/// execution of `closure` any call to `with` will yield `ptr`, unless this
|
||||
/// is recursively called again.
|
||||
pub fn set<R>(ptr: &CallThreadState, closure: impl FnOnce() -> R) -> R {
|
||||
struct Reset<'a, T: Copy>(&'a Cell<T>, T);
|
||||
pub fn set<R>(state: &CallThreadState<'_>, closure: impl FnOnce() -> R) -> Result<R, Trap> {
|
||||
struct Reset<'a, 'b>(&'a CallThreadState<'b>);
|
||||
|
||||
impl<T: Copy> Drop for Reset<'_, T> {
|
||||
impl Drop for Reset<'_, '_> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
self.0.set(self.1);
|
||||
raw::replace(self.0.prev.replace(ptr::null()))
|
||||
.expect("tls should be previously initialized");
|
||||
}
|
||||
}
|
||||
|
||||
PTR.with(|p| {
|
||||
let _r = Reset(p, p.replace(ptr));
|
||||
closure()
|
||||
})
|
||||
// Note that this extension of the lifetime to `'static` should be
|
||||
// safe because we only ever access it below with an anonymous
|
||||
// lifetime, meaning `'static` never leaks out of this module.
|
||||
let ptr = unsafe {
|
||||
mem::transmute::<*const CallThreadState<'_>, *const CallThreadState<'static>>(state)
|
||||
};
|
||||
let prev = raw::replace(ptr)?;
|
||||
state.prev.set(prev);
|
||||
let _reset = Reset(state);
|
||||
Ok(closure())
|
||||
}
|
||||
|
||||
/// Returns the last pointer configured with `set` above. Panics if `set`
|
||||
/// has not been previously called.
|
||||
pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState>) -> R) -> R {
|
||||
PTR.with(|ptr| {
|
||||
let p = ptr.get();
|
||||
pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState<'_>>) -> R) -> R {
|
||||
let p = raw::get();
|
||||
unsafe { closure(if p.is_null() { None } else { Some(&*p) }) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn lazy_per_thread_init() -> Result<(), Trap> {
|
||||
// Unused on Windows
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A module for registering a custom alternate signal stack (sigaltstack).
|
||||
///
|
||||
/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
|
||||
@@ -779,7 +893,7 @@ mod tls {
|
||||
/// and registering our own alternate stack that is large enough and has a guard
|
||||
/// page.
|
||||
#[cfg(unix)]
|
||||
fn setup_unix_sigaltstack() -> Result<(), Trap> {
|
||||
pub fn lazy_per_thread_init() -> Result<(), Trap> {
|
||||
use std::cell::RefCell;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
@@ -834,7 +948,7 @@ fn setup_unix_sigaltstack() -> Result<(), Trap> {
|
||||
0,
|
||||
);
|
||||
if ptr == libc::MAP_FAILED {
|
||||
return Err(Trap::new_from_runtime(TrapCode::VMOutOfMemory));
|
||||
return Err(Trap::oom());
|
||||
}
|
||||
|
||||
// Prepare the stack with readable/writable memory and then register it
|
||||
|
||||
@@ -384,7 +384,7 @@ impl VMMemoryDefinition {
|
||||
.checked_add(len)
|
||||
.map_or(true, |m| m > self.current_length)
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::HeapAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds));
|
||||
}
|
||||
|
||||
let dst = usize::try_from(dst).unwrap();
|
||||
@@ -414,7 +414,7 @@ impl VMMemoryDefinition {
|
||||
.checked_add(len)
|
||||
.map_or(true, |m| m > self.current_length)
|
||||
{
|
||||
return Err(Trap::new_from_runtime(TrapCode::HeapAccessOutOfBounds));
|
||||
return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds));
|
||||
}
|
||||
|
||||
let dst = isize::try_from(dst).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user