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:
Pat Hickey
2022-05-25 12:25:50 -07:00
committed by GitHub
parent 010e028d67
commit bffce37050
20 changed files with 310 additions and 234 deletions

View File

@@ -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;

View File

@@ -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",
)))
})

View File

@@ -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));
}
}
}