make backtrace collection a Config field rather than a cargo feature (#4183)
* sorta working in runtime * wasmtime-runtime: get rid of wasm-backtrace feature * wasmtime: factor to make backtraces recording optional. not configurable yet * get rid of wasm-backtrace features * trap tests: now a Trap optionally contains backtrace * eliminate wasm-backtrace feature * code review fixes * ci: no more wasm-backtrace feature * c_api: backtraces always enabled * config: unwind required by backtraces and ref types * plumbed * test that disabling backtraces works * code review comments * fuzzing generator: wasm_backtrace is a runtime config now * doc fix
This commit is contained in:
@@ -704,7 +704,6 @@ impl VMExternRefActivationsTable {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "wasm-backtrace"), allow(dead_code))]
|
||||
fn insert_precise_stack_root(
|
||||
precise_stack_roots: &mut HashSet<VMExternRefWithTraits>,
|
||||
root: NonNull<VMExternData>,
|
||||
@@ -867,7 +866,6 @@ impl<T> std::ops::DerefMut for DebugOnly<T> {
|
||||
///
|
||||
/// Additionally, you must have registered the stack maps for every Wasm module
|
||||
/// that has frames on the stack with the given `stack_maps_registry`.
|
||||
#[cfg_attr(not(feature = "wasm-backtrace"), allow(unused_mut, unused_variables))]
|
||||
pub unsafe fn gc(
|
||||
module_info_lookup: &dyn ModuleInfoLookup,
|
||||
externref_activations_table: &mut VMExternRefActivationsTable,
|
||||
@@ -895,7 +893,6 @@ pub unsafe fn gc(
|
||||
None => {
|
||||
if cfg!(debug_assertions) {
|
||||
// Assert that there aren't any Wasm frames on the stack.
|
||||
#[cfg(feature = "wasm-backtrace")]
|
||||
backtrace::trace(|frame| {
|
||||
assert!(module_info_lookup.lookup(frame.ip() as usize).is_none());
|
||||
true
|
||||
@@ -937,7 +934,6 @@ pub unsafe fn gc(
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm-backtrace")]
|
||||
backtrace::trace(|frame| {
|
||||
let pc = frame.ip() as usize;
|
||||
let sp = frame.sp() as usize;
|
||||
|
||||
@@ -507,7 +507,7 @@ pub unsafe extern "C" fn memory_atomic_notify(
|
||||
// just to be sure.
|
||||
let addr_to_check = addr.checked_add(4).unwrap();
|
||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
||||
Err(Trap::User(anyhow::anyhow!(
|
||||
Err(Trap::user(anyhow::anyhow!(
|
||||
"unimplemented: wasm atomics (fn memory_atomic_notify) unsupported",
|
||||
)))
|
||||
})
|
||||
@@ -534,7 +534,7 @@ pub unsafe extern "C" fn memory_atomic_wait32(
|
||||
// but we still double-check
|
||||
let addr_to_check = addr.checked_add(4).unwrap();
|
||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
||||
Err(Trap::User(anyhow::anyhow!(
|
||||
Err(Trap::user(anyhow::anyhow!(
|
||||
"unimplemented: wasm atomics (fn memory_atomic_wait32) unsupported",
|
||||
)))
|
||||
})
|
||||
@@ -561,7 +561,7 @@ pub unsafe extern "C" fn memory_atomic_wait64(
|
||||
// but we still double-check
|
||||
let addr_to_check = addr.checked_add(8).unwrap();
|
||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
||||
Err(Trap::User(anyhow::anyhow!(
|
||||
Err(Trap::user(anyhow::anyhow!(
|
||||
"unimplemented: wasm atomics (fn memory_atomic_wait64) unsupported",
|
||||
)))
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ use std::sync::Once;
|
||||
use wasmtime_environ::TrapCode;
|
||||
|
||||
pub use self::tls::{tls_eager_initialize, TlsRestore};
|
||||
pub use backtrace::Backtrace;
|
||||
|
||||
#[link(name = "wasmtime-helpers")]
|
||||
extern "C" {
|
||||
@@ -112,14 +113,19 @@ pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
|
||||
#[derive(Debug)]
|
||||
pub enum Trap {
|
||||
/// A user-raised trap through `raise_user_trap`.
|
||||
User(Error),
|
||||
User {
|
||||
/// The user-provided error
|
||||
error: Error,
|
||||
/// Native stack backtrace at the time the trap occurred
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
|
||||
/// A trap raised from jit code
|
||||
Jit {
|
||||
/// The program counter in JIT code where this trap happened.
|
||||
pc: usize,
|
||||
/// Native stack backtrace at the time the trap occurred
|
||||
backtrace: Backtrace,
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
|
||||
/// A trap raised from a wasm libcall
|
||||
@@ -127,63 +133,53 @@ pub enum Trap {
|
||||
/// Code of the trap.
|
||||
trap_code: TrapCode,
|
||||
/// Native stack backtrace at the time the trap occurred
|
||||
backtrace: Backtrace,
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
|
||||
/// A trap indicating that the runtime was unable to allocate sufficient memory.
|
||||
OOM {
|
||||
/// Native stack backtrace at the time the OOM occurred
|
||||
backtrace: Backtrace,
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Trap {
|
||||
/// Construct a new Wasm trap with the given source location and trap code.
|
||||
/// Construct a new Wasm trap with the given trap code.
|
||||
///
|
||||
/// Internally saves a backtrace when constructed.
|
||||
/// Internally saves a backtrace when passed across a setjmp boundary, if the
|
||||
/// engine is configured to save backtraces.
|
||||
pub fn wasm(trap_code: TrapCode) -> Self {
|
||||
Trap::Wasm {
|
||||
trap_code,
|
||||
backtrace: Backtrace::new(),
|
||||
backtrace: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new OOM trap with the given source location and trap code.
|
||||
/// Construct a new Wasm trap from a user Error.
|
||||
///
|
||||
/// Internally saves a backtrace when constructed.
|
||||
/// Internally saves a backtrace when passed across a setjmp boundary, if the
|
||||
/// engine is configured to save backtraces.
|
||||
pub fn user(error: Error) -> Self {
|
||||
Trap::User {
|
||||
error,
|
||||
backtrace: None,
|
||||
}
|
||||
}
|
||||
/// Construct a new OOM trap.
|
||||
///
|
||||
/// Internally saves a backtrace when passed across a setjmp boundary, if the
|
||||
/// engine is configured to save backtraces.
|
||||
pub fn oom() -> Self {
|
||||
Trap::OOM {
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A crate-local backtrace type which conditionally, at compile time, actually
|
||||
/// contains a backtrace from the `backtrace` crate or nothing.
|
||||
#[derive(Debug)]
|
||||
pub struct Backtrace {
|
||||
#[cfg(feature = "wasm-backtrace")]
|
||||
trace: backtrace::Backtrace,
|
||||
}
|
||||
|
||||
impl Backtrace {
|
||||
/// Captures a new backtrace
|
||||
///
|
||||
/// Note that this function does nothing if the `wasm-backtrace` feature is
|
||||
/// disabled.
|
||||
pub fn new() -> Backtrace {
|
||||
Backtrace {
|
||||
#[cfg(feature = "wasm-backtrace")]
|
||||
trace: backtrace::Backtrace::new_unresolved(),
|
||||
}
|
||||
Trap::OOM { backtrace: None }
|
||||
}
|
||||
|
||||
/// Returns the backtrace frames associated with this backtrace. Note that
|
||||
/// this is conditionally defined and not present when `wasm-backtrace` is
|
||||
/// not present.
|
||||
#[cfg(feature = "wasm-backtrace")]
|
||||
pub fn frames(&self) -> &[backtrace::BacktraceFrame] {
|
||||
self.trace.frames()
|
||||
fn insert_backtrace(&mut self, bt: Backtrace) {
|
||||
match self {
|
||||
Trap::User { backtrace, .. } => *backtrace = Some(bt),
|
||||
Trap::Jit { backtrace, .. } => *backtrace = Some(bt),
|
||||
Trap::Wasm { backtrace, .. } => *backtrace = Some(bt),
|
||||
Trap::OOM { backtrace, .. } => *backtrace = Some(bt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,13 +189,14 @@ impl Backtrace {
|
||||
/// Highly unsafe since `closure` won't have any dtors run.
|
||||
pub unsafe fn catch_traps<'a, F>(
|
||||
signal_handler: Option<*const SignalHandler<'static>>,
|
||||
capture_backtrace: bool,
|
||||
callee: *mut VMContext,
|
||||
mut closure: F,
|
||||
) -> Result<(), Box<Trap>>
|
||||
where
|
||||
F: FnMut(*mut VMContext),
|
||||
{
|
||||
return CallThreadState::new(signal_handler).with(|cx| {
|
||||
return CallThreadState::new(signal_handler, capture_backtrace).with(|cx| {
|
||||
wasmtime_setjmp(
|
||||
cx.jmp_buf.as_ptr(),
|
||||
call_closure::<F>,
|
||||
@@ -219,29 +216,34 @@ where
|
||||
/// Temporary state stored on the stack which is registered in the `tls` module
|
||||
/// below for calls into wasm.
|
||||
pub struct CallThreadState {
|
||||
unwind: UnsafeCell<MaybeUninit<UnwindReason>>,
|
||||
unwind: UnsafeCell<MaybeUninit<(UnwindReason, Option<Backtrace>)>>,
|
||||
jmp_buf: Cell<*const u8>,
|
||||
handling_trap: Cell<bool>,
|
||||
signal_handler: Option<*const SignalHandler<'static>>,
|
||||
prev: Cell<tls::Ptr>,
|
||||
capture_backtrace: bool,
|
||||
}
|
||||
|
||||
enum UnwindReason {
|
||||
Panic(Box<dyn Any + Send>),
|
||||
UserTrap(Error),
|
||||
LibTrap(Trap),
|
||||
JitTrap { backtrace: Backtrace, pc: usize },
|
||||
JitTrap { pc: usize }, // Removed a backtrace here
|
||||
}
|
||||
|
||||
impl CallThreadState {
|
||||
#[inline]
|
||||
fn new(signal_handler: Option<*const SignalHandler<'static>>) -> CallThreadState {
|
||||
fn new(
|
||||
signal_handler: Option<*const SignalHandler<'static>>,
|
||||
capture_backtrace: bool,
|
||||
) -> CallThreadState {
|
||||
CallThreadState {
|
||||
unwind: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
jmp_buf: Cell::new(ptr::null()),
|
||||
handling_trap: Cell::new(false),
|
||||
signal_handler,
|
||||
prev: Cell::new(ptr::null()),
|
||||
capture_backtrace,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,16 +259,26 @@ impl CallThreadState {
|
||||
#[cold]
|
||||
unsafe fn read_trap(&self) -> Box<Trap> {
|
||||
Box::new(match (*self.unwind.get()).as_ptr().read() {
|
||||
UnwindReason::UserTrap(data) => Trap::User(data),
|
||||
UnwindReason::LibTrap(trap) => trap,
|
||||
UnwindReason::JitTrap { backtrace, pc } => Trap::Jit { pc, backtrace },
|
||||
UnwindReason::Panic(panic) => std::panic::resume_unwind(panic),
|
||||
(UnwindReason::UserTrap(error), backtrace) => Trap::User { error, backtrace },
|
||||
(UnwindReason::LibTrap(mut trap), backtrace) => {
|
||||
if let Some(backtrace) = backtrace {
|
||||
trap.insert_backtrace(backtrace);
|
||||
}
|
||||
trap
|
||||
}
|
||||
(UnwindReason::JitTrap { pc }, backtrace) => Trap::Jit { pc, backtrace },
|
||||
(UnwindReason::Panic(panic), _) => std::panic::resume_unwind(panic),
|
||||
})
|
||||
}
|
||||
|
||||
fn unwind_with(&self, reason: UnwindReason) -> ! {
|
||||
let backtrace = if self.capture_backtrace {
|
||||
Some(Backtrace::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
unsafe {
|
||||
(*self.unwind.get()).as_mut_ptr().write(reason);
|
||||
(*self.unwind.get()).as_mut_ptr().write((reason, backtrace));
|
||||
wasmtime_longjmp(self.jmp_buf.get());
|
||||
}
|
||||
}
|
||||
@@ -327,14 +339,15 @@ impl CallThreadState {
|
||||
}
|
||||
|
||||
fn capture_backtrace(&self, pc: *const u8) {
|
||||
let backtrace = Backtrace::new();
|
||||
let backtrace = if self.capture_backtrace {
|
||||
Some(Backtrace::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
unsafe {
|
||||
(*self.unwind.get())
|
||||
.as_mut_ptr()
|
||||
.write(UnwindReason::JitTrap {
|
||||
backtrace,
|
||||
pc: pc as usize,
|
||||
});
|
||||
.write((UnwindReason::JitTrap { pc: pc as usize }, backtrace));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user