Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make runtime and trap errors well defined #1401

Merged
merged 9 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## **[Unreleased]**

- [#1401](https://github.com/wasmerio/wasmer/pull/1401) Make breaking change to `RuntimeError`: `RuntimeError` is now more explicit about its possible error values allowing for better insight into why a call into Wasm failed.
- [#1382](https://github.com/wasmerio/wasmer/pull/1382) Refactored test infranstructure (part 2)
- [#1380](https://github.com/wasmerio/wasmer/pull/1380) Refactored test infranstructure (part 1)
- [#1357](https://github.com/wasmerio/wasmer/pull/1357) Refactored bin commands into separate files
Expand Down
Binary file modified examples/callback-guest/callback-guest.wasm
Binary file not shown.
13 changes: 6 additions & 7 deletions lib/clif-backend/src/signal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use crate::{
trampoline::Trampolines,
};
use libc::c_void;
use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc};
use std::{cell::Cell, ptr::NonNull, sync::Arc};
use wasmer_runtime_core::{
backend::RunnableModule,
error::RuntimeError,
module::ModuleInfo,
typed_func::{Trampoline, Wasm},
types::{LocalFuncIndex, SigIndex},
Expand All @@ -26,11 +27,9 @@ pub use self::unix::*;
pub use self::windows::*;

thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any + Send>>> = Cell::new(None);
pub static TRAP_EARLY_DATA: Cell<Option<RuntimeError>> = Cell::new(None);
}

pub struct CallProtError(pub Box<dyn Any + Send>);

pub struct Caller {
handler_data: HandlerData,
trampolines: Arc<Trampolines>,
Expand Down Expand Up @@ -63,7 +62,7 @@ impl RunnableModule for Caller {
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<RuntimeError>,
invoke_env: Option<NonNull<c_void>>,
) -> bool {
let handler_data = &*invoke_env.unwrap().cast().as_ptr();
Expand All @@ -80,7 +79,7 @@ impl RunnableModule for Caller {

match res {
Err(err) => {
*error_out = Some(err.0);
*error_out = Some(err.into());
false
}
Ok(()) => true,
Expand All @@ -101,7 +100,7 @@ impl RunnableModule for Caller {
})
}

unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> ! {
unsafe fn do_early_trap(&self, data: RuntimeError) -> ! {
TRAP_EARLY_DATA.with(|cell| cell.set(Some(data)));
trigger_trap()
}
Expand Down
31 changes: 16 additions & 15 deletions lib/clif-backend/src/signal/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! unless you have memory unsafety elsewhere in your code.
//!
use crate::relocation::{TrapCode, TrapData};
use crate::signal::{CallProtError, HandlerData};
use crate::signal::HandlerData;
use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
Expand All @@ -19,6 +19,7 @@ use std::cell::{Cell, UnsafeCell};
use std::ptr;
use std::sync::Once;
use wasmer_runtime_core::backend::ExceptionCode;
use wasmer_runtime_core::error::InvokeError;

extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
Expand Down Expand Up @@ -65,7 +66,7 @@ pub unsafe fn trigger_trap() -> ! {
pub fn call_protected<T>(
handler_data: &HandlerData,
f: impl FnOnce() -> T,
) -> Result<T, CallProtError> {
) -> Result<T, InvokeError> {
unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
let prev_jmp_buf = *jmp_buf;
Expand All @@ -79,16 +80,12 @@ pub fn call_protected<T>(
*jmp_buf = prev_jmp_buf;

if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError(data))
Err(InvokeError::EarlyTrap(Box::new(data)))
} else {
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());

if let Some(TrapData {
trapcode,
srcloc: _,
}) = handler_data.lookup(inst_ptr)
{
Err(CallProtError(Box::new(match Signal::from_c_int(signum) {
if let Some(TrapData { trapcode, srcloc }) = handler_data.lookup(inst_ptr) {
let code = match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode {
TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds,
TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds,
Expand All @@ -101,9 +98,10 @@ pub fn call_protected<T>(
TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic,
TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable,
_ => {
return Err(CallProtError(Box::new(
"unknown clif trap code".to_string(),
)))
return Err(InvokeError::UnknownTrapCode {
trap_code: format!("{:?}", trapcode),
srcloc,
})
}
},
Ok(SIGSEGV) | Ok(SIGBUS) => ExceptionCode::MemoryOutOfBounds,
Expand All @@ -112,7 +110,8 @@ pub fn call_protected<T>(
"ExceptionCode::Unknown signal:{:?}",
Signal::from_c_int(signum)
),
})))
};
Err(InvokeError::TrapCode { srcloc, code })
} else {
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
Expand All @@ -123,8 +122,10 @@ pub fn call_protected<T>(
_ => "unknown trapped signal",
};
// When the trap-handler is fully implemented, this will return more information.
let s = format!("unknown trap at {:p} - {}", faulting_addr, signal);
Err(CallProtError(Box::new(s)))
Err(InvokeError::UnknownTrap {
address: faulting_addr as usize,
signal,
})
}
}
} else {
Expand Down
98 changes: 54 additions & 44 deletions lib/clif-backend/src/signal/windows.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
relocation::{TrapCode, TrapData},
signal::{CallProtError, HandlerData},
signal::HandlerData,
};
use std::{
cell::Cell,
Expand All @@ -9,6 +9,7 @@ use std::{
};
use wasmer_runtime_core::{
backend::ExceptionCode,
error::InvokeError,
typed_func::Trampoline,
vm::{Ctx, Func},
};
Expand All @@ -32,14 +33,42 @@ thread_local! {
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
}

fn get_signal_name(code: DWORD) -> &'static str {
match code {
EXCEPTION_FLT_DENORMAL_OPERAND
| EXCEPTION_FLT_DIVIDE_BY_ZERO
| EXCEPTION_FLT_INEXACT_RESULT
| EXCEPTION_FLT_INVALID_OPERATION
| EXCEPTION_FLT_OVERFLOW
| EXCEPTION_FLT_STACK_CHECK
| EXCEPTION_FLT_UNDERFLOW => "floating-point exception",
EXCEPTION_ILLEGAL_INSTRUCTION => "illegal instruction",
EXCEPTION_ACCESS_VIOLATION => "segmentation violation",
EXCEPTION_DATATYPE_MISALIGNMENT => "datatype misalignment",
EXCEPTION_BREAKPOINT => "breakpoint",
EXCEPTION_SINGLE_STEP => "single step",
EXCEPTION_ARRAY_BOUNDS_EXCEEDED => "array bounds exceeded",
EXCEPTION_INT_DIVIDE_BY_ZERO => "integer division by zero",
EXCEPTION_INT_OVERFLOW => "integer overflow",
EXCEPTION_PRIV_INSTRUCTION => "privileged instruction",
EXCEPTION_IN_PAGE_ERROR => "in page error",
EXCEPTION_NONCONTINUABLE_EXCEPTION => "non continuable exception",
EXCEPTION_STACK_OVERFLOW => "stack overflow",
EXCEPTION_GUARD_PAGE => "guard page",
EXCEPTION_INVALID_HANDLE => "invalid handle",
EXCEPTION_POSSIBLE_DEADLOCK => "possible deadlock",
_ => "unknown exception code",
}
}

pub fn call_protected(
handler_data: &HandlerData,
trampoline: Trampoline,
ctx: *mut Ctx,
func: NonNull<Func>,
param_vec: *const u64,
return_vec: *mut u64,
) -> Result<(), CallProtError> {
) -> Result<(), InvokeError> {
// TODO: trap early
// user code error
// if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Expand All @@ -58,64 +87,45 @@ pub fn call_protected(
instruction_pointer,
} = result.unwrap_err();

if let Some(TrapData {
trapcode,
srcloc: _,
}) = handler_data.lookup(instruction_pointer as _)
{
Err(CallProtError(Box::new(match code as DWORD {
if let Some(TrapData { trapcode, srcloc }) = handler_data.lookup(instruction_pointer as _) {
let exception_code = match code as DWORD {
EXCEPTION_ACCESS_VIOLATION => ExceptionCode::MemoryOutOfBounds,
EXCEPTION_ILLEGAL_INSTRUCTION => match trapcode {
TrapCode::BadSignature => ExceptionCode::IncorrectCallIndirectSignature,
TrapCode::IndirectCallToNull => ExceptionCode::CallIndirectOOB,
TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds,
TrapCode::TableOutOfBounds => ExceptionCode::CallIndirectOOB,
TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable,
_ => return Err(CallProtError(Box::new("unknown trap code".to_string()))),
_ => {
return Err(InvokeError::UnknownTrapCode {
trap_code: format!("{}", code as DWORD),
srcloc,
})
}
},
EXCEPTION_STACK_OVERFLOW => ExceptionCode::MemoryOutOfBounds,
EXCEPTION_INT_DIVIDE_BY_ZERO | EXCEPTION_INT_OVERFLOW => {
ExceptionCode::IllegalArithmetic
}
_ => {
return Err(CallProtError(Box::new(
"unknown exception code".to_string(),
)))
let signal = get_signal_name(code as DWORD);
return Err(InvokeError::UnknownTrap {
address: exception_address as usize,
signal,
});
}
})))
} else {
let signal = match code as DWORD {
EXCEPTION_FLT_DENORMAL_OPERAND
| EXCEPTION_FLT_DIVIDE_BY_ZERO
| EXCEPTION_FLT_INEXACT_RESULT
| EXCEPTION_FLT_INVALID_OPERATION
| EXCEPTION_FLT_OVERFLOW
| EXCEPTION_FLT_STACK_CHECK
| EXCEPTION_FLT_UNDERFLOW => "floating-point exception",
EXCEPTION_ILLEGAL_INSTRUCTION => "illegal instruction",
EXCEPTION_ACCESS_VIOLATION => "segmentation violation",
EXCEPTION_DATATYPE_MISALIGNMENT => "datatype misalignment",
EXCEPTION_BREAKPOINT => "breakpoint",
EXCEPTION_SINGLE_STEP => "single step",
EXCEPTION_ARRAY_BOUNDS_EXCEEDED => "array bounds exceeded",
EXCEPTION_INT_DIVIDE_BY_ZERO => "int div by zero",
EXCEPTION_INT_OVERFLOW => "int overflow",
EXCEPTION_PRIV_INSTRUCTION => "priv instruction",
EXCEPTION_IN_PAGE_ERROR => "in page error",
EXCEPTION_NONCONTINUABLE_EXCEPTION => "non continuable exception",
EXCEPTION_STACK_OVERFLOW => "stack overflow",
EXCEPTION_GUARD_PAGE => "guard page",
EXCEPTION_INVALID_HANDLE => "invalid handle",
EXCEPTION_POSSIBLE_DEADLOCK => "possible deadlock",
_ => "unknown exception code",
};
return Err(InvokeError::TrapCode {
srcloc,
code: exception_code,
});
} else {
let signal = get_signal_name(code as DWORD);

let s = format!(
"unhandled trap at {:x} - code #{:x}: {}",
exception_address, code, signal,
);

Err(CallProtError(Box::new(s)))
Err(InvokeError::UnknownTrap {
address: exception_address as usize,
signal,
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/llvm-backend/cpp/object_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ MemoryManager::~MemoryManager() {
callbacks.dealloc_memory(read_section.base, read_section.size);
callbacks.dealloc_memory(readwrite_section.base, readwrite_section.size);
}
void unwinding_setjmp(jmp_buf stack_out, void (*func)(void *), void *userdata) {
void unwinding_setjmp(jmp_buf &stack_out, void (*func)(void *), void *userdata) {
if (setjmp(stack_out)) {

} else {
Expand Down
33 changes: 21 additions & 12 deletions lib/llvm-backend/cpp/object_loader.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ typedef struct {
visit_fde_t visit_fde;
} callbacks_t;

typedef struct {
size_t data, vtable;
} box_any_t;
typedef struct runtime_error_t_privdecl runtime_error_t;

enum WasmTrapType {
Unreachable = 0,
Expand All @@ -65,6 +63,9 @@ enum WasmTrapType {

extern "C" void callback_trampoline(void *, void *);

extern "C" void copy_runtime_error(runtime_error_t *src, runtime_error_t *dst);
extern "C" void free_runtime_error_without_drop(runtime_error_t*);

struct MemoryManager : llvm::RuntimeDyld::MemoryManager {
public:
MemoryManager(callbacks_t callbacks) : callbacks(callbacks) {}
Expand Down Expand Up @@ -121,7 +122,7 @@ private:

struct WasmErrorSink {
WasmTrapType *trap_out;
box_any_t *user_error;
runtime_error_t *user_error;
};

struct WasmException : std::exception {
Expand Down Expand Up @@ -149,17 +150,25 @@ public:

struct UserException : UncatchableException {
public:
UserException(size_t data, size_t vtable) : error_data({data, vtable}) {}
UserException(size_t data) {
error_data = reinterpret_cast<runtime_error_t*>(data);
}

UserException(const UserException &) = delete;

~UserException() {
free_runtime_error_without_drop(error_data);
}

virtual std::string description() const noexcept override {
return "user exception";
}

// The parts of a `Box<dyn Any>`.
box_any_t error_data;
// The pointer to `Option<RuntimeError>`.
runtime_error_t *error_data;

virtual void write_error(WasmErrorSink &out) const noexcept override {
*out.user_error = error_data;
copy_runtime_error(error_data, out.user_error);
}
};

Expand Down Expand Up @@ -274,10 +283,10 @@ result_t module_load(const uint8_t *mem_ptr, size_t mem_size,

void module_delete(WasmModule *module) { delete module; }

// Throw a fat pointer that's assumed to be `*mut dyn Any` on the rust
// Throw a pointer that's assumed to be `*mut Option<RuntimeError>` on the rust
// side.
[[noreturn]] void throw_any(size_t data, size_t vtable) {
unsafe_unwind(new UserException(data, vtable));
[[noreturn]] void throw_runtime_error(size_t data) {
unsafe_unwind(new UserException(data));
}

// Throw a pointer that's assumed to be codegen::BreakpointHandler on the
Expand All @@ -288,7 +297,7 @@ void module_delete(WasmModule *module) { delete module; }

bool cxx_invoke_trampoline(trampoline_t trampoline, void *ctx, void *func,
void *params, void *results, WasmTrapType *trap_out,
box_any_t *user_error, void *invoke_env) noexcept {
runtime_error_t *user_error, void *invoke_env) noexcept {
try {
catch_unwind([trampoline, ctx, func, params, results]() {
trampoline(ctx, func, params, results);
Expand Down
Loading