feat: add ability to dump extism kernel memory and generate coredumps, cleanup kernel memory layout (#539)

Fixes https://github.com/extism/extism/issues/537

- Requires wasmtime 14.0.0 or greater for coredump serialization
- Adds `EXTISM_COREDUMP` environment variable to write wasmtime coredump
to a file when an error occurs
- This will create a coredump from the main wasm module, which means it
doesn't give us too much insight into the kernel
- Adds `EXTISM_MEMDUMP` environment variable to write extism linear
memory to a file when an error occurs
  - This gives us access to the kernel memory  
- Adds some missing profiling options
- Converts timeouts to a Trap instead of a plain error, this helps us
get better information about where the timeout occured
- Some small improvements to error handling after a plugin call
- Adds a test for coredump and memdump generation
- Adds the ability to configure debug options using `PluginBuilder`
- Fixes memory layout and a wasted page of memory in the kernel, found
while debugging a memory dump

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zshipko <zshipko@users.noreply.github.com>
This commit is contained in:
zach
2023-10-24 17:31:26 -07:00
committed by GitHub
parent dba0b67cd3
commit 4bd0ed6d03
9 changed files with 292 additions and 106 deletions

View File

@@ -17,13 +17,13 @@
//! ## Input/Output
//!
//! Input and output are just allocated blocks of memory that are marked as either input or output using
//! the `extism_input_set` or `extism_output_set` functions. The global variables `INPUT_OFFSET` contains
//! the offset in memory to the input data and `INPUT_LENGTH` contains the size of the input data. `OUTPUT_OFFSET`
//! and `OUTPUT_LENGTH` are used for the output data.
//! the `extism_input_set` or `extism_output_set` functions. The MemoryRoot field `input_offset` contains
//! the offset in memory to the input data and `input_length` contains the size of the input data. `output_offset`
//! and `output_length` are used for the output data.
//!
//! ## Error handling
//!
//! The `ERROR` global is used to track the current error message. If it is set to `0` then there is no error.
//! The `error` field is used to track the current error message. If it is set to `0` then there is no error.
//! The length of the error message can be retreived using `extism_length`.
//!
//! ## Memory offsets
@@ -45,31 +45,6 @@ pub type Length = u64;
/// WebAssembly page size
const PAGE_SIZE: usize = 65536;
/// Determines the amount of bytes that can be wasted by re-using a block. If more than this number is wasted by re-using
/// a block then it will be split into two smaller blocks.
const BLOCK_SPLIT_SIZE: usize = 128;
/// Offset to the input data
static mut INPUT_OFFSET: Pointer = 0;
/// Length of the input data
static mut INPUT_LENGTH: Length = 0;
/// Offset to the output data
static mut OUTPUT_OFFSET: Pointer = 0;
/// Offset to the input data
static mut OUTPUT_LENGTH: Length = 0;
/// Current error message
static mut ERROR: AtomicU64 = AtomicU64::new(0);
/// Determines if the kernel has been initialized already
static mut INITIALIZED: AtomicBool = AtomicBool::new(false);
/// A pointer to the first page that will be managed by Extism, this is set during initialization
static mut START_PAGE: usize = 0;
/// Provides information about the usage status of a `MemoryBlock`
#[repr(u8)]
#[derive(PartialEq)]
@@ -98,10 +73,22 @@ pub enum MemoryStatus {
/// including their data.
#[repr(C)]
pub struct MemoryRoot {
/// Set to true after initialization
pub initialized: AtomicBool,
/// Position of the bump allocator, relative to `blocks` field
pub position: AtomicU64,
/// The total size of all data allocated using this allocator
pub length: AtomicU64,
/// Offset of error block
pub error: AtomicU64,
/// Input position in memory
pub input_offset: Pointer,
/// Input length
pub input_length: Length,
/// Output position in memory
pub output_offset: Pointer,
/// Output length
pub output_length: Length,
/// A pointer to the start of the first block
pub blocks: [MemoryBlock; 0],
}
@@ -131,32 +118,41 @@ pub fn num_pages(nbytes: u64) -> usize {
}
}
// Get the `MemoryRoot` at the correct offset in memory
// Get the `MemoryRoot`, this is always stored at offset 1 in memory
#[inline]
unsafe fn memory_root() -> &'static mut MemoryRoot {
&mut *((START_PAGE * PAGE_SIZE) as *mut MemoryRoot)
&mut *(1 as *mut MemoryRoot)
}
impl MemoryRoot {
/// Initialize or load the `MemoryRoot` from the correct position in memory
pub unsafe fn new() -> &'static mut MemoryRoot {
let root = memory_root();
// If this fails then `INITIALIZED` is already `true` and we can just return the
// already initialized `MemoryRoot`
if INITIALIZED
if root
.initialized
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return memory_root();
return root;
}
// Ensure that at least one page is allocated to store the `MemoryRoot` data
START_PAGE = core::arch::wasm32::memory_grow(0, 1);
if START_PAGE == usize::MAX {
panic!("Out of memory");
if core::arch::wasm32::memory_size(0) == 0 {
if core::arch::wasm32::memory_grow(0, 1) == usize::MAX {
core::arch::wasm32::unreachable()
}
}
root.input_offset = 0;
root.input_length = 0;
root.output_offset = 0;
root.output_length = 0;
root.error.store(0, Ordering::Release);
// Initialize the `MemoryRoot` length, position and data
let root = memory_root();
root.length.store(
PAGE_SIZE as u64 - core::mem::size_of::<MemoryRoot>() as u64,
Ordering::Release,
@@ -181,6 +177,11 @@ impl MemoryRoot {
self.length.load(Ordering::Acquire) as usize,
);
self.position.store(0, Ordering::Release);
self.error.store(0, Ordering::Release);
self.input_offset = 0;
self.input_length = 0;
self.output_offset = 0;
self.output_length = 0;
}
#[inline(always)]
@@ -224,7 +225,7 @@ impl MemoryRoot {
// Re-use freed blocks when they're large enough
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
// Split block if there is too much excess
if b.size - length as usize >= BLOCK_SPLIT_SIZE {
if b.size - length as usize >= 128 {
b.size -= length as usize;
b.used = 0;
@@ -348,14 +349,15 @@ pub unsafe fn extism_free(p: Pointer) {
if p == 0 {
return;
}
let block = MemoryRoot::new().find_block(p);
let root = MemoryRoot::new();
let block = root.find_block(p);
if let Some(block) = block {
block.free();
// If the input pointer is freed for some reason, make sure the input length to 0
// since the original data is gone
if p == INPUT_OFFSET {
INPUT_LENGTH = 0;
if p == root.input_offset {
root.input_length = 0;
}
}
}
@@ -396,21 +398,23 @@ pub unsafe fn extism_load_u64(p: Pointer) -> u64 {
/// Load a byte from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u8(p: Pointer) -> u8 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p >= INPUT_LENGTH {
if p >= root.input_length {
return 0;
}
*((INPUT_OFFSET + p) as *mut u8)
*((root.input_offset + p) as *mut u8)
}
/// Load a u64 from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u64(p: Pointer) -> u64 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p + core::mem::size_of::<u64>() as Pointer > INPUT_LENGTH {
if p + core::mem::size_of::<u64>() as Pointer > root.input_length {
return 0;
}
*((INPUT_OFFSET + p) as *mut u64)
*((root.input_offset + p) as *mut u64)
}
/// Write a byte in Extism-managed memory
@@ -436,82 +440,83 @@ pub unsafe fn extism_store_u64(p: Pointer, x: u64) {
/// Set the range of the input data in memory
#[no_mangle]
pub unsafe fn extism_input_set(p: Pointer, len: Length) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
let root = MemoryRoot::new();
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
INPUT_OFFSET = p;
INPUT_LENGTH = len;
root.input_offset = p;
root.input_length = len;
}
/// Set the range of the output data in memory
#[no_mangle]
pub unsafe fn extism_output_set(p: Pointer, len: Length) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
let root = MemoryRoot::new();
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
OUTPUT_OFFSET = p;
OUTPUT_LENGTH = len;
root.output_offset = p;
root.output_length = len;
}
/// Get the input length
#[no_mangle]
pub fn extism_input_length() -> Length {
unsafe { INPUT_LENGTH }
unsafe { MemoryRoot::new().input_length }
}
/// Get the input offset in Exitsm-managed memory
#[no_mangle]
pub fn extism_input_offset() -> Length {
unsafe { INPUT_OFFSET }
unsafe { MemoryRoot::new().input_offset }
}
/// Get the output length
#[no_mangle]
pub fn extism_output_length() -> Length {
unsafe { OUTPUT_LENGTH }
pub unsafe fn extism_output_length() -> Length {
unsafe { MemoryRoot::new().output_length }
}
/// Get the output offset in Extism-managed memory
#[no_mangle]
pub fn extism_output_offset() -> Length {
unsafe { OUTPUT_OFFSET }
pub unsafe fn extism_output_offset() -> Length {
MemoryRoot::new().output_offset
}
/// Reset the allocator
#[no_mangle]
pub unsafe fn extism_reset() {
ERROR.store(0, Ordering::SeqCst);
MemoryRoot::new().reset()
}
/// Set the error message offset
#[no_mangle]
pub unsafe fn extism_error_set(ptr: Pointer) {
let root = MemoryRoot::new();
// Allow ERROR to be set to 0
if ptr == 0 {
ERROR.store(ptr, Ordering::SeqCst);
root.error.store(ptr, Ordering::SeqCst);
return;
}
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::new().pointer_in_bounds(ptr) {
if !root.pointer_in_bounds(ptr) {
return;
}
ERROR.store(ptr, Ordering::SeqCst);
root.error.store(ptr, Ordering::SeqCst);
}
/// Get the error message offset, if it's `0` then no error has been set
#[no_mangle]
pub unsafe fn extism_error_get() -> Pointer {
ERROR.load(Ordering::SeqCst)
MemoryRoot::new().error.load(Ordering::SeqCst)
}
/// Get the position of the allocator, this can be used as an indication of how many bytes are currently in-use

View File

@@ -9,8 +9,8 @@ repository = "https://github.com/extism/extism"
description = "Extism runtime and Rust SDK"
[dependencies]
wasmtime = ">= 13.0.0, < 15.0.0"
wasmtime-wasi = ">= 13.0.0, < 15.0.0"
wasmtime = ">= 14.0.0, < 15.0.0"
wasmtime-wasi = ">= 14.0.0, < 15.0.0"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"

View File

@@ -15,6 +15,16 @@ To use the `extism` crate, you can add it to your Cargo file:
extism = "*"
```
## Environment variables
There are a few environment variables that can be used for debugging purposes:
- `EXTISM_ENABLE_WASI_OUTPUT=1`: show WASI stdout/stderr
- `EXTISM_MEMDUMP=extism.mem`: dump Extism linear memory to a file
- `EXTISM_COREDUMP=extism.core`: write [coredump](https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md) to a file when a WebAssembly function traps
- `EXTISM_DEBUG=1`: generate debug information
- `EXTISM_PROFILE=perf|jitdump|vtune`: enable Wasmtime profiling
## Getting Started
This guide should walk you through some of the concepts in Extism and the `extism` crate.
@@ -39,7 +49,7 @@ fn main() {
}
```
> **Note**: See [the Manifest docs](https://docs.rs/extism-manifest/0.5.0/extism_manifest/) as it has a rich schema and a lot of options.
> **Note**: See [the Manifest docs](https://docs.rs/extism-manifest/extism_manifest/) as it has a rich schema and a lot of options.
### Calling A Plug-in's Exports

View File

@@ -258,16 +258,20 @@ impl CurrentPlugin {
/// Get a pointer to the plugin memory
pub(crate) fn memory_ptr(&mut self) -> *mut u8 {
let (linker, mut store) = self.linker_and_store();
if let Some(mem) = linker.get(&mut store, "env", "memory") {
if let Some(mem) = mem.into_memory() {
return mem.data_ptr(&mut store);
}
if let Some(mem) = self.memory() {
let (_, mut store) = self.linker_and_store();
return mem.data_ptr(&mut store);
}
std::ptr::null_mut()
}
/// Get extism memory
pub(crate) fn memory(&mut self) -> Option<wasmtime::Memory> {
let (linker, mut store) = self.linker_and_store();
linker.get(&mut store, "env", "memory")?.into_memory()
}
/// Get a `MemoryHandle` from a `Val` reference - this can be used to convert a host function's
/// argument directly to `MemoryHandle`
pub fn memory_from_val(&mut self, offs: &Val) -> Option<MemoryHandle> {

Binary file not shown.

View File

@@ -27,6 +27,7 @@ pub use plugin_builder::PluginBuilder;
pub(crate) use internal::{Internal, Wasi};
pub(crate) use log::{debug, error, trace};
pub(crate) use plugin_builder::DebugOptions;
pub(crate) use timer::{Timer, TimerAction};
#[cfg(test)]

View File

@@ -70,6 +70,8 @@ pub struct Plugin {
/// Set to `true` when de-initializarion may have occured (i.e.a call to `_start`),
/// in this case we need to re-initialize the entire module.
pub(crate) needs_reset: bool,
pub(crate) debug_options: DebugOptions,
}
unsafe impl Send for Plugin {}
@@ -108,6 +110,8 @@ const EXPORT_MODULE_NAME: &str = "env";
fn profiling_strategy() -> ProfilingStrategy {
match std::env::var("EXTISM_PROFILE").as_deref() {
Ok("perf") => ProfilingStrategy::PerfMap,
Ok("jitdump") => ProfilingStrategy::JitDump,
Ok("vtune") => ProfilingStrategy::VTune,
Ok(x) => {
log::warn!("Invalid value for EXTISM_PROFILE: {x}");
ProfilingStrategy::None
@@ -116,12 +120,6 @@ fn profiling_strategy() -> ProfilingStrategy {
}
}
// Raise an error when the epoch deadline is encountered - this is used for timeout/cancellation
// to stop a plugin that is executing
fn deadline_callback(_: StoreContextMut<CurrentPlugin>) -> Result<UpdateDeadline, Error> {
Err(Error::msg("timeout"))
}
impl Plugin {
/// Create a new plugin from the given manifest, and host functions. The `with_wasi` parameter determines
/// whether or not the module should be executed with WASI enabled.
@@ -141,13 +139,36 @@ impl Plugin {
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
) -> Result<Plugin, Error> {
// Create a new engine, if the `EXTISM_DEBUG` environment variable is set
// then we enable debug info
Self::build_new(wasm, imports, with_wasi, Default::default())
}
pub(crate) fn build_new(
wasm: impl AsRef<[u8]>,
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
mut debug_options: DebugOptions,
) -> Result<Plugin, Error> {
// Configure debug options
debug_options.debug_info =
debug_options.debug_info || std::env::var("EXTISM_DEBUG").is_ok();
if let Ok(x) = std::env::var("EXTISM_COREDUMP") {
debug_options.coredump = Some(std::path::PathBuf::from(x));
};
if let Ok(x) = std::env::var("EXTISM_MEMDUMP") {
debug_options.memdump = Some(std::path::PathBuf::from(x));
};
let profiling_strategy = debug_options
.profiling_strategy
.map_or(ProfilingStrategy::None, |_| profiling_strategy());
debug_options.profiling_strategy = Some(profiling_strategy.clone());
// Setup wasmtime types
let engine = Engine::new(
Config::new()
.epoch_interruption(true)
.debug_info(std::env::var("EXTISM_DEBUG").is_ok())
.profiler(profiling_strategy()),
.debug_info(debug_options.debug_info)
.coredump_on_trap(debug_options.coredump.is_some())
.profiler(profiling_strategy),
)?;
let mut imports = imports.into_iter();
let (manifest, modules) = manifest::load(&engine, wasm.as_ref())?;
@@ -245,6 +266,7 @@ impl Plugin {
output: Output::default(),
_functions: imports.collect(),
needs_reset: false,
debug_options,
};
plugin.current_plugin_mut().store = &mut plugin.store;
@@ -576,11 +598,10 @@ impl Plugin {
.map(std::time::Duration::from_millis),
})
.unwrap();
self.store.epoch_deadline_callback(deadline_callback);
// Call the function
let mut results = vec![wasmtime::Val::null(); n_results];
let res = func.call(self.store_mut(), &[], results.as_mut_slice());
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
// Stop timer
self.timer_tx
@@ -592,13 +613,24 @@ impl Plugin {
self.get_output_after_call();
self.needs_reset = name == "_start";
let mut msg = None;
if self.output.error_offset != 0 && self.output.error_length != 0 {
let handle = MemoryHandle {
offset: self.output.error_offset,
length: self.output.error_length,
};
if let Ok(e) = self.current_plugin_mut().memory_str(handle) {
return Err((Error::msg(e.to_string()), -1));
let x = e.to_string();
error!("Call to {name} returned with error message: {}", x);
// If `res` is `Ok` and there is an error message set, then convert the response
// to an `Error`
if res.is_ok() {
res = Err(Error::msg(x));
} else {
msg = Some(x);
}
}
}
@@ -613,24 +645,69 @@ impl Plugin {
// Return result to caller
Ok(0)
}
Err(e) => match e.downcast::<wasmtime_wasi::I32Exit>() {
Ok(exit) => {
trace!("WASI return code: {}", exit.0);
if exit.0 != 0 {
return Err((Error::msg("WASI return code"), exit.0));
Err(e) => {
if let Some(coredump) = e.downcast_ref::<wasmtime::WasmCoreDump>() {
if let Some(file) = self.debug_options.coredump.clone() {
debug!("Saving coredump to {}", file.display());
if let Err(e) =
std::fs::write(file, coredump.serialize(self.store_mut(), "extism"))
{
error!("Unable to write coredump: {:?}", e);
}
}
return Ok(0);
}
Err(e) => {
let cause = e.root_cause().to_string();
if cause == "timeout" || cause == "oom" {
return Err((Error::msg(cause), -1));
if let Some(file) = &self.debug_options.memdump.clone() {
trace!("Memory dump enabled");
if let Some(memory) = self.current_plugin_mut().memory() {
debug!("Dumping memory to {}", file.display());
let data = memory.data(&mut self.store);
if let Err(e) = std::fs::write(file, &data) {
error!("Unable to write memory dump: {:?}", e);
}
} else {
error!("Unable to get extism memory for writing to disk");
}
}
let wasi_exit_code = e
.downcast_ref::<wasmtime_wasi::I32Exit>()
.map(|e| e.0)
.or_else(|| {
e.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0)
});
if let Some(exit_code) = wasi_exit_code {
trace!("WASI exit code: {}", exit_code);
if exit_code == 0 && msg.is_none() {
return Ok(0);
}
error!("Call: {e:?}");
return Err((e.context("Call failed"), -1));
return Err((
Error::msg(msg.unwrap_or_else(|| "WASI exit code".to_string())),
exit_code,
));
}
},
// Handle timeout interrupts
if let Some(wasmtime::Trap::Interrupt) = e.downcast_ref::<wasmtime::Trap>() {
trace!("Call to {name} timed out");
return Err((Error::msg("timeout"), -1));
}
// Handle out-of-memory error from `MemoryLimiter`
let cause = e.root_cause().to_string();
if cause == "oom" {
return Err((Error::msg(cause), -1));
}
error!("Call to {name} encountered an error: {e:?}");
return Err((
e.context(msg.unwrap_or_else(|| "Error in Extism plugin call".to_string())),
-1,
));
}
}
}
@@ -718,7 +795,7 @@ pub(crate) enum GuestRuntime {
///
/// # const WASM: &[u8] = include_bytes!("../../wasm/code.wasm");
/// // Convert from `Plugin` to `MyPlugin`
/// let mut plugin: MyPlugin = extism::Plugin::new(WASM, [], true).unwrap().into();
/// let mut plugin: MyPlugin = extism::Plugin::new(WASM, [], true).unwrap().try_into().unwrap();
/// // and call the `count_vowels` function
/// let count = plugin.count_vowels("this is a test").unwrap();
/// ```
@@ -730,9 +807,15 @@ macro_rules! typed_plugin {
unsafe impl Send for $name {}
unsafe impl Sync for $name {}
impl From<$crate::Plugin> for $name {
fn from(x: $crate::Plugin) -> Self {
$name(x)
impl TryFrom<$crate::Plugin> for $name {
type Error = $crate::Error;
fn try_from(mut x: $crate::Plugin) -> Result<Self, Self::Error> {
$(
if !x.function_exists(stringify!($f)) {
return Err($crate::Error::msg(format!("Invalid function: {}", stringify!($f))));
}
)*
Ok($name(x))
}
}

View File

@@ -5,11 +5,20 @@ enum Source {
Data(Vec<u8>),
}
#[derive(Default, Clone)]
pub(crate) struct DebugOptions {
pub(crate) profiling_strategy: Option<wasmtime::ProfilingStrategy>,
pub(crate) coredump: Option<std::path::PathBuf>,
pub(crate) memdump: Option<std::path::PathBuf>,
pub(crate) debug_info: bool,
}
/// PluginBuilder is used to configure and create `Plugin` instances
pub struct PluginBuilder {
source: Source,
wasi: bool,
functions: Vec<Function>,
debug_options: DebugOptions,
}
impl PluginBuilder {
@@ -19,6 +28,7 @@ impl PluginBuilder {
source: Source::Data(data.into()),
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
}
}
@@ -28,6 +38,7 @@ impl PluginBuilder {
source: Source::Manifest(manifest),
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
}
}
@@ -68,11 +79,34 @@ impl PluginBuilder {
self
}
pub fn with_profiling_strategy(mut self, p: wasmtime::ProfilingStrategy) -> Self {
self.debug_options.profiling_strategy = Some(p);
self
}
pub fn with_coredump(mut self, path: impl AsRef<std::path::Path>) -> Self {
self.debug_options.coredump = Some(path.as_ref().to_path_buf());
self
}
pub fn with_memdump(mut self, path: impl AsRef<std::path::Path>) -> Self {
self.debug_options.memdump = Some(path.as_ref().to_path_buf());
self
}
pub fn with_debug_info(mut self) -> Self {
self.debug_options.debug_info = true;
self
}
/// Generate a new plugin with the configured settings
pub fn build(self) -> Result<Plugin, Error> {
match self.source {
Source::Manifest(m) => Plugin::new_with_manifest(&m, self.functions, self.wasi),
Source::Data(d) => Plugin::new(d, self.functions, self.wasi),
Source::Manifest(m) => {
let data = serde_json::to_vec(&m)?;
Plugin::build_new(&data, self.functions, self.wasi, self.debug_options)
}
Source::Data(d) => Plugin::build_new(d, self.functions, self.wasi, self.debug_options),
}
}
}

View File

@@ -214,7 +214,9 @@ fn test_timeout() {
let end = std::time::Instant::now();
let time = end - start;
println!("Timed out plugin ran for {:?}", time);
assert!(output.unwrap_err().root_cause().to_string() == "timeout");
let s = output.unwrap_err().root_cause().to_string();
println!("{}", s);
assert!(s == "timeout");
// std::io::stdout().write_all(output).unwrap();
}
@@ -236,7 +238,7 @@ fn test_typed_plugin_macro() {
hello_world,
);
let mut plugin: CountVowelsPlugin = Plugin::new(WASM, [f], true).unwrap().into();
let mut plugin: CountVowelsPlugin = Plugin::new(WASM, [f], true).unwrap().try_into().unwrap();
let Json(output0): Json<Count> = plugin.count_vowels("abc123").unwrap();
let Json(output1): Json<Count> = plugin.0.call("count_vowels", "abc123").unwrap();
@@ -254,7 +256,7 @@ fn test_multiple_instantiations() {
hello_world,
);
let mut plugin: CountVowelsPlugin = Plugin::new(WASM, [f], true).unwrap().into();
let mut plugin: CountVowelsPlugin = Plugin::new(WASM, [f], true).unwrap().try_into().unwrap();
// This is 10,001 because the wasmtime store limit is 10,000 - we want to test
// that our reinstantiation process is working and that limit is never hit.
@@ -314,7 +316,10 @@ fn test_memory_max() {
let mut plugin = Plugin::new_with_manifest(&manifest, [], true).unwrap();
let output: Result<String, Error> = plugin.call("count_vowels", "a".repeat(65536 * 2));
assert!(output.is_err());
assert!(output.unwrap_err().root_cause().to_string() == "oom");
let err = output.unwrap_err().root_cause().to_string();
println!("{:?}", err);
assert_eq!(err, "oom");
// Should pass with memory.max set to a large enough number
let manifest =
@@ -356,3 +361,47 @@ fn test_extism_error() {
assert!(output.is_err());
assert_eq!(output.unwrap_err().root_cause().to_string(), "TEST");
}
#[test]
fn test_extism_memdump() {
let f = Function::new(
"hello_world",
[ValType::I64],
[ValType::I64],
None,
hello_world_set_error,
);
let mut plugin = PluginBuilder::new_with_module(WASM)
.with_wasi(true)
.with_functions([f])
.with_memdump("extism.mem")
.build()
.unwrap();
let output: Result<String, Error> = plugin.call("count_vowels", "a".repeat(1024));
assert!(output.is_err());
assert!(std::path::PathBuf::from("extism.mem").exists());
let _ = std::fs::remove_file("extism.mem");
}
#[test]
fn test_extism_coredump() {
let f = Function::new(
"hello_world",
[ValType::I64],
[ValType::I64],
None,
hello_world_set_error,
);
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_LOOP)])
.with_timeout(std::time::Duration::from_secs(1));
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.with_functions([f])
.with_coredump("extism.core")
.build()
.unwrap();
let output: Result<&[u8], Error> = plugin.call("infinite_loop", "abc123");
assert!(output.is_err());
assert!(std::path::PathBuf::from("extism.core").exists());
let _ = std::fs::remove_file("extism.core");
}