Support disabling backtraces at compile time (#3932)

* Support disabling backtraces at compile time

This commit adds support to Wasmtime to disable, at compile time, the
gathering of backtraces on traps. The `wasmtime` crate now sports a
`wasm-backtrace` feature which, when disabled, will mean that backtraces
are never collected at compile time nor are unwinding tables inserted
into compiled objects.

The motivation for this commit stems from the fact that generating a
backtrace is quite a slow operation. Currently backtrace generation is
done with libunwind and `_Unwind_Backtrace` typically found in glibc or
other system libraries. When thousands of modules are loaded into the
same process though this means that the initial backtrace can take
nearly half a second and all subsequent backtraces can take upwards of
hundreds of milliseconds. Relative to all other operations in Wasmtime
this is extremely expensive at this time. In the future we'd like to
implement a more performant backtrace scheme but such an implementation
would require coordination with Cranelift and is a big chunk of work
that may take some time, so in the meantime if embedders don't need a
backtrace they can still use this option to disable backtraces at
compile time and avoid the performance pitfalls of collecting
backtraces.

In general I tried to originally make this a runtime configuration
option but ended up opting for a compile-time option because `Trap::new`
otherwise has no arguments and always captures a backtrace. By making
this a compile-time option it was possible to configure, statically, the
behavior of `Trap::new`. Additionally I also tried to minimize the
amount of `#[cfg]` necessary by largely only having it at the producer
and consumer sites.

Also a noteworthy restriction of this implementation is that if
backtrace support is disabled at compile time then reference types
support will be unconditionally disabled at runtime. With backtrace
support disabled there's no way to trace the stack of wasm frames which
means that GC can't happen given our current implementation.

* Always enable backtraces for the C API
This commit is contained in:
Alex Crichton
2022-03-16 09:18:16 -05:00
committed by GitHub
parent 7a3d5cf814
commit 3f9bff17c8
15 changed files with 173 additions and 84 deletions

View File

@@ -22,7 +22,7 @@ indexmap = "1.0.2"
thiserror = "1.0.4"
more-asserts = "0.2.1"
cfg-if = "1.0"
backtrace = "0.3.61"
backtrace = { version = "0.3.61", optional = true }
rand = "0.8.3"
anyhow = "1.0.38"
memfd = { version = "0.4.1", optional = true }
@@ -46,8 +46,8 @@ cc = "1.0"
maintenance = { status = "actively-developed" }
[features]
default = []
memory-init-cow = ['memfd']
wasm-backtrace = ["backtrace"]
async = ["wasmtime-fiber"]

View File

@@ -704,6 +704,7 @@ 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>,
@@ -866,6 +867,7 @@ 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,
@@ -893,6 +895,7 @@ 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
@@ -917,7 +920,7 @@ pub unsafe fn gc(
// newly-discovered precise set.
// The SP of the previous (younger) frame we processed.
let mut last_sp = None;
let mut last_sp: Option<usize> = None;
// Whether we have found our stack canary or not yet.
let mut found_canary = false;
@@ -934,6 +937,7 @@ 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

@@ -61,7 +61,7 @@ pub use crate::mmap_vec::MmapVec;
pub use crate::table::{Table, TableElement};
pub use crate::traphandlers::{
catch_traps, init_traps, raise_lib_trap, raise_user_trap, resume_panic, tls_eager_initialize,
SignalHandler, TlsRestore, Trap,
Backtrace, SignalHandler, TlsRestore, Trap,
};
pub use crate::vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,

View File

@@ -61,7 +61,6 @@ use crate::instance::Instance;
use crate::table::{Table, TableElementType};
use crate::traphandlers::{raise_lib_trap, resume_panic, Trap};
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
use backtrace::Backtrace;
use std::mem;
use std::ptr::{self, NonNull};
use wasmtime_environ::{
@@ -588,10 +587,7 @@ unsafe fn validate_atomic_addr(
addr: usize,
) -> Result<(), Trap> {
if addr > instance.get_memory(memory).current_length {
return Err(Trap::Wasm {
trap_code: TrapCode::HeapOutOfBounds,
backtrace: Backtrace::new_unresolved(),
});
return Err(Trap::wasm(TrapCode::HeapOutOfBounds));
}
Ok(())
}

View File

@@ -3,7 +3,6 @@
use crate::VMContext;
use anyhow::Error;
use backtrace::Backtrace;
use std::any::Any;
use std::cell::{Cell, UnsafeCell};
use std::mem::MaybeUninit;
@@ -143,10 +142,9 @@ impl Trap {
///
/// Internally saves a backtrace when constructed.
pub fn wasm(trap_code: TrapCode) -> Self {
let backtrace = Backtrace::new_unresolved();
Trap::Wasm {
trap_code,
backtrace,
backtrace: Backtrace::new(),
}
}
@@ -154,8 +152,38 @@ impl Trap {
///
/// Internally saves a backtrace when constructed.
pub fn oom() -> Self {
let backtrace = Backtrace::new_unresolved();
Trap::OOM { backtrace }
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(),
}
}
/// 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()
}
}
@@ -299,7 +327,7 @@ impl CallThreadState {
}
fn capture_backtrace(&self, pc: *const u8) {
let backtrace = Backtrace::new_unresolved();
let backtrace = Backtrace::new();
unsafe {
(*self.unwind.get())
.as_mut_ptr()