From 7d1b418257988c11a38a052b1994d661deddfb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 22 Jan 2021 00:38:55 +0100 Subject: [PATCH] Adds host function for handle panicking This pr adds an extra host function that should be called by the wasm instance when its panicking. Currently we only log such a panic with an error severity. In the future we would call this host function and have the advantage that the panic message would be returned as error, instead of just some generic "panicked" message. This pr only adds the host function and the client side implementation. Some future pr would switch over the runtime to use this functionality. --- client/executor/wasmi/src/lib.rs | 16 +++++++++++++++- client/executor/wasmtime/src/host.rs | 11 +++++++++++ .../executor/wasmtime/src/instance_wrapper.rs | 19 +++++++++---------- client/executor/wasmtime/src/runtime.rs | 13 +++++++++---- primitives/io/src/lib.rs | 11 +++++++++++ primitives/wasm-interface/src/lib.rs | 6 +++++- 6 files changed, 60 insertions(+), 16 deletions(-) diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index e6a6ef3a61039..ed8b63f53bd31 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -46,6 +46,7 @@ struct FunctionExecutor<'a> { host_functions: &'a [&'static dyn Function], allow_missing_func_imports: bool, missing_functions: &'a [String], + instance_panicked: Option, } impl<'a> FunctionExecutor<'a> { @@ -65,8 +66,13 @@ impl<'a> FunctionExecutor<'a> { host_functions, allow_missing_func_imports, missing_functions, + instance_panicked: None, }) } + + fn panicked(&mut self) -> Option { + self.instance_panicked.take() + } } impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> { @@ -124,6 +130,10 @@ impl<'a> FunctionContext for FunctionExecutor<'a> { fn sandbox(&mut self) -> &mut dyn Sandbox { self } + + fn instance_panicked(&mut self, message: &str) { + self.instance_panicked = Some(Error::RuntimePanicked(message.into())); + } } impl<'a> Sandbox for FunctionExecutor<'a> { @@ -503,7 +513,11 @@ fn call_in_wasm_module( "Failed to execute code with {} pages", memory.current_size().0, ); - Err(e.into()) + + match function_executor.panicked() { + Some(e) => Err(e), + None => Err(e.into()), + } }, _ => Err(Error::InvalidReturn), } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index c1eb77ff81f34..02041ac5e35b2 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -54,6 +54,7 @@ pub struct HostState { sandbox_store: RefCell>, allocator: RefCell, instance: Rc, + instance_panicked: RefCell>, } impl HostState { @@ -63,6 +64,7 @@ impl HostState { sandbox_store: RefCell::new(sandbox::Store::new()), allocator: RefCell::new(allocator), instance, + instance_panicked: RefCell::new(None), } } @@ -70,6 +72,11 @@ impl HostState { pub fn materialize<'a>(&'a self) -> HostContext<'a> { HostContext(self) } + + /// Returns an error if the wasm instance informed about us about itself panicking. + fn panicked(&mut self) -> Option { + self.instance_panicked.borrow_mut().take() + } } /// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime @@ -156,6 +163,10 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn sandbox(&mut self) -> &mut dyn Sandbox { self } + + fn instance_panicked(&mut self, message: &str) { + *self.instance_panicked.borrow_mut() = Some(Error::RuntimePanicked(message.into())); + } } impl<'a> Sandbox for HostContext<'a> { diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 2103ab9b7b98c..f16cc772a206d 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -98,7 +98,7 @@ impl EntryPoint { let data_ptr = u32::from(data_ptr) as i32; let data_len = u32::from(data_len) as i32; - (match self.call_type { + match self.call_type { EntryPointType::Direct => { self.func.call(&[ wasmtime::Val::I32(data_ptr), @@ -112,15 +112,14 @@ impl EntryPoint { wasmtime::Val::I32(data_len), ]) }, - }) - .map(|results| - // the signature is checked to have i64 return type - results[0].unwrap_i64() as u64 - ) - .map_err(|err| Error::from(format!( - "Wasm execution trapped: {}", - err - ))) + }.map(|results| + // the signature is checked to have i64 return type + results[0].unwrap_i64() as u64 + ) + .map_err(|err| Error::from(format!( + "Wasm execution trapped: {}", + err, + ))) } pub fn direct(func: wasmtime::Func) -> std::result::Result { diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index a17a034918db7..1f85368123a77 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -153,10 +153,15 @@ fn perform_call( ) -> Result> { let (data_ptr, data_len) = inject_input_data(&instance_wrapper, &mut allocator, data)?; - let host_state = HostState::new(allocator, instance_wrapper.clone()); - let ret = state_holder::with_initialized_state(&host_state, || -> Result<_> { - Ok(unpack_ptr_and_len(entrypoint.call(data_ptr, data_len)?)) - }); + let mut host_state = HostState::new(allocator, instance_wrapper.clone()); + let ret = state_holder::with_initialized_state(&host_state, || + entrypoint.call(data_ptr, data_len).map(unpack_ptr_and_len) + ); + + if let Some(err) = host_state.panicked() { + return Err(err); + } + let (output_ptr, output_len) = ret?; let output = extract_output_data(&instance_wrapper, output_ptr, output_len)?; diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 397dd3c21712a..62ba783583bf8 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -1007,6 +1007,17 @@ trait Allocator { } } +/// Wasm only panic handler. +#[runtime_interface(wasm_only)] +trait PanicHandler { + /// Should be called when the wasm instance is panicking. + /// + /// The given `message` should correspond to the reason the instance panicked. + fn panicking(&mut self, message: &str) { + self.instance_panicked(message); + } +} + /// Interface that provides functions for logging from within the runtime. #[runtime_interface] pub trait Logging { diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index fd200268473b0..cfbf32788d2ad 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -272,7 +272,9 @@ impl PartialEq for dyn Function { } } -/// Context used by `Function` to interact with the allocator and the memory of the wasm instance. +/// Context used by [`Function`] to interact with the executing context. +/// +/// This includes access to the memory and the sandbox. pub trait FunctionContext { /// Read memory from `address` into a vector. fn read_memory(&self, address: Pointer, size: WordSize) -> Result> { @@ -290,6 +292,8 @@ pub trait FunctionContext { fn deallocate_memory(&mut self, ptr: Pointer) -> Result<()>; /// Provides access to the sandbox. fn sandbox(&mut self) -> &mut dyn Sandbox; + /// The wasm instance panicked with the given `message`. + fn instance_panicked(&mut self, message: &str); } /// Sandbox memory identifier.