mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-27 23:21:05 +00:00
b382cc8f9d
* Improve sandbox internal api This improves the internal sandbox api for the executor implementations. The main point is to hide the tls in the internal api and not having it exposed to the outside. This is especially needed for wasmtime 0.29.0 * Fmt * Make it nicer
783 lines
23 KiB
Rust
783 lines
23 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
//! This crate provides an implementation of `WasmModule` that is baked by wasmi.
|
|
|
|
use codec::{Decode, Encode};
|
|
use log::{debug, error, trace};
|
|
use sc_executor_common::{
|
|
error::{Error, WasmError},
|
|
runtime_blob::{DataSegmentsSnapshot, RuntimeBlob},
|
|
sandbox,
|
|
util::MemoryTransfer,
|
|
wasm_runtime::{InvokeMethod, WasmInstance, WasmModule},
|
|
};
|
|
use sp_core::sandbox as sandbox_primitives;
|
|
use sp_runtime_interface::unpack_ptr_and_len;
|
|
use sp_wasm_interface::{
|
|
Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize,
|
|
};
|
|
use std::{cell::RefCell, rc::Rc, str, sync::Arc};
|
|
use wasmi::{
|
|
memory_units::Pages,
|
|
FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef,
|
|
RuntimeValue::{self, I32, I64},
|
|
TableRef,
|
|
};
|
|
|
|
struct FunctionExecutor {
|
|
sandbox_store: Rc<RefCell<sandbox::Store<wasmi::FuncRef>>>,
|
|
heap: RefCell<sc_allocator::FreeingBumpHeapAllocator>,
|
|
memory: MemoryRef,
|
|
table: Option<TableRef>,
|
|
host_functions: Arc<Vec<&'static dyn Function>>,
|
|
allow_missing_func_imports: bool,
|
|
missing_functions: Arc<Vec<String>>,
|
|
}
|
|
|
|
impl FunctionExecutor {
|
|
fn new(
|
|
m: MemoryRef,
|
|
heap_base: u32,
|
|
t: Option<TableRef>,
|
|
host_functions: Arc<Vec<&'static dyn Function>>,
|
|
allow_missing_func_imports: bool,
|
|
missing_functions: Arc<Vec<String>>,
|
|
) -> Result<Self, Error> {
|
|
Ok(FunctionExecutor {
|
|
sandbox_store: Rc::new(RefCell::new(sandbox::Store::new(
|
|
sandbox::SandboxBackend::Wasmi,
|
|
))),
|
|
heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)),
|
|
memory: m,
|
|
table: t,
|
|
host_functions,
|
|
allow_missing_func_imports,
|
|
missing_functions,
|
|
})
|
|
}
|
|
}
|
|
|
|
struct SandboxContext<'a> {
|
|
executor: &'a mut FunctionExecutor,
|
|
dispatch_thunk: wasmi::FuncRef,
|
|
}
|
|
|
|
impl<'a> sandbox::SandboxContext for SandboxContext<'a> {
|
|
fn invoke(
|
|
&mut self,
|
|
invoke_args_ptr: Pointer<u8>,
|
|
invoke_args_len: WordSize,
|
|
state: u32,
|
|
func_idx: sandbox::SupervisorFuncIndex,
|
|
) -> Result<i64, Error> {
|
|
let result = wasmi::FuncInstance::invoke(
|
|
&self.dispatch_thunk,
|
|
&[
|
|
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
|
|
RuntimeValue::I32(invoke_args_len as i32),
|
|
RuntimeValue::I32(state as i32),
|
|
RuntimeValue::I32(usize::from(func_idx) as i32),
|
|
],
|
|
self.executor,
|
|
);
|
|
|
|
match result {
|
|
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
|
|
Ok(_) => return Err("Supervisor function returned unexpected result!".into()),
|
|
Err(err) => Err(Error::Trap(err)),
|
|
}
|
|
}
|
|
|
|
fn supervisor_context(&mut self) -> &mut dyn FunctionContext {
|
|
self.executor
|
|
}
|
|
}
|
|
|
|
impl FunctionContext for FunctionExecutor {
|
|
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
|
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
|
self.memory.set(address.into(), data).map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
|
let heap = &mut self.heap.borrow_mut();
|
|
self.memory
|
|
.with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string()))
|
|
}
|
|
|
|
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
|
let heap = &mut self.heap.borrow_mut();
|
|
self.memory
|
|
.with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string()))
|
|
}
|
|
|
|
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Sandbox for FunctionExecutor {
|
|
fn memory_get(
|
|
&mut self,
|
|
memory_id: MemoryId,
|
|
offset: WordSize,
|
|
buf_ptr: Pointer<u8>,
|
|
buf_len: WordSize,
|
|
) -> WResult<u32> {
|
|
let sandboxed_memory =
|
|
self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
|
|
|
let len = buf_len as usize;
|
|
|
|
let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) {
|
|
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
|
Ok(buffer) => buffer,
|
|
};
|
|
|
|
if let Err(_) = self.memory.set(buf_ptr.into(), &buffer) {
|
|
return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS)
|
|
}
|
|
|
|
Ok(sandbox_primitives::ERR_OK)
|
|
}
|
|
|
|
fn memory_set(
|
|
&mut self,
|
|
memory_id: MemoryId,
|
|
offset: WordSize,
|
|
val_ptr: Pointer<u8>,
|
|
val_len: WordSize,
|
|
) -> WResult<u32> {
|
|
let sandboxed_memory =
|
|
self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
|
|
|
let len = val_len as usize;
|
|
|
|
let buffer = match self.memory.get(val_ptr.into(), len) {
|
|
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
|
Ok(buffer) => buffer,
|
|
};
|
|
|
|
if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) {
|
|
return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS)
|
|
}
|
|
|
|
Ok(sandbox_primitives::ERR_OK)
|
|
}
|
|
|
|
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
|
|
self.sandbox_store
|
|
.borrow_mut()
|
|
.memory_teardown(memory_id)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult<MemoryId> {
|
|
self.sandbox_store
|
|
.borrow_mut()
|
|
.new_memory(initial, maximum)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn invoke(
|
|
&mut self,
|
|
instance_id: u32,
|
|
export_name: &str,
|
|
mut args: &[u8],
|
|
return_val: Pointer<u8>,
|
|
return_val_len: WordSize,
|
|
state: u32,
|
|
) -> WResult<u32> {
|
|
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
|
|
|
|
// Deserialize arguments and convert them into wasmi types.
|
|
let args = Vec::<sp_wasm_interface::Value>::decode(&mut args)
|
|
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
|
.into_iter()
|
|
.map(Into::into)
|
|
.collect::<Vec<_>>();
|
|
|
|
let instance =
|
|
self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?;
|
|
|
|
let dispatch_thunk = self
|
|
.sandbox_store
|
|
.borrow()
|
|
.dispatch_thunk(instance_id)
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
match instance.invoke(
|
|
export_name,
|
|
&args,
|
|
state,
|
|
&mut SandboxContext { dispatch_thunk, executor: self },
|
|
) {
|
|
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
|
Ok(Some(val)) => {
|
|
// Serialize return value and write it back into the memory.
|
|
sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| {
|
|
if val.len() > return_val_len as usize {
|
|
Err("Return value buffer is too small")?;
|
|
}
|
|
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
|
|
Ok(sandbox_primitives::ERR_OK)
|
|
})
|
|
},
|
|
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
|
}
|
|
}
|
|
|
|
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
|
self.sandbox_store
|
|
.borrow_mut()
|
|
.instance_teardown(instance_id)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn instance_new(
|
|
&mut self,
|
|
dispatch_thunk_id: u32,
|
|
wasm: &[u8],
|
|
raw_env_def: &[u8],
|
|
state: u32,
|
|
) -> WResult<u32> {
|
|
// Extract a dispatch thunk from instance's table by the specified index.
|
|
let dispatch_thunk = {
|
|
let table = self
|
|
.table
|
|
.as_ref()
|
|
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
|
|
table
|
|
.get(dispatch_thunk_id)
|
|
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
|
|
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
|
|
};
|
|
|
|
let guest_env =
|
|
match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) {
|
|
Ok(guest_env) => guest_env,
|
|
Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32),
|
|
};
|
|
|
|
let store = self.sandbox_store.clone();
|
|
let result = store.borrow_mut().instantiate(
|
|
wasm,
|
|
guest_env,
|
|
state,
|
|
&mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() },
|
|
);
|
|
|
|
let instance_idx_or_err_code =
|
|
match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) {
|
|
Ok(instance_idx) => instance_idx,
|
|
Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION,
|
|
Err(_) => sandbox_primitives::ERR_MODULE,
|
|
};
|
|
|
|
Ok(instance_idx_or_err_code)
|
|
}
|
|
|
|
fn get_global_val(
|
|
&self,
|
|
instance_idx: u32,
|
|
name: &str,
|
|
) -> WResult<Option<sp_wasm_interface::Value>> {
|
|
self.sandbox_store
|
|
.borrow()
|
|
.instance(instance_idx)
|
|
.map(|i| i.get_global_val(name))
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
}
|
|
|
|
/// Will be used on initialization of a module to resolve function and memory imports.
|
|
struct Resolver<'a> {
|
|
/// All the hot functions that we export for the WASM blob.
|
|
host_functions: &'a [&'static dyn Function],
|
|
/// Should we allow missing function imports?
|
|
///
|
|
/// If `true`, we return a stub that will return an error when being called.
|
|
allow_missing_func_imports: bool,
|
|
/// All the names of functions for that we did not provide a host function.
|
|
missing_functions: RefCell<Vec<String>>,
|
|
/// Will be used as initial and maximum size of the imported memory.
|
|
heap_pages: usize,
|
|
/// By default, runtimes should import memory and this is `Some(_)` after
|
|
/// resolving. However, to be backwards compatible, we also support memory
|
|
/// exported by the WASM blob (this will be `None` after resolving).
|
|
import_memory: RefCell<Option<MemoryRef>>,
|
|
}
|
|
|
|
impl<'a> Resolver<'a> {
|
|
fn new(
|
|
host_functions: &'a [&'static dyn Function],
|
|
allow_missing_func_imports: bool,
|
|
heap_pages: usize,
|
|
) -> Resolver<'a> {
|
|
Resolver {
|
|
host_functions,
|
|
allow_missing_func_imports,
|
|
missing_functions: RefCell::new(Vec::new()),
|
|
heap_pages,
|
|
import_memory: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
|
|
fn resolve_func(
|
|
&self,
|
|
name: &str,
|
|
signature: &wasmi::Signature,
|
|
) -> std::result::Result<wasmi::FuncRef, wasmi::Error> {
|
|
let signature = sp_wasm_interface::Signature::from(signature);
|
|
for (function_index, function) in self.host_functions.iter().enumerate() {
|
|
if name == function.name() {
|
|
if signature == function.signature() {
|
|
return Ok(wasmi::FuncInstance::alloc_host(signature.into(), function_index))
|
|
} else {
|
|
return Err(wasmi::Error::Instantiation(format!(
|
|
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
|
|
function.name(),
|
|
signature,
|
|
function.signature(),
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.allow_missing_func_imports {
|
|
trace!(target: "wasm-executor", "Could not find function `{}`, a stub will be provided instead.", name);
|
|
let id = self.missing_functions.borrow().len() + self.host_functions.len();
|
|
self.missing_functions.borrow_mut().push(name.to_string());
|
|
|
|
Ok(wasmi::FuncInstance::alloc_host(signature.into(), id))
|
|
} else {
|
|
Err(wasmi::Error::Instantiation(format!("Export {} not found", name)))
|
|
}
|
|
}
|
|
|
|
fn resolve_memory(
|
|
&self,
|
|
field_name: &str,
|
|
memory_type: &wasmi::MemoryDescriptor,
|
|
) -> Result<MemoryRef, wasmi::Error> {
|
|
if field_name == "memory" {
|
|
match &mut *self.import_memory.borrow_mut() {
|
|
Some(_) =>
|
|
Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())),
|
|
memory_ref @ None => {
|
|
if memory_type
|
|
.maximum()
|
|
.map(|m| m.saturating_sub(memory_type.initial()))
|
|
.map(|m| self.heap_pages > m as usize)
|
|
.unwrap_or(false)
|
|
{
|
|
Err(wasmi::Error::Instantiation(format!(
|
|
"Heap pages ({}) is greater than imported memory maximum ({}).",
|
|
self.heap_pages,
|
|
memory_type
|
|
.maximum()
|
|
.map(|m| m.saturating_sub(memory_type.initial()))
|
|
.expect("Maximum is set, checked above; qed"),
|
|
)))
|
|
} else {
|
|
let memory = MemoryInstance::alloc(
|
|
Pages(memory_type.initial() as usize + self.heap_pages),
|
|
Some(Pages(memory_type.initial() as usize + self.heap_pages)),
|
|
)?;
|
|
*memory_ref = Some(memory.clone());
|
|
Ok(memory)
|
|
}
|
|
},
|
|
}
|
|
} else {
|
|
Err(wasmi::Error::Instantiation(format!(
|
|
"Unknown memory reference with name: {}",
|
|
field_name
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl wasmi::Externals for FunctionExecutor {
|
|
fn invoke_index(
|
|
&mut self,
|
|
index: usize,
|
|
args: wasmi::RuntimeArgs,
|
|
) -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> {
|
|
let mut args = args.as_ref().iter().copied().map(Into::into);
|
|
|
|
if let Some(function) = self.host_functions.clone().get(index) {
|
|
function
|
|
.execute(self, &mut args)
|
|
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
|
|
.map_err(wasmi::Trap::from)
|
|
.map(|v| v.map(Into::into))
|
|
} else if self.allow_missing_func_imports &&
|
|
index >= self.host_functions.len() &&
|
|
index < self.host_functions.len() + self.missing_functions.len()
|
|
{
|
|
Err(Error::from(format!(
|
|
"Function `{}` is only a stub. Calling a stub is not allowed.",
|
|
self.missing_functions[index - self.host_functions.len()],
|
|
))
|
|
.into())
|
|
} else {
|
|
Err(Error::from(format!("Could not find host function with index: {}", index)).into())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef, Error> {
|
|
Ok(module
|
|
.export_by_name("memory")
|
|
.ok_or_else(|| Error::InvalidMemoryReference)?
|
|
.as_memory()
|
|
.ok_or_else(|| Error::InvalidMemoryReference)?
|
|
.clone())
|
|
}
|
|
|
|
/// Find the global named `__heap_base` in the given wasm module instance and
|
|
/// tries to get its value.
|
|
fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
|
|
let heap_base_val = module
|
|
.export_by_name("__heap_base")
|
|
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
|
.as_global()
|
|
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
|
.get();
|
|
|
|
match heap_base_val {
|
|
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
|
|
_ => Err(Error::HeapBaseNotFoundOrInvalid),
|
|
}
|
|
}
|
|
|
|
/// Call a given method in the given wasm-module runtime.
|
|
fn call_in_wasm_module(
|
|
module_instance: &ModuleRef,
|
|
memory: &MemoryRef,
|
|
method: InvokeMethod,
|
|
data: &[u8],
|
|
host_functions: Arc<Vec<&'static dyn Function>>,
|
|
allow_missing_func_imports: bool,
|
|
missing_functions: Arc<Vec<String>>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
// Initialize FunctionExecutor.
|
|
let table: Option<TableRef> = module_instance
|
|
.export_by_name("__indirect_function_table")
|
|
.and_then(|e| e.as_table().cloned());
|
|
let heap_base = get_heap_base(module_instance)?;
|
|
|
|
let mut function_executor = FunctionExecutor::new(
|
|
memory.clone(),
|
|
heap_base,
|
|
table.clone(),
|
|
host_functions,
|
|
allow_missing_func_imports,
|
|
missing_functions,
|
|
)?;
|
|
|
|
// Write the call data
|
|
let offset = function_executor.allocate_memory(data.len() as u32)?;
|
|
function_executor.write_memory(offset, data)?;
|
|
|
|
let result = match method {
|
|
InvokeMethod::Export(method) => module_instance.invoke_export(
|
|
method,
|
|
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
|
|
&mut function_executor,
|
|
),
|
|
InvokeMethod::Table(func_ref) => {
|
|
let func = table
|
|
.ok_or(Error::NoTable)?
|
|
.get(func_ref)?
|
|
.ok_or(Error::NoTableEntryWithIndex(func_ref))?;
|
|
FuncInstance::invoke(
|
|
&func,
|
|
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
|
|
&mut function_executor,
|
|
)
|
|
.map_err(Into::into)
|
|
},
|
|
InvokeMethod::TableWithWrapper { dispatcher_ref, func } => {
|
|
let dispatcher = table
|
|
.ok_or(Error::NoTable)?
|
|
.get(dispatcher_ref)?
|
|
.ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?;
|
|
|
|
FuncInstance::invoke(
|
|
&dispatcher,
|
|
&[I32(func as _), I32(u32::from(offset) as i32), I32(data.len() as i32)],
|
|
&mut function_executor,
|
|
)
|
|
.map_err(Into::into)
|
|
},
|
|
};
|
|
|
|
match result {
|
|
Ok(Some(I64(r))) => {
|
|
let (ptr, length) = unpack_ptr_and_len(r as u64);
|
|
memory.get(ptr.into(), length as usize).map_err(|_| Error::Runtime)
|
|
},
|
|
Err(e) => {
|
|
trace!(
|
|
target: "wasm-executor",
|
|
"Failed to execute code with {} pages",
|
|
memory.current_size().0,
|
|
);
|
|
Err(e.into())
|
|
},
|
|
_ => Err(Error::InvalidReturn),
|
|
}
|
|
}
|
|
|
|
/// Prepare module instance
|
|
fn instantiate_module(
|
|
heap_pages: usize,
|
|
module: &Module,
|
|
host_functions: &[&'static dyn Function],
|
|
allow_missing_func_imports: bool,
|
|
) -> Result<(ModuleRef, Vec<String>, MemoryRef), Error> {
|
|
let resolver = Resolver::new(host_functions, allow_missing_func_imports, heap_pages);
|
|
// start module instantiation. Don't run 'start' function yet.
|
|
let intermediate_instance =
|
|
ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?;
|
|
|
|
// Verify that the module has the heap base global variable.
|
|
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
|
|
|
|
// Get the memory reference. Runtimes should import memory, but to be backwards
|
|
// compatible we also support exported memory.
|
|
let memory = match resolver.import_memory.into_inner() {
|
|
Some(memory) => memory,
|
|
None => {
|
|
debug!(
|
|
target: "wasm-executor",
|
|
"WASM blob does not imports memory, falling back to exported memory",
|
|
);
|
|
|
|
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
|
|
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
|
|
|
memory
|
|
},
|
|
};
|
|
|
|
if intermediate_instance.has_start() {
|
|
// Runtime is not allowed to have the `start` function.
|
|
Err(Error::RuntimeHasStartFn)
|
|
} else {
|
|
Ok((
|
|
intermediate_instance.assert_no_start(),
|
|
resolver.missing_functions.into_inner(),
|
|
memory,
|
|
))
|
|
}
|
|
}
|
|
|
|
/// A state snapshot of an instance taken just after instantiation.
|
|
///
|
|
/// It is used for restoring the state of the module after execution.
|
|
#[derive(Clone)]
|
|
struct GlobalValsSnapshot {
|
|
/// The list of all global mutable variables of the module in their sequential order.
|
|
global_mut_values: Vec<RuntimeValue>,
|
|
}
|
|
|
|
impl GlobalValsSnapshot {
|
|
// Returns `None` if instance is not valid.
|
|
fn take(module_instance: &ModuleRef) -> Self {
|
|
// Collect all values of mutable globals.
|
|
let global_mut_values = module_instance
|
|
.globals()
|
|
.iter()
|
|
.filter(|g| g.is_mutable())
|
|
.map(|g| g.get())
|
|
.collect();
|
|
Self { global_mut_values }
|
|
}
|
|
|
|
/// Reset the runtime instance to the initial version by restoring
|
|
/// the preserved memory and globals.
|
|
///
|
|
/// Returns `Err` if applying the snapshot is failed.
|
|
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
|
|
for (global_ref, global_val) in instance
|
|
.globals()
|
|
.iter()
|
|
.filter(|g| g.is_mutable())
|
|
.zip(self.global_mut_values.iter())
|
|
{
|
|
// the instance should be the same as used for preserving and
|
|
// we iterate the same way it as we do it for preserving values that means that the
|
|
// types should be the same and all the values are mutable. So no error is expected/
|
|
global_ref.set(*global_val).map_err(|_| WasmError::ApplySnapshotFailed)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A runtime along with initial copy of data segments.
|
|
pub struct WasmiRuntime {
|
|
/// A wasm module.
|
|
module: Module,
|
|
/// The host functions registered for this instance.
|
|
host_functions: Arc<Vec<&'static dyn Function>>,
|
|
/// Enable stub generation for functions that are not available in `host_functions`.
|
|
/// These stubs will error when the wasm blob tries to call them.
|
|
allow_missing_func_imports: bool,
|
|
/// Numer of heap pages this runtime uses.
|
|
heap_pages: u64,
|
|
|
|
global_vals_snapshot: GlobalValsSnapshot,
|
|
data_segments_snapshot: DataSegmentsSnapshot,
|
|
}
|
|
|
|
impl WasmModule for WasmiRuntime {
|
|
fn new_instance(&self) -> Result<Box<dyn WasmInstance>, Error> {
|
|
// Instantiate this module.
|
|
let (instance, missing_functions, memory) = instantiate_module(
|
|
self.heap_pages as usize,
|
|
&self.module,
|
|
&self.host_functions,
|
|
self.allow_missing_func_imports,
|
|
)
|
|
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
|
|
|
Ok(Box::new(WasmiInstance {
|
|
instance,
|
|
memory,
|
|
global_vals_snapshot: self.global_vals_snapshot.clone(),
|
|
data_segments_snapshot: self.data_segments_snapshot.clone(),
|
|
host_functions: self.host_functions.clone(),
|
|
allow_missing_func_imports: self.allow_missing_func_imports,
|
|
missing_functions: Arc::new(missing_functions),
|
|
}))
|
|
}
|
|
}
|
|
|
|
/// Create a new `WasmiRuntime` given the code. This function loads the module and
|
|
/// stores it in the instance.
|
|
pub fn create_runtime(
|
|
blob: RuntimeBlob,
|
|
heap_pages: u64,
|
|
host_functions: Vec<&'static dyn Function>,
|
|
allow_missing_func_imports: bool,
|
|
) -> Result<WasmiRuntime, WasmError> {
|
|
let data_segments_snapshot =
|
|
DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?;
|
|
|
|
let module =
|
|
Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?;
|
|
|
|
let global_vals_snapshot = {
|
|
let (instance, _, _) = instantiate_module(
|
|
heap_pages as usize,
|
|
&module,
|
|
&host_functions,
|
|
allow_missing_func_imports,
|
|
)
|
|
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
|
GlobalValsSnapshot::take(&instance)
|
|
};
|
|
|
|
Ok(WasmiRuntime {
|
|
module,
|
|
data_segments_snapshot,
|
|
global_vals_snapshot,
|
|
host_functions: Arc::new(host_functions),
|
|
allow_missing_func_imports,
|
|
heap_pages,
|
|
})
|
|
}
|
|
|
|
/// Wasmi instance wrapper along with the state snapshot.
|
|
pub struct WasmiInstance {
|
|
/// A wasm module instance.
|
|
instance: ModuleRef,
|
|
/// The memory instance of used by the wasm module.
|
|
memory: MemoryRef,
|
|
/// The snapshot of global variable values just after instantiation.
|
|
global_vals_snapshot: GlobalValsSnapshot,
|
|
/// The snapshot of data segments.
|
|
data_segments_snapshot: DataSegmentsSnapshot,
|
|
/// The host functions registered for this instance.
|
|
host_functions: Arc<Vec<&'static dyn Function>>,
|
|
/// Enable stub generation for functions that are not available in `host_functions`.
|
|
/// These stubs will error when the wasm blob trie to call them.
|
|
allow_missing_func_imports: bool,
|
|
/// List of missing functions detected during function resolution
|
|
missing_functions: Arc<Vec<String>>,
|
|
}
|
|
|
|
// This is safe because `WasmiInstance` does not leak any references to `self.memory` and
|
|
// `self.instance`
|
|
unsafe impl Send for WasmiInstance {}
|
|
|
|
impl WasmInstance for WasmiInstance {
|
|
fn call(&self, method: InvokeMethod, data: &[u8]) -> Result<Vec<u8>, Error> {
|
|
// We reuse a single wasm instance for multiple calls and a previous call (if any)
|
|
// altered the state. Therefore, we need to restore the instance to original state.
|
|
|
|
// First, zero initialize the linear memory.
|
|
self.memory.erase().map_err(|e| {
|
|
// Snapshot restoration failed. This is pretty unexpected since this can happen
|
|
// if some invariant is broken or if the system is under extreme memory pressure
|
|
// (so erasing fails).
|
|
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
|
|
WasmError::ErasingFailed(e.to_string())
|
|
})?;
|
|
|
|
// Second, reapply data segments into the linear memory.
|
|
self.data_segments_snapshot
|
|
.apply(|offset, contents| self.memory.set(offset, contents))?;
|
|
|
|
// Third, restore the global variables to their initial values.
|
|
self.global_vals_snapshot.apply(&self.instance)?;
|
|
|
|
call_in_wasm_module(
|
|
&self.instance,
|
|
&self.memory,
|
|
method,
|
|
data,
|
|
self.host_functions.clone(),
|
|
self.allow_missing_func_imports,
|
|
self.missing_functions.clone(),
|
|
)
|
|
}
|
|
|
|
fn get_global_const(&self, name: &str) -> Result<Option<sp_wasm_interface::Value>, Error> {
|
|
match self.instance.export_by_name(name) {
|
|
Some(global) => Ok(Some(
|
|
global
|
|
.as_global()
|
|
.ok_or_else(|| format!("`{}` is not a global", name))?
|
|
.get()
|
|
.into(),
|
|
)),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
}
|