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:
@@ -18,6 +18,7 @@ anyhow = "1.0.19"
|
||||
region = "2.0.0"
|
||||
libc = "0.2"
|
||||
cfg-if = "0.1.9"
|
||||
backtrace = "0.3.42"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
|
||||
@@ -156,12 +156,7 @@ impl WrappedCallable for WasmtimeFn {
|
||||
)
|
||||
})
|
||||
} {
|
||||
let message = error.0;
|
||||
let backtrace = error.1;
|
||||
|
||||
let trap = take_api_trap().unwrap_or_else(|| {
|
||||
Trap::new_with_trace(format!("call error: {}", message), backtrace)
|
||||
});
|
||||
let trap = take_api_trap().unwrap_or_else(|| Trap::from_jit(error));
|
||||
return Err(trap);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ fn instantiate_in_context(
|
||||
let instance = compiled_module.instantiate().map_err(|e| -> Error {
|
||||
if let Some(trap) = take_api_trap() {
|
||||
trap.into()
|
||||
} else if let InstantiationError::StartTrap(msg) = e {
|
||||
Trap::new(msg).into()
|
||||
} else if let InstantiationError::StartTrap(trap) = e {
|
||||
Trap::from_jit(trap).into()
|
||||
} else {
|
||||
e.into()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::instance::Instance;
|
||||
use backtrace::Backtrace;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_runtime::{get_backtrace, Backtrace, BacktraceFrame};
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
@@ -12,7 +12,8 @@ pub struct Trap {
|
||||
|
||||
struct TrapInner {
|
||||
message: String,
|
||||
trace: Vec<FrameInfo>,
|
||||
wasm_trace: Vec<FrameInfo>,
|
||||
native_trace: Backtrace,
|
||||
}
|
||||
|
||||
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||
@@ -27,21 +28,29 @@ impl Trap {
|
||||
/// assert_eq!("unexpected error", trap.message());
|
||||
/// ```
|
||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||
Self::new_with_trace(message, get_backtrace())
|
||||
Trap::new_with_trace(message.into(), Backtrace::new_unresolved())
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_trace<I: Into<String>>(message: I, backtrace: Backtrace) -> Self {
|
||||
let mut trace = Vec::with_capacity(backtrace.len());
|
||||
for i in 0..backtrace.len() {
|
||||
// Don't include frames without backtrace info.
|
||||
if let Some(info) = FrameInfo::try_from(backtrace[i]) {
|
||||
trace.push(info);
|
||||
pub(crate) fn from_jit(jit: wasmtime_runtime::Trap) -> Self {
|
||||
Trap::new_with_trace(jit.to_string(), jit.backtrace)
|
||||
}
|
||||
|
||||
fn new_with_trace(message: String, native_trace: Backtrace) -> Self {
|
||||
let mut wasm_trace = Vec::new();
|
||||
for frame in native_trace.frames() {
|
||||
let pc = frame.ip() as usize;
|
||||
if let Some(info) = wasmtime_runtime::jit_function_registry::find(pc) {
|
||||
wasm_trace.push(FrameInfo {
|
||||
func_index: info.func_index as u32,
|
||||
module_name: info.module_id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Trap {
|
||||
inner: Arc::new(TrapInner {
|
||||
message: message.into(),
|
||||
trace,
|
||||
message,
|
||||
wasm_trace,
|
||||
native_trace,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -52,7 +61,7 @@ impl Trap {
|
||||
}
|
||||
|
||||
pub fn trace(&self) -> &[FrameInfo] {
|
||||
&self.inner.trace
|
||||
&self.inner.wasm_trace
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +69,8 @@ impl fmt::Debug for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Trap")
|
||||
.field("message", &self.inner.message)
|
||||
.field("trace", &self.inner.trace)
|
||||
.field("wasm_trace", &self.inner.wasm_trace)
|
||||
.field("native_trace", &self.inner.native_trace)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -99,17 +109,4 @@ impl FrameInfo {
|
||||
pub fn module_name(&self) -> Option<&str> {
|
||||
self.module_name.as_deref()
|
||||
}
|
||||
|
||||
pub(crate) fn try_from(backtrace: BacktraceFrame) -> Option<FrameInfo> {
|
||||
if let Some(tag) = backtrace.tag() {
|
||||
let func_index = tag.func_index as u32;
|
||||
let module_name = tag.module_id.clone();
|
||||
Some(FrameInfo {
|
||||
func_index,
|
||||
module_name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user