Preserve full native stack traces in errors (#823)
* Preserve full native stack traces in errors This commit builds on #759 by performing a few refactorings: * The `backtrace` crate is updated to 0.3.42 which incorporates the Windows-specific stack-walking code, so that's no longer needed. * A full `backtrace::Backtrace` type is held in a trap at all times. * The trap structures in the `wasmtime-*` internal crates were refactored a bit to preserve more information and deal with raw values rather than converting between various types and strings. * The `wasmtime::Trap` type has been updated with these various changes. Eventually I think we'll want to likely render full stack traces (and/or partial wasm ones) into error messages, but for now that's left as-is and we can always improve it later. I suspect the most relevant thing we need to do is to implement function name symbolication for wasm functions first, and then afterwards we can incorporate native function names! * Fix some test suite assertions
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
//! WebAssembly trap handling, which is built on top of the lower-level
|
||||
//! signalhandling mechanisms.
|
||||
|
||||
use crate::backtrace::{get_backtrace, Backtrace};
|
||||
use crate::trap_registry::get_trap_registry;
|
||||
use crate::trap_registry::TrapDescription;
|
||||
use crate::vmcontext::{VMContext, VMFunctionBody};
|
||||
use backtrace::Backtrace;
|
||||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
use wasmtime_environ::ir;
|
||||
|
||||
@@ -19,7 +20,7 @@ extern "C" {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RECORDED_TRAP: Cell<Option<(TrapDescription, Backtrace)>> = Cell::new(None);
|
||||
static RECORDED_TRAP: Cell<Option<Trap>> = Cell::new(None);
|
||||
static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null());
|
||||
static RESET_GUARD_PAGE: Cell<bool> = Cell::new(false);
|
||||
}
|
||||
@@ -44,26 +45,26 @@ pub extern "C" fn CheckIfTrapAtAddress(_pc: *const u8) -> i8 {
|
||||
pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) {
|
||||
// TODO: please see explanation in CheckIfTrapAtAddress.
|
||||
let registry = get_trap_registry();
|
||||
let trap_desc = registry
|
||||
.get_trap(pc as usize)
|
||||
.unwrap_or_else(|| TrapDescription {
|
||||
source_loc: ir::SourceLoc::default(),
|
||||
trap_code: ir::TrapCode::StackOverflow,
|
||||
});
|
||||
|
||||
let trap_backtrace = get_backtrace();
|
||||
let trap = Trap {
|
||||
desc: registry
|
||||
.get_trap(pc as usize)
|
||||
.unwrap_or_else(|| TrapDescription {
|
||||
source_loc: ir::SourceLoc::default(),
|
||||
trap_code: ir::TrapCode::StackOverflow,
|
||||
}),
|
||||
backtrace: Backtrace::new_unresolved(),
|
||||
};
|
||||
|
||||
if reset_guard_page {
|
||||
RESET_GUARD_PAGE.with(|v| v.set(true));
|
||||
}
|
||||
|
||||
RECORDED_TRAP.with(|data| {
|
||||
assert_eq!(
|
||||
data.get(),
|
||||
None,
|
||||
let prev = data.replace(Some(trap));
|
||||
assert!(
|
||||
prev.is_none(),
|
||||
"Only one trap per thread can be recorded at a moment!"
|
||||
);
|
||||
data.set(Some((trap_desc, trap_backtrace)))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,21 +114,31 @@ fn reset_guard_page() {}
|
||||
|
||||
/// Stores trace message with backtrace.
|
||||
#[derive(Debug)]
|
||||
pub struct TrapMessageAndStack(pub String, pub Backtrace);
|
||||
pub struct Trap {
|
||||
/// What sort of trap happened, as well as where in the original wasm module
|
||||
/// it happened.
|
||||
pub desc: TrapDescription,
|
||||
/// Native stack backtrace at the time the trap occurred
|
||||
pub backtrace: Backtrace,
|
||||
}
|
||||
|
||||
fn trap_message_and_stack() -> TrapMessageAndStack {
|
||||
let trap_desc = RECORDED_TRAP
|
||||
.with(|data| data.replace(None))
|
||||
.expect("trap_message must be called after trap occurred");
|
||||
|
||||
TrapMessageAndStack(
|
||||
format!(
|
||||
impl fmt::Display for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"wasm trap: {}, source location: {}",
|
||||
trap_code_to_expected_string(trap_desc.0.trap_code),
|
||||
trap_desc.0.source_loc,
|
||||
),
|
||||
trap_desc.1,
|
||||
)
|
||||
trap_code_to_expected_string(self.desc.trap_code),
|
||||
self.desc.source_loc
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Trap {}
|
||||
|
||||
fn last_trap() -> Trap {
|
||||
RECORDED_TRAP
|
||||
.with(|data| data.replace(None))
|
||||
.expect("trap_message must be called after trap occurred")
|
||||
}
|
||||
|
||||
fn trap_code_to_expected_string(trap_code: ir::TrapCode) -> String {
|
||||
@@ -156,9 +167,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
||||
vmctx: *mut VMContext,
|
||||
callee: *const VMFunctionBody,
|
||||
values_vec: *mut u8,
|
||||
) -> Result<(), TrapMessageAndStack> {
|
||||
) -> Result<(), Trap> {
|
||||
if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 {
|
||||
Err(trap_message_and_stack())
|
||||
Err(last_trap())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -170,9 +181,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
||||
pub unsafe extern "C" fn wasmtime_call(
|
||||
vmctx: *mut VMContext,
|
||||
callee: *const VMFunctionBody,
|
||||
) -> Result<(), TrapMessageAndStack> {
|
||||
) -> Result<(), Trap> {
|
||||
if WasmtimeCall(vmctx as *mut u8, callee) == 0 {
|
||||
Err(trap_message_and_stack())
|
||||
Err(last_trap())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user