pub mod execution;
mod siginfo_ext;
pub mod signals;
pub mod state;
pub use crate::instance::execution::{KillError, KillState, KillSuccess, KillSwitch};
pub use crate::instance::signals::{signal_handler_none, SignalBehavior, SignalHandler};
pub use crate::instance::state::State;
use crate::alloc::{Alloc, HOST_PAGE_SIZE_EXPECTED};
use crate::context::Context;
use crate::embed_ctx::CtxMap;
use crate::error::Error;
use crate::module::{self, FunctionHandle, FunctionPointer, Global, GlobalValue, Module, TrapCode};
use crate::region::RegionInternal;
use crate::val::{UntypedRetVal, Val};
use crate::WASM_PAGE_SIZE;
use libc::{c_void, pthread_self, siginfo_t, uintptr_t};
use lucet_module::InstanceRuntimeData;
use memoffset::offset_of;
use std::any::Any;
use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut, UnsafeCell};
use std::marker::PhantomData;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::ptr::{self, NonNull};
use std::sync::Arc;
pub const LUCET_INSTANCE_MAGIC: u64 = 746_932_922;
thread_local! {
pub(crate) static HOST_CTX: UnsafeCell<Context> = UnsafeCell::new(Context::new());
pub(crate) static CURRENT_INSTANCE: RefCell<Option<NonNull<Instance>>> = RefCell::new(None);
}
pub struct InstanceHandle {
inst: NonNull<Instance>,
needs_inst_drop: bool,
}
unsafe impl Send for InstanceHandle {}
pub fn new_instance_handle(
instance: *mut Instance,
module: Arc<dyn Module>,
alloc: Alloc,
embed_ctx: CtxMap,
) -> Result<InstanceHandle, Error> {
let inst = NonNull::new(instance)
.ok_or_else(|| lucet_format_err!("instance pointer is null; this is a bug"))?;
lucet_ensure!(
unsafe { inst.as_ref().magic } != LUCET_INSTANCE_MAGIC,
"created a new instance handle in memory with existing instance magic; this is a bug"
);
let mut handle = InstanceHandle {
inst,
needs_inst_drop: false,
};
let inst = Instance::new(alloc, module, embed_ctx);
unsafe {
ptr::write(&mut *handle, inst);
};
handle.needs_inst_drop = true;
handle.reset()?;
Ok(handle)
}
pub fn instance_handle_to_raw(mut inst: InstanceHandle) -> *mut Instance {
inst.needs_inst_drop = false;
inst.inst.as_ptr()
}
pub unsafe fn instance_handle_from_raw(
ptr: *mut Instance,
needs_inst_drop: bool,
) -> InstanceHandle {
InstanceHandle {
inst: NonNull::new_unchecked(ptr),
needs_inst_drop,
}
}
impl Deref for InstanceHandle {
type Target = Instance;
fn deref(&self) -> &Self::Target {
unsafe { self.inst.as_ref() }
}
}
impl DerefMut for InstanceHandle {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.inst.as_mut() }
}
}
impl Drop for InstanceHandle {
fn drop(&mut self) {
if self.needs_inst_drop {
unsafe {
let inst = self.inst.as_mut();
let region: Arc<dyn RegionInternal> = inst.alloc().region.clone();
std::ptr::drop_in_place(inst);
mem::drop(region);
}
}
}
}
#[repr(C)]
#[repr(align(4096))]
pub struct Instance {
magic: u64,
pub(crate) embed_ctx: CtxMap,
module: Arc<dyn Module>,
pub(crate) ctx: Context,
pub(crate) state: State,
pub(crate) kill_state: Arc<KillState>,
alloc: Alloc,
fatal_handler: fn(&Instance) -> !,
c_fatal_handler: Option<unsafe extern "C" fn(*mut Instance)>,
signal_handler: Box<
dyn Fn(
&Instance,
&Option<TrapCode>,
libc::c_int,
*const siginfo_t,
*const c_void,
) -> SignalBehavior,
>,
entrypoint: Option<FunctionPointer>,
pub(crate) resumed_val: Option<Box<dyn Any + 'static>>,
_padding: (),
}
impl Drop for Instance {
fn drop(&mut self) {
self.magic = 0;
}
}
#[derive(Debug)]
pub enum RunResult {
Returned(UntypedRetVal),
Yielded(YieldedVal),
}
impl RunResult {
pub fn returned(self) -> Result<UntypedRetVal, Error> {
match self {
RunResult::Returned(rv) => Ok(rv),
RunResult::Yielded(_) => Err(Error::InstanceNotReturned),
}
}
pub fn returned_ref(&self) -> Result<&UntypedRetVal, Error> {
match self {
RunResult::Returned(rv) => Ok(rv),
RunResult::Yielded(_) => Err(Error::InstanceNotReturned),
}
}
pub fn is_returned(&self) -> bool {
self.returned_ref().is_ok()
}
pub fn expect_returned(self, msg: &str) -> UntypedRetVal {
self.returned().expect(msg)
}
pub fn unwrap_returned(self) -> UntypedRetVal {
self.returned().unwrap()
}
pub fn yielded(self) -> Result<YieldedVal, Error> {
match self {
RunResult::Returned(_) => Err(Error::InstanceNotYielded),
RunResult::Yielded(yv) => Ok(yv),
}
}
pub fn yielded_ref(&self) -> Result<&YieldedVal, Error> {
match self {
RunResult::Returned(_) => Err(Error::InstanceNotYielded),
RunResult::Yielded(yv) => Ok(yv),
}
}
pub fn is_yielded(&self) -> bool {
self.yielded_ref().is_ok()
}
pub fn expect_yielded(self, msg: &str) -> YieldedVal {
self.yielded().expect(msg)
}
pub fn unwrap_yielded(self) -> YieldedVal {
self.yielded().unwrap()
}
}
pub trait InstanceInternal {
fn alloc(&self) -> &Alloc;
fn alloc_mut(&mut self) -> &mut Alloc;
fn module(&self) -> &dyn Module;
fn state(&self) -> &State;
fn valid_magic(&self) -> bool;
}
impl InstanceInternal for Instance {
fn alloc(&self) -> &Alloc {
&self.alloc
}
fn alloc_mut(&mut self) -> &mut Alloc {
&mut self.alloc
}
fn module(&self) -> &dyn Module {
self.module.deref()
}
fn state(&self) -> &State {
&self.state
}
fn valid_magic(&self) -> bool {
self.magic == LUCET_INSTANCE_MAGIC
}
}
impl Instance {
pub fn run(&mut self, entrypoint: &str, args: &[Val]) -> Result<RunResult, Error> {
let func = self.module.get_export_func(entrypoint)?;
self.run_func(func, &args)
}
pub fn run_func_idx(
&mut self,
table_idx: u32,
func_idx: u32,
args: &[Val],
) -> Result<RunResult, Error> {
let func = self.module.get_func_from_idx(table_idx, func_idx)?;
self.run_func(func, &args)
}
pub fn resume(&mut self) -> Result<RunResult, Error> {
self.resume_with_val(EmptyYieldVal)
}
pub fn resume_with_val<A: Any + 'static>(&mut self, val: A) -> Result<RunResult, Error> {
match &self.state {
State::Yielded { expecting, .. } => {
if !expecting.is::<PhantomData<A>>() {
return Err(Error::InvalidArgument(
"type mismatch between yielded instance expected value and resumed value",
));
}
}
_ => return Err(Error::InvalidArgument("can only resume a yielded instance")),
}
self.resumed_val = Some(Box::new(val) as Box<dyn Any + 'static>);
self.swap_and_return()
}
pub fn reset(&mut self) -> Result<(), Error> {
self.alloc.reset_heap(self.module.as_ref())?;
let globals = unsafe { self.alloc.globals_mut() };
let mod_globals = self.module.globals();
for (i, v) in mod_globals.iter().enumerate() {
globals[i] = match v.global() {
Global::Import { .. } => {
return Err(Error::Unsupported(format!(
"global imports are unsupported; found: {:?}",
v
)));
}
Global::Def(def) => def.init_val(),
};
}
self.state = State::Ready;
self.run_start()?;
Ok(())
}
pub fn grow_memory(&mut self, additional_pages: u32) -> Result<u32, Error> {
let additional_bytes = additional_pages
.checked_mul(WASM_PAGE_SIZE)
.ok_or_else(|| lucet_format_err!("additional pages larger than wasm address space",))?;
let orig_len = self
.alloc
.expand_heap(additional_bytes, self.module.as_ref())?;
Ok(orig_len / WASM_PAGE_SIZE)
}
pub fn heap(&self) -> &[u8] {
unsafe { self.alloc.heap() }
}
pub fn heap_mut(&mut self) -> &mut [u8] {
unsafe { self.alloc.heap_mut() }
}
pub fn heap_u32(&self) -> &[u32] {
unsafe { self.alloc.heap_u32() }
}
pub fn heap_u32_mut(&mut self) -> &mut [u32] {
unsafe { self.alloc.heap_u32_mut() }
}
pub fn globals(&self) -> &[GlobalValue] {
unsafe { self.alloc.globals() }
}
pub fn globals_mut(&mut self) -> &mut [GlobalValue] {
unsafe { self.alloc.globals_mut() }
}
pub fn check_heap<T>(&self, ptr: *const T, len: usize) -> bool {
self.alloc.mem_in_heap(ptr, len)
}
pub fn contains_embed_ctx<T: Any>(&self) -> bool {
self.embed_ctx.contains::<T>()
}
pub fn get_embed_ctx<T: Any>(&self) -> Option<Result<Ref<'_, T>, BorrowError>> {
self.embed_ctx.try_get::<T>()
}
pub fn get_embed_ctx_mut<T: Any>(&self) -> Option<Result<RefMut<'_, T>, BorrowMutError>> {
self.embed_ctx.try_get_mut::<T>()
}
pub fn insert_embed_ctx<T: Any>(&mut self, x: T) -> Option<T> {
self.embed_ctx.insert(x)
}
pub fn remove_embed_ctx<T: Any>(&mut self) -> Option<T> {
self.embed_ctx.remove::<T>()
}
pub fn set_signal_handler<H>(&mut self, handler: H)
where
H: 'static
+ Fn(
&Instance,
&Option<TrapCode>,
libc::c_int,
*const siginfo_t,
*const c_void,
) -> SignalBehavior,
{
self.signal_handler = Box::new(handler) as Box<SignalHandler>;
}
pub fn set_fatal_handler(&mut self, handler: fn(&Instance) -> !) {
self.fatal_handler = handler;
}
pub fn set_c_fatal_handler(&mut self, handler: unsafe extern "C" fn(*mut Instance)) {
self.c_fatal_handler = Some(handler);
}
pub fn kill_switch(&self) -> KillSwitch {
KillSwitch::new(Arc::downgrade(&self.kill_state))
}
pub fn is_ready(&self) -> bool {
self.state.is_ready()
}
pub fn is_yielded(&self) -> bool {
self.state.is_yielded()
}
pub fn is_faulted(&self) -> bool {
self.state.is_faulted()
}
pub fn is_terminated(&self) -> bool {
self.state.is_terminated()
}
#[doc(hidden)]
pub fn uninterruptable<T, F: FnOnce() -> T>(&mut self, f: F) -> T {
self.kill_state.begin_hostcall();
let res = f();
let stop_reason = self.kill_state.end_hostcall();
if let Some(termination_details) = stop_reason {
unsafe {
self.terminate(termination_details);
}
}
res
}
#[inline]
pub fn get_instruction_count(&self) -> Option<u64> {
if self.module.is_instruction_count_instrumented() {
return Some(self.get_instance_implicits().instruction_count);
}
None
}
#[inline]
pub fn set_instruction_count(&mut self, instruction_count: u64) {
self.get_instance_implicits_mut().instruction_count = instruction_count;
}
}
impl Instance {
fn new(alloc: Alloc, module: Arc<dyn Module>, embed_ctx: CtxMap) -> Self {
let globals_ptr = alloc.slot().globals as *mut i64;
let mut inst = Instance {
magic: LUCET_INSTANCE_MAGIC,
embed_ctx,
module,
ctx: Context::new(),
state: State::Ready,
kill_state: Arc::new(KillState::new()),
alloc,
fatal_handler: default_fatal_handler,
c_fatal_handler: None,
signal_handler: Box::new(signal_handler_none) as Box<SignalHandler>,
entrypoint: None,
resumed_val: None,
_padding: (),
};
inst.set_globals_ptr(globals_ptr);
inst.set_instruction_count(0);
assert_eq!(mem::size_of::<Instance>(), HOST_PAGE_SIZE_EXPECTED);
let unpadded_size = offset_of!(Instance, _padding);
assert!(unpadded_size <= HOST_PAGE_SIZE_EXPECTED - mem::size_of::<*mut i64>());
inst
}
#[inline]
fn get_instance_implicits(&self) -> &InstanceRuntimeData {
unsafe {
let implicits_ptr = (self as *const _ as *const u8)
.offset((HOST_PAGE_SIZE_EXPECTED - mem::size_of::<InstanceRuntimeData>()) as isize)
as *const InstanceRuntimeData;
mem::transmute::<*const InstanceRuntimeData, &InstanceRuntimeData>(implicits_ptr)
}
}
#[inline]
fn get_instance_implicits_mut(&mut self) -> &mut InstanceRuntimeData {
unsafe {
let implicits_ptr = (self as *mut _ as *mut u8)
.offset((HOST_PAGE_SIZE_EXPECTED - mem::size_of::<InstanceRuntimeData>()) as isize)
as *mut InstanceRuntimeData;
mem::transmute::<*mut InstanceRuntimeData, &mut InstanceRuntimeData>(implicits_ptr)
}
}
#[allow(dead_code)]
#[inline]
fn get_globals_ptr(&self) -> *mut i64 {
self.get_instance_implicits().globals_ptr
}
#[inline]
fn set_globals_ptr(&mut self, globals_ptr: *mut i64) {
self.get_instance_implicits_mut().globals_ptr = globals_ptr
}
fn run_func(&mut self, func: FunctionHandle, args: &[Val]) -> Result<RunResult, Error> {
if !(self.state.is_ready() || (self.state.is_faulted() && !self.state.is_fatal())) {
return Err(Error::InvalidArgument(
"instance must be ready or non-fatally faulted",
));
}
if func.ptr.as_usize() == 0 {
return Err(Error::InvalidArgument(
"entrypoint function cannot be null; this is probably a malformed module",
));
}
let sig = self.module.get_signature(func.id);
if sig.params.len() != args.len() {
return Err(Error::InvalidArgument(
"entrypoint function signature mismatch (number of arguments is incorrect)",
));
}
for (param_ty, arg) in sig.params.iter().zip(args.iter()) {
if param_ty != &arg.value_type() {
return Err(Error::InvalidArgument(
"entrypoint function signature mismatch",
));
}
}
self.entrypoint = Some(func.ptr);
let mut args_with_vmctx = vec![Val::from(self.alloc.slot().heap)];
args_with_vmctx.extend_from_slice(args);
let self_ptr = self as *mut _;
Context::init_with_callback(
unsafe { self.alloc.stack_u64_mut() },
&mut self.ctx,
execution::exit_guest_region,
self_ptr,
func.ptr.as_usize(),
&args_with_vmctx,
)?;
unsafe {
let top_of_stack = self.ctx.gpr.rsp as *mut u64;
self.ctx.gpr.rsi = *top_of_stack;
*top_of_stack = crate::context::lucet_context_activate as u64;
self.ctx.gpr.rdi = self.kill_state.terminable_ptr() as u64;
}
self.swap_and_return()
}
fn swap_and_return(&mut self) -> Result<RunResult, Error> {
debug_assert!(
self.state.is_ready()
|| (self.state.is_faulted() && !self.state.is_fatal())
|| self.state.is_yielded()
);
self.state = State::Running;
self.kill_state.schedule(unsafe { pthread_self() });
CURRENT_INSTANCE.with(|current_instance| {
let mut current_instance = current_instance.borrow_mut();
assert!(
current_instance.is_none(),
"no other instance is running on this thread"
);
*current_instance = Some(unsafe { NonNull::new_unchecked(self) });
});
self.with_signals_on(|i| {
HOST_CTX.with(|host_ctx| {
unsafe { Context::swap(&mut *host_ctx.get(), &mut i.ctx) };
Ok(())
})
})?;
CURRENT_INSTANCE.with(|current_instance| {
*current_instance.borrow_mut() = None;
});
self.kill_state.deschedule();
let st = mem::replace(&mut self.state, State::Transitioning);
match st {
State::Running => {
let retval = self.ctx.get_untyped_retval();
self.state = State::Ready;
Ok(RunResult::Returned(retval))
}
State::Terminating { details, .. } => {
self.state = State::Terminated;
Err(Error::RuntimeTerminated(details))
}
State::Yielding { val, expecting } => {
self.state = State::Yielded { expecting };
Ok(RunResult::Yielded(val))
}
State::Faulted {
mut details,
siginfo,
context,
} => {
details.rip_addr_details = self
.module
.addr_details(details.rip_addr as *const c_void)?;
self.state = State::Faulted {
details: details.clone(),
siginfo,
context,
};
if details.fatal {
if let Some(h) = self.c_fatal_handler {
unsafe { h(self as *mut Instance) }
}
(self.fatal_handler)(self)
} else {
Err(Error::RuntimeFault(details))
}
}
State::Ready | State::Terminated | State::Yielded { .. } | State::Transitioning => Err(
lucet_format_err!("\"impossible\" state found in `swap_and_return()`: {}", st),
),
}
}
fn run_start(&mut self) -> Result<(), Error> {
if let Some(start) = self.module.get_start_func()? {
let res = self.run_func(start, &[])?;
if res.is_yielded() {
return Err(Error::StartYielded);
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct FaultDetails {
pub fatal: bool,
pub trapcode: Option<TrapCode>,
pub rip_addr: uintptr_t,
pub rip_addr_details: Option<module::AddrDetails>,
}
impl std::fmt::Display for FaultDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.fatal {
write!(f, "fault FATAL ")?;
} else {
write!(f, "fault ")?;
}
if let Some(trapcode) = self.trapcode {
write!(f, "{:?} ", trapcode)?;
} else {
write!(f, "TrapCode::UNKNOWN ")?;
}
write!(f, "code at address {:p}", self.rip_addr as *const c_void)?;
if let Some(ref addr_details) = self.rip_addr_details {
if let Some(ref fname) = addr_details.file_name {
let sname = addr_details
.sym_name
.as_ref()
.map(String::as_str)
.unwrap_or("<unknown>");
write!(f, " (symbol {}:{})", fname, sname)?;
}
if addr_details.in_module_code {
write!(f, " (inside module code)")
} else {
write!(f, " (not inside module code)")
}
} else {
write!(f, " (unknown whether in module)")
}
}
}
pub enum TerminationDetails {
Signal,
CtxNotFound,
YieldTypeMismatch,
BorrowError(&'static str),
Provided(Box<dyn Any + 'static>),
Remote,
}
impl TerminationDetails {
pub fn provide<A: Any + 'static>(details: A) -> Self {
TerminationDetails::Provided(Box::new(details))
}
pub fn provided_details(&self) -> Option<&dyn Any> {
match self {
TerminationDetails::Provided(a) => Some(a.as_ref()),
_ => None,
}
}
}
#[test]
fn termination_details_any_typing() {
let hello = "hello, world".to_owned();
let details = TerminationDetails::provide(hello.clone());
let provided = details.provided_details().expect("got Provided");
assert_eq!(
provided.downcast_ref::<String>().expect("right type"),
&hello
);
}
impl PartialEq for TerminationDetails {
fn eq(&self, rhs: &TerminationDetails) -> bool {
use TerminationDetails::*;
match (self, rhs) {
(Signal, Signal) => true,
(BorrowError(msg1), BorrowError(msg2)) => msg1 == msg2,
(CtxNotFound, CtxNotFound) => true,
_ => false,
}
}
}
impl std::fmt::Debug for TerminationDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TerminationDetails::")?;
match self {
TerminationDetails::Signal => write!(f, "Signal"),
TerminationDetails::BorrowError(msg) => write!(f, "BorrowError({})", msg),
TerminationDetails::CtxNotFound => write!(f, "CtxNotFound"),
TerminationDetails::YieldTypeMismatch => write!(f, "YieldTypeMismatch"),
TerminationDetails::Provided(_) => write!(f, "Provided(Any)"),
TerminationDetails::Remote => write!(f, "Remote"),
}
}
}
unsafe impl Send for TerminationDetails {}
unsafe impl Sync for TerminationDetails {}
pub struct YieldedVal {
val: Box<dyn Any + 'static>,
}
impl std::fmt::Debug for YieldedVal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_none() {
write!(f, "YieldedVal {{ val: None }}")
} else {
write!(f, "YieldedVal {{ val: Some }}")
}
}
}
impl YieldedVal {
pub(crate) fn new<A: Any + 'static>(val: A) -> Self {
YieldedVal { val: Box::new(val) }
}
pub fn is_none(&self) -> bool {
self.val.is::<EmptyYieldVal>()
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
pub fn downcast<A: Any + 'static>(self) -> Result<Box<A>, YieldedVal> {
match self.val.downcast() {
Ok(val) => Ok(val),
Err(val) => Err(YieldedVal { val }),
}
}
pub fn downcast_ref<A: Any + 'static>(&self) -> Option<&A> {
self.val.downcast_ref()
}
}
#[derive(Debug)]
pub(crate) struct EmptyYieldVal;
fn default_fatal_handler(inst: &Instance) -> ! {
panic!("> instance {:p} had fatal error: {}", inst, inst.state);
}