diff options
Diffstat (limited to 'third_party/rust/lucet-runtime-wasmsbx/src')
-rw-r--r-- | third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs | 530 | ||||
-rw-r--r-- | third_party/rust/lucet-runtime-wasmsbx/src/lib.rs | 384 |
2 files changed, 914 insertions, 0 deletions
diff --git a/third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs b/third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs new file mode 100644 index 0000000000..d8ab481870 --- /dev/null +++ b/third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs @@ -0,0 +1,530 @@ +use crate::{DlModule, Instance, Limits, MmapRegion, Module, Region}; +use libc::{c_char, c_int, c_void}; +use lucet_module::TrapCode; +use lucet_runtime_internals::c_api::*; +use lucet_runtime_internals::instance::{ + instance_handle_from_raw, instance_handle_to_raw, InstanceInternal, +}; +use lucet_runtime_internals::vmctx::VmctxInternal; +use lucet_runtime_internals::WASM_PAGE_SIZE; +use lucet_runtime_internals::{ + assert_nonnull, lucet_hostcall_terminate, lucet_hostcalls, with_ffi_arcs, +}; +use num_traits::FromPrimitive; +use std::ffi::CStr; +use std::ptr; +use std::sync::{Arc, Once}; + +macro_rules! with_instance_ptr { + ( $name:ident, $body:block ) => {{ + assert_nonnull!($name); + let $name: &mut Instance = &mut *($name as *mut Instance); + $body + }}; +} + +macro_rules! with_instance_ptr_unchecked { + ( $name:ident, $body:block ) => {{ + let $name: &mut Instance = &mut *($name as *mut Instance); + $body + }}; +} + +#[no_mangle] +pub extern "C" fn lucet_error_name(e: c_int) -> *const c_char { + if let Some(e) = lucet_error::from_i32(e) { + use self::lucet_error::*; + match e { + Ok => "lucet_error_ok\0".as_ptr() as _, + InvalidArgument => "lucet_error_invalid_argument\0".as_ptr() as _, + RegionFull => "lucet_error_region_full\0".as_ptr() as _, + Module => "lucet_error_module\0".as_ptr() as _, + LimitsExceeded => "lucet_error_limits_exceeded\0".as_ptr() as _, + NoLinearMemory => "lucet_error_no_linear_memory\0".as_ptr() as _, + SymbolNotFound => "lucet_error_symbol_not_found\0".as_ptr() as _, + FuncNotFound => "lucet_error_func_not_found\0".as_ptr() as _, + RuntimeFault => "lucet_error_runtime_fault\0".as_ptr() as _, + RuntimeTerminated => "lucet_error_runtime_terminated\0".as_ptr() as _, + Dl => "lucet_error_dl\0".as_ptr() as _, + InstanceNotReturned => "lucet_error_instance_not_returned\0".as_ptr() as _, + InstanceNotYielded => "lucet_error_instance_not_yielded\0".as_ptr() as _, + StartYielded => "lucet_error_start_yielded\0".as_ptr() as _, + Internal => "lucet_error_internal\0".as_ptr() as _, + Unsupported => "lucet_error_unsupported\0".as_ptr() as _, + } + } else { + "!!! error: unknown lucet_error variant\0".as_ptr() as _ + } +} + +#[no_mangle] +pub extern "C" fn lucet_result_tag_name(tag: libc::c_int) -> *const c_char { + if let Some(tag) = lucet_result_tag::from_i32(tag) { + use self::lucet_result_tag::*; + match tag { + Returned => "lucet_result_tag_returned\0".as_ptr() as _, + Yielded => "lucet_result_tag_yielded\0".as_ptr() as _, + Faulted => "lucet_result_tag_faulted\0".as_ptr() as _, + Terminated => "lucet_result_tag_terminated\0".as_ptr() as _, + Errored => "lucet_result_tag_errored\0".as_ptr() as _, + } + } else { + "!!! unknown lucet_result_tag variant!\0".as_ptr() as _ + } +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_mmap_region_create( + instance_capacity: u64, + limits: *const lucet_alloc_limits, + region_out: *mut *mut lucet_region, +) -> lucet_error { + assert_nonnull!(region_out); + let limits = limits + .as_ref() + .map(|l| l.into()) + .unwrap_or(Limits::default()); + match MmapRegion::create(instance_capacity as usize, &limits) { + Ok(region) => { + let region_thin = Arc::into_raw(Arc::new(region as Arc<dyn Region>)); + region_out.write(region_thin as _); + return lucet_error::Ok; + } + Err(e) => return e.into(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_region_release(region: *const lucet_region) { + Arc::from_raw(region as *const Arc<dyn Region>); +} + +// omg this naming convention might not scale +#[no_mangle] +pub unsafe extern "C" fn lucet_region_new_instance_with_ctx( + region: *const lucet_region, + module: *const lucet_dl_module, + embed_ctx: *mut c_void, + inst_out: *mut *mut lucet_instance, +) -> lucet_error { + assert_nonnull!(inst_out); + with_ffi_arcs!([region: dyn Region, module: DlModule], { + region + .new_instance_builder(module.clone() as Arc<dyn Module>) + .with_embed_ctx(embed_ctx) + .build() + .map(|i| { + inst_out.write(instance_handle_to_raw(i) as _); + lucet_error::Ok + }) + .unwrap_or_else(|e| e.into()) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_region_new_instance( + region: *const lucet_region, + module: *const lucet_dl_module, + inst_out: *mut *mut lucet_instance, +) -> lucet_error { + lucet_region_new_instance_with_ctx(region, module, ptr::null_mut(), inst_out) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_dl_module_load( + path: *const c_char, + mod_out: *mut *mut lucet_dl_module, +) -> lucet_error { + assert_nonnull!(mod_out); + let path = CStr::from_ptr(path); + DlModule::load(path.to_string_lossy().into_owned()) + .map(|m| { + mod_out.write(Arc::into_raw(m) as _); + lucet_error::Ok + }) + .unwrap_or_else(|e| e.into()) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_dl_module_release(module: *const lucet_dl_module) { + Arc::from_raw(module as *const DlModule); +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_run( + inst: *mut lucet_instance, + entrypoint: *const c_char, + argc: usize, + argv: *const lucet_val::lucet_val, + result_out: *mut lucet_result::lucet_result, +) -> lucet_error { + assert_nonnull!(entrypoint); + if argc != 0 && argv.is_null() { + return lucet_error::InvalidArgument; + } + let args = if argc == 0 { + vec![] + } else { + std::slice::from_raw_parts(argv, argc) + .into_iter() + .map(|v| v.into()) + .collect() + }; + let entrypoint = match CStr::from_ptr(entrypoint).to_str() { + Ok(entrypoint_str) => entrypoint_str, + Err(_) => { + return lucet_error::SymbolNotFound; + } + }; + + with_instance_ptr!(inst, { + let res = inst.run(entrypoint, args.as_slice()); + let ret = res + .as_ref() + .map(|_| lucet_error::Ok) + .unwrap_or_else(|e| e.into()); + if !result_out.is_null() { + std::ptr::write(result_out, res.into()); + } + ret + }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_run_func_idx( + inst: *mut lucet_instance, + table_idx: u32, + func_idx: u32, + argc: usize, + argv: *const lucet_val::lucet_val, + result_out: *mut lucet_result::lucet_result, +) -> lucet_error { + if argc != 0 && argv.is_null() { + return lucet_error::InvalidArgument; + } + let args = if argc == 0 { + vec![] + } else { + std::slice::from_raw_parts(argv, argc) + .into_iter() + .map(|v| v.into()) + .collect() + }; + with_instance_ptr!(inst, { + let res = inst.run_func_idx(table_idx, func_idx, args.as_slice()); + let ret = res + .as_ref() + .map(|_| lucet_error::Ok) + .unwrap_or_else(|e| e.into()); + if !result_out.is_null() { + std::ptr::write(result_out, res.into()); + } + ret + }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_resume( + inst: *const lucet_instance, + val: *mut c_void, + result_out: *mut lucet_result::lucet_result, +) -> lucet_error { + with_instance_ptr!(inst, { + let res = inst.resume_with_val(CYieldedVal { val }); + let ret = res + .as_ref() + .map(|_| lucet_error::Ok) + .unwrap_or_else(|e| e.into()); + if !result_out.is_null() { + std::ptr::write(result_out, res.into()); + } + ret + }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_reset(inst: *mut lucet_instance) -> lucet_error { + with_instance_ptr!(inst, { + inst.reset() + .map(|_| lucet_error::Ok) + .unwrap_or_else(|e| e.into()) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_release(inst: *mut lucet_instance) { + instance_handle_from_raw(inst as *mut Instance, true); +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_heap(inst: *mut lucet_instance) -> *mut u8 { + with_instance_ptr_unchecked!(inst, { inst.heap_mut().as_mut_ptr() }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_heap_len(inst: *const lucet_instance) -> u32 { + with_instance_ptr_unchecked!(inst, { inst.heap().len() as u32 }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_check_heap( + inst: *const lucet_instance, + ptr: *const c_void, + len: usize, +) -> bool { + with_instance_ptr_unchecked!(inst, { inst.check_heap(ptr, len) }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_grow_heap( + inst: *mut lucet_instance, + additional_pages: u32, + previous_pages_out: *mut u32, +) -> lucet_error { + with_instance_ptr!(inst, { + match inst.grow_memory(additional_pages) { + Ok(previous_pages) => { + if !previous_pages_out.is_null() { + previous_pages_out.write(previous_pages); + } + lucet_error::Ok + } + Err(e) => e.into(), + } + }) +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_embed_ctx(inst: *mut lucet_instance) -> *mut c_void { + with_instance_ptr_unchecked!(inst, { + inst.get_embed_ctx::<*mut c_void>() + .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut())) + .unwrap_or(ptr::null_mut()) + }) +} + +/// Release or run* must not be called in the body of this function! +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_set_signal_handler( + inst: *mut lucet_instance, + signal_handler: lucet_signal_handler, +) -> lucet_error { + let handler = move |inst: &Instance, trap: &Option<TrapCode>, signum, siginfo, context| { + let inst = inst as *const Instance as *mut lucet_instance; + signal_handler(inst, trap.into(), signum, siginfo, context).into() + }; + with_instance_ptr!(inst, { + inst.set_signal_handler(handler); + }); + lucet_error::Ok +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_instance_set_fatal_handler( + inst: *mut lucet_instance, + fatal_handler: lucet_fatal_handler, +) -> lucet_error { + // transmuting is fine here because *mut lucet_instance = *mut Instance + let fatal_handler: unsafe extern "C" fn(inst: *mut Instance) = + std::mem::transmute(fatal_handler); + with_instance_ptr!(inst, { + inst.set_c_fatal_handler(fatal_handler); + }); + lucet_error::Ok +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_retval_gp(retval: *const lucet_untyped_retval) -> lucet_retval_gp { + lucet_retval_gp { + as_untyped: (*retval).gp, + } +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_retval_f32(retval: *const lucet_untyped_retval) -> f32 { + let mut v = 0.0f32; + core::arch::x86_64::_mm_storeu_ps( + &mut v as *mut f32, + core::arch::x86_64::_mm_loadu_ps((*retval).fp.as_ptr() as *const f32), + ); + v +} + +#[no_mangle] +pub unsafe extern "C" fn lucet_retval_f64(retval: *const lucet_untyped_retval) -> f64 { + let mut v = 0.0f64; + core::arch::x86_64::_mm_storeu_pd( + &mut v as *mut f64, + core::arch::x86_64::_mm_loadu_pd((*retval).fp.as_ptr() as *const f64), + ); + v +} + +static C_API_INIT: Once = Once::new(); + +/// Should never actually be called, but should be reachable via a trait method to prevent DCE. +pub fn ensure_linked() { + use std::ptr::read_volatile; + C_API_INIT.call_once(|| unsafe { + read_volatile(lucet_vmctx_get_heap as *const extern "C" fn()); + read_volatile(lucet_vmctx_current_memory as *const extern "C" fn()); + read_volatile(lucet_vmctx_grow_memory as *const extern "C" fn()); + }); +} + +lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_get_heap( + &mut vmctx, + ) -> *mut u8 { + vmctx.instance().alloc().slot().heap as *mut u8 + } + + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_get_globals( + &mut vmctx, + ) -> *mut i64 { + vmctx.instance().alloc().slot().globals as *mut i64 + } + + #[no_mangle] + /// Get the number of WebAssembly pages currently in the heap. + pub unsafe extern "C" fn lucet_vmctx_current_memory( + &mut vmctx, + ) -> u32 { + vmctx.instance().alloc().heap_len() as u32 / WASM_PAGE_SIZE + } + + #[no_mangle] + /// Grows the guest heap by the given number of WebAssembly pages. + /// + /// On success, returns the number of pages that existed before the call. On failure, returns `-1`. + pub unsafe extern "C" fn lucet_vmctx_grow_memory( + &mut vmctx, + additional_pages: u32, + ) -> i32 { + if let Ok(old_pages) = vmctx.instance_mut().grow_memory(additional_pages) { + old_pages as i32 + } else { + -1 + } + } + + #[no_mangle] + /// Check if a memory region is inside the instance heap. + pub unsafe extern "C" fn lucet_vmctx_check_heap( + &mut vmctx, + ptr: *mut c_void, + len: libc::size_t, + ) -> bool { + vmctx.instance().check_heap(ptr, len) + } + + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_get_func_from_idx( + &mut vmctx, + table_idx: u32, + func_idx: u32, + ) -> *const c_void { + vmctx.instance() + .module() + .get_func_from_idx(table_idx, func_idx) + .map(|fptr| fptr.ptr.as_usize() as *const c_void) + .unwrap_or(std::ptr::null()) + } + + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_terminate( + &mut _vmctx, + details: *mut c_void, + ) -> () { + lucet_hostcall_terminate!(CTerminationDetails { details }); + } + + #[no_mangle] + /// Get the delegate object for the current instance. + /// + /// TODO: rename + pub unsafe extern "C" fn lucet_vmctx_get_delegate( + &mut vmctx, + ) -> *mut c_void { + vmctx.instance() + .get_embed_ctx::<*mut c_void>() + .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut())) + .unwrap_or(std::ptr::null_mut()) + } + + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_yield( + &mut vmctx, + val: *mut c_void, + ) -> *mut c_void { + vmctx + .yield_val_try_val(CYieldedVal { val }) + .map(|CYieldedVal { val }| val) + .unwrap_or(std::ptr::null_mut()) + } +} + +#[cfg(test)] +mod tests { + use super::lucet_dl_module; + use crate::DlModule; + use lucet_module::bindings::Bindings; + use lucet_wasi_sdk::{CompileOpts, LinkOpt, LinkOpts, Lucetc}; + use lucetc::LucetcOpts; + use std::sync::Arc; + use tempfile::TempDir; + + extern "C" { + fn lucet_runtime_test_expand_heap(module: *mut lucet_dl_module) -> bool; + fn lucet_runtime_test_yield_resume(module: *mut lucet_dl_module) -> bool; + } + + #[test] + fn expand_heap() { + let workdir = TempDir::new().expect("create working directory"); + + let native_build = Lucetc::new(&["tests/guests/null.c"]) + .with_cflag("-nostartfiles") + .with_link_opt(LinkOpt::NoDefaultEntryPoint) + .with_link_opt(LinkOpt::AllowUndefinedAll) + .with_link_opt(LinkOpt::ExportAll); + + let so_file = workdir.path().join("null.so"); + + native_build.build(so_file.clone()).unwrap(); + + let dlmodule = DlModule::load(so_file).unwrap(); + + unsafe { + assert!(lucet_runtime_test_expand_heap( + Arc::into_raw(dlmodule) as *mut lucet_dl_module + )); + } + } + + #[test] + fn yield_resume() { + let workdir = TempDir::new().expect("create working directory"); + + let native_build = Lucetc::new(&["tests/guests/yield_resume.c"]) + .with_cflag("-nostartfiles") + .with_link_opt(LinkOpt::NoDefaultEntryPoint) + .with_link_opt(LinkOpt::AllowUndefinedAll) + .with_link_opt(LinkOpt::ExportAll) + .with_bindings(Bindings::from_file("tests/guests/yield_resume_bindings.json").unwrap()); + + let so_file = workdir.path().join("yield_resume.so"); + + native_build.build(so_file.clone()).unwrap(); + + let dlmodule = DlModule::load(so_file).unwrap(); + + unsafe { + assert!(lucet_runtime_test_yield_resume( + Arc::into_raw(dlmodule) as *mut lucet_dl_module + )); + } + } +} diff --git a/third_party/rust/lucet-runtime-wasmsbx/src/lib.rs b/third_party/rust/lucet-runtime-wasmsbx/src/lib.rs new file mode 100644 index 0000000000..60126a9e41 --- /dev/null +++ b/third_party/rust/lucet-runtime-wasmsbx/src/lib.rs @@ -0,0 +1,384 @@ +//! # Lucet Runtime for Sandboxed WebAssembly Applications +//! +//! This crate runs programs that were compiled with the `lucetc` WebAssembly to native code +//! compiler. It provides an interface for modules to be loaded from shared object files (see +//! `DlModule`), and for hosts to provide specialized functionality to guests (see +//! `Instance::embed_ctx()`). +//! +//! The runtime is a critical part of the safety and security story for Lucet. While the semantics +//! of WebAssembly and the `lucetc` compiler provide many guarantees, the runtime must be correct in +//! order for the assumptions of those guarantees to hold. For example, the runtime uses guard pages +//! to ensure that any attempts by guest programs to access memory past the end of the guest heap are +//! safely caught. +//! +//! The runtime is also extensible, and some of the key types are defined as traits for +//! flexibility. See the `lucet-runtime-internals` crate for details. +//! +//! ## Running a Lucet Program +//! +//! There are a few essential types for using the runtime: +//! +//! - [`Instance`](struct.Instance.html): a Lucet program, together with its dedicated memory and +//! signal handlers. Users of this API never own an `Instance` directly, but can own the +//! [`InstanceHandle`](struct.InstanceHandle.html) smart pointer. +//! +//! - [`Region`](trait.Region.html): the memory from which instances are created. This crate +//! includes [`MmapRegion`](struct.MmapRegion.html), an implementation backed by `mmap`. +//! +//! - [`Limits`](struct.Limits.html): upper bounds for the resources a Lucet instance may +//! consume. These may be larger or smaller than the limits described in the WebAssembly module +//! itself; the smaller limit is always enforced. +//! +//! - [`Module`](trait.Module.html): the read-only parts of a Lucet program, including its code and +//! initial heap configuration. This crate includes [`DlModule`](struct.DlModule.html), an +//! implementation backed by dynamic loading of shared objects. +//! +//! - [`Val`](enum.Val.html): an enum describing values in WebAssembly, used to provide +//! arguments. These can be created using `From` implementations of primitive types, for example +//! `5u64.into()` in the example below. +//! +//! - [`RunResult`](enum.RunResult.html): the result of running or resuming an instance. These +//! contain either `UntypedRetVal`s for WebAssembly functions that have returned, or `YieldedVal`s +//! for WebAssembly programs that have yielded. +//! +//! - [`UntypedRetVal`](struct.UntypedRetVal.html): values returned from WebAssembly +//! functions. These must be interpreted at the correct type by the user via `From` implementations +//! or `retval.as_T()` methods, for example `u64::from(retval)` in the example below. +//! +//! - [`YieldedVal`](struct.YieldedVal.html): dynamically-values yielded by WebAssembly +//! programs. Not all yield points are given values, so this may be empty. To use the values, if +//! present, you must first downcast them with the provided methods. +//! +//! To run a Lucet program, you start by creating a region, capable of backing a number of +//! instances. You then load a module and then create a new instance using the region and the +//! module. You can then run any of the functions that the Lucet program exports, retrieve return +//! values from those functions, and access the linear memory of the guest. +//! +//! ```no_run +//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region}; +//! +//! let module = DlModule::load("/my/lucet/module.so").unwrap(); +//! let region = MmapRegion::create(1, &Limits::default()).unwrap(); +//! let mut inst = region.new_instance(module).unwrap(); +//! +//! let retval = inst.run("factorial", &[5u64.into()]).unwrap().unwrap_returned(); +//! assert_eq!(u64::from(retval), 120u64); +//! ``` +//! +//! ## Embedding With Hostcalls +//! +//! A "hostcall" is a function called by WebAssembly that is not defined in WebAssembly. Since +//! WebAssembly is such a minimal language, hostcalls are required for Lucet programs to do anything +//! interesting with the outside world. For example, in Fastly's [Terrarium +//! demo](https://wasm.fastly-labs.com/), hostcalls are provided for manipulating HTTP requests, +//! accessing a key/value store, etc. +//! +//! Some simple hostcalls can be implemented by wrapping an externed C function with the +//! [`lucet_hostcalls!`](macro.lucet_hostcalls.html] macro. The function must take a special `&mut +//! vmctx` argument for the guest context, similar to `&mut self` on methods. Hostcalls that require +//! access to some underlying state, such as the key/value store in Terrarium, can access a custom +//! embedder context through `vmctx`. For example, to make a `u32` available to hostcalls: +//! +//! ```no_run +//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region, lucet_hostcalls}; +//! use lucet_runtime::vmctx::{Vmctx, lucet_vmctx}; +//! +//! struct MyContext { x: u32 } +//! +//! lucet_hostcalls! { +//! #[no_mangle] +//! pub unsafe extern "C" fn foo( +//! &mut vmctx, +//! ) -> () { +//! let mut hostcall_context = vmctx.get_embed_ctx_mut::<MyContext>(); +//! hostcall_context.x = 42; +//! } +//! } +//! +//! let module = DlModule::load("/my/lucet/module.so").unwrap(); +//! let region = MmapRegion::create(1, &Limits::default()).unwrap(); +//! let mut inst = region +//! .new_instance_builder(module) +//! .with_embed_ctx(MyContext { x: 0 }) +//! .build() +//! .unwrap(); +//! +//! inst.run("call_foo", &[]).unwrap(); +//! +//! let context_after = inst.get_embed_ctx::<MyContext>().unwrap().unwrap(); +//! assert_eq!(context_after.x, 42); +//! ``` +//! +//! The embedder context is backed by a structure that can hold a single value of any type. Rust +//! embedders should add their own custom state type (like `MyContext` above) for any context they +//! require, rather than using a common type (such as the `u32`) from the standard library. This +//! avoids collisions between libraries, and allows for easy composition of embeddings. +//! +//! For C-based embedders, the type `*mut libc::c_void` is privileged as the only type that the C +//! API provides. The following example shows how a Rust embedder can initialize a C-compatible +//! context: +//! +//! ```no_run +//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region}; +//! +//! let module = DlModule::load("/my/lucet/module.so").unwrap(); +//! let region = MmapRegion::create(1, &Limits::default()).unwrap(); +//! #[repr(C)] +//! struct MyForeignContext { x: u32 }; +//! let mut foreign_ctx = Box::into_raw(Box::new(MyForeignContext{ x: 0 })); +//! let mut inst = region +//! .new_instance_builder(module) +//! .with_embed_ctx(foreign_ctx as *mut libc::c_void) +//! .build() +//! .unwrap(); +//! +//! inst.run("main", &[]).unwrap(); +//! +//! // clean up embedder context +//! drop(inst); +//! // foreign_ctx must outlive inst, but then must be turned back into a box +//! // in order to drop. +//! unsafe { Box::from_raw(foreign_ctx) }; +//! ``` +//! +//! ## Yielding and Resuming +//! +//! Lucet hostcalls can use the `vmctx` argument to yield, suspending themselves and optionally +//! returning a value back to the host context. A yielded instance can then be resumed by the host, +//! and execution will continue from the point of the yield. +//! +//! Four yield methods are available for hostcall implementors: +//! +//! | | Yields value? | Expects value? | +//! |-------------------------------------------------------------------------------------|---------------|----------------| +//! | [`yield_`](vmctx/struct.Vmctx.html#method.yield_) | ❌ | ❌ | +//! | [`yield_val`](vmctx/struct.Vmctx.html#method.yield_val) | ✅ | ❌ | +//! | [`yield_expecting_val`](vmctx/struct.Vmctx.html#method.yield_expecting_val) | ❌ | ✅ | +//! | [`yield_val_expecting_val`](vmctx/struct.Vmctx.html#method.yield_val_expecting_val) | ✅ | ✅ | +//! +//! The host is free to ignore values yielded by guests, but a yielded instance may only be resumed +//! with a value of the correct type using +//! [`Instance::resume_with_val()`](struct.Instance.html#method.resume_with_val), if one is +//! expected. +//! +//! ### Factorial example +//! +//! In this example, we use yielding and resuming to offload multiplication to the host context, and +//! to incrementally return results to the host. While certainly overkill for computing a factorial +//! function, this structure mirrors that of many asynchronous workflows. +//! +//! Since the focus of this example is on the behavior of hostcalls that yield, our Lucet guest +//! program just invokes a hostcall: +//! +//! ```no_run +//! // factorials_guest.rs +//! extern "C" { +//! fn hostcall_factorials(n: u64) -> u64; +//! } +//! +//! #[no_mangle] +//! pub extern "C" fn run() -> u64 { +//! unsafe { +//! hostcall_factorials(5) +//! } +//! } +//! ``` +//! +//! In our hostcall, there are two changes from a standard recursive implementation of factorial. +//! +//! - Instead of performing the `n * fact(n - 1)` multiplication ourselves, we yield the operands +//! and expect the product when resumed. +//! +//! - Whenever we have computed a factorial, including both intermediate values and the final +//! answer, we yield it. +//! +//! The final answer is returned normally as the result of the guest function. +//! +//! To implement this, we introduce a new `enum` type to represent what we want the host to do next, +//! and yield it when appropriate. +//! +//! ```no_run +//! use lucet_runtime::lucet_hostcalls; +//! use lucet_runtime::vmctx::Vmctx; +//! +//! pub enum FactorialsK { +//! Mult(u64, u64), +//! Result(u64), +//! } +//! +//! lucet_hostcalls! { +//! #[no_mangle] +//! pub unsafe extern "C" fn hostcall_factorials( +//! &mut vmctx, +//! n: u64, +//! ) -> u64 { +//! fn fact(vmctx: &mut Vmctx, n: u64) -> u64 { +//! let result = if n <= 1 { +//! 1 +//! } else { +//! let n_rec = fact(vmctx, n - 1); +//! // yield a request for the host to perform multiplication +//! vmctx.yield_val_expecting_val(FactorialsK::Mult(n, n_rec)) +//! // once resumed, that yield evaluates to the multiplication result +//! }; +//! // yield a result +//! vmctx.yield_val(FactorialsK::Result(result)); +//! result +//! } +//! fact(vmctx, n) +//! } +//! } +//! ``` +//! +//! The host side of the code, then, is an interpreter that repeatedly checks the yielded value and +//! performs the appropriate operation. The hostcall returns normally with the final answer when it +//! is finished, so we exit the loop when the run/resume result is `Ok`. +//! +//! ```no_run +//! # pub enum FactorialsK { +//! # Mult(u64, u64), +//! # Result(u64), +//! # } +//! use lucet_runtime::{DlModule, Error, Limits, MmapRegion, Region}; +//! +//! let module = DlModule::load("factorials_guest.so").unwrap(); +//! let region = MmapRegion::create(1, &Limits::default()).unwrap(); +//! let mut inst = region.new_instance(module).unwrap(); +//! +//! let mut factorials = vec![]; +//! +//! let mut res = inst.run("run", &[]).unwrap(); +//! +//! while let Ok(val) = res.yielded_ref() { +//! if let Some(k) = val.downcast_ref::<FactorialsK>() { +//! match k { +//! FactorialsK::Mult(n, n_rec) => { +//! // guest wants us to multiply for it +//! res = inst.resume_with_val(n * n_rec).unwrap(); +//! } +//! FactorialsK::Result(n) => { +//! // guest is returning an answer +//! factorials.push(*n); +//! res = inst.resume().unwrap(); +//! } +//! } +//! } else { +//! panic!("didn't yield with expected type"); +//! } +//! } +//! +//! // intermediate values are correct +//! assert_eq!(factorials.as_slice(), &[1, 2, 6, 24, 120]); +//! // final value is correct +//! assert_eq!(u64::from(res.unwrap_returned()), 120u64); +//! ``` +//! +//! ## Custom Signal Handlers +//! +//! Since Lucet programs are run as native machine code, signals such as `SIGSEGV` and `SIGFPE` can +//! arise during execution. Rather than letting these signals bring down the entire process, the +//! Lucet runtime installs alternate signal handlers that limit the effects to just the instance +//! that raised the signal. +//! +//! By default, the signal handler sets the instance state to `State::Fault` and returns early from +//! the call to `Instance::run()`. You can, however, implement custom error recovery and logging +//! behavior by defining new signal handlers on a per-instance basis. For example, the following +//! signal handler increments a counter of signals it has seen before setting the fault state: +//! +//! ```no_run +//! use lucet_runtime::{ +//! DlModule, Error, Instance, Limits, MmapRegion, Region, SignalBehavior, TrapCode, +//! }; +//! use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; +//! +//! static SIGNAL_COUNT: AtomicUsize = ATOMIC_USIZE_INIT; +//! +//! fn signal_handler_count( +//! _inst: &Instance, +//! _trapcode: &Option<TrapCode>, +//! _signum: libc::c_int, +//! _siginfo_ptr: *const libc::siginfo_t, +//! _ucontext_ptr: *const libc::c_void, +//! ) -> SignalBehavior { +//! SIGNAL_COUNT.fetch_add(1, Ordering::SeqCst); +//! SignalBehavior::Default +//! } +//! +//! let module = DlModule::load("/my/lucet/module.so").unwrap(); +//! let region = MmapRegion::create(1, &Limits::default()).unwrap(); +//! let mut inst = region.new_instance(module).unwrap(); +//! +//! // install the handler +//! inst.set_signal_handler(signal_handler_count); +//! +//! match inst.run("raise_a_signal", &[]) { +//! Err(Error::RuntimeFault(_)) => { +//! println!("I've now handled {} signals!", SIGNAL_COUNT.load(Ordering::SeqCst)); +//! } +//! res => panic!("unexpected result: {:?}", res), +//! } +//! ``` +//! +//! When implementing custom signal handlers for the Lucet runtime, the usual caveats about signal +//! safety apply: see +//! [`signal-safety(7)`](http://man7.org/linux/man-pages/man7/signal-safety.7.html). +//! +//! ## Interaction With Host Signal Handlers +//! +//! Great care must be taken if host application installs or otherwise modifies signal handlers +//! anywhere in the process. Lucet installs handlers for `SIGBUS`, `SIGFPE`, `SIGILL`, and `SIGSEGV` +//! when the first Lucet instance begins running, and restores the preëxisting handlers when the +//! last Lucet instance terminates. During this time, other threads in the host process *must not* +//! modify those signal handlers, since signal handlers can only be installed on a process-wide +//! basis. +//! +//! Despite this limitation, Lucet is designed to compose with other signal handlers in the host +//! program. If one of the above signals is caught by the Lucet signal handler, but that thread is +//! not currently running a Lucet instance, the saved host signal handler is called. This means +//! that, for example, a `SIGSEGV` on a non-Lucet thread of a host program will still likely abort +//! the entire process. + +#![deny(bare_trait_objects)] + +pub mod c_api; + +#[cfg(feature = "signature_checking")] +pub use lucet_module::PublicKey; +pub use lucet_module::TrapCode; +pub use lucet_runtime_internals::alloc::Limits; +pub use lucet_runtime_internals::error::Error; +pub use lucet_runtime_internals::instance::{ + FaultDetails, Instance, InstanceHandle, RunResult, SignalBehavior, TerminationDetails, + YieldedVal, +}; +pub use lucet_runtime_internals::module::{DlModule, Module}; +pub use lucet_runtime_internals::region::mmap::MmapRegion; +pub use lucet_runtime_internals::region::{InstanceBuilder, Region, RegionCreate}; +pub use lucet_runtime_internals::val::{UntypedRetVal, Val}; +pub use lucet_runtime_internals::{lucet_hostcall_terminate, lucet_hostcalls, WASM_PAGE_SIZE}; + +pub mod vmctx { + //! Functions for manipulating instances from hostcalls. + //! + //! The Lucet compiler inserts an extra `*mut lucet_vmctx` argument to all functions defined and + //! called by WebAssembly code. Through this pointer, code running in the guest context can + //! access and manipulate the instance and its structures. These functions are intended for use + //! in hostcall implementations, and must only be used from within a running guest. + //! + //! # Panics + //! + //! All of the `Vmctx` methods will panic if the `Vmctx` was not created from a valid pointer + //! associated with a running instance. This should never occur if run in guest code on the + //! pointer argument inserted by the compiler. + pub use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx}; +} + +/// Call this if you're having trouble with `lucet_*` symbols not being exported. +/// +/// This is pretty hackish; we will hopefully be able to avoid this altogether once [this +/// issue](https://github.com/rust-lang/rust/issues/58037) is addressed. +#[no_mangle] +#[doc(hidden)] +pub extern "C" fn lucet_internal_ensure_linked() { + self::c_api::ensure_linked(); +} |