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:
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -137,7 +137,8 @@ jobs:
|
|||||||
- run: cargo check -p wasmtime --no-default-features --features uffd
|
- run: cargo check -p wasmtime --no-default-features --features uffd
|
||||||
- run: cargo check -p wasmtime --no-default-features --features pooling-allocator
|
- run: cargo check -p wasmtime --no-default-features --features pooling-allocator
|
||||||
- run: cargo check -p wasmtime --no-default-features --features cranelift
|
- run: cargo check -p wasmtime --no-default-features --features cranelift
|
||||||
- run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache
|
- run: cargo check -p wasmtime --no-default-features --features wasm-backtrace
|
||||||
|
- run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache,wasm-backtrace
|
||||||
|
|
||||||
# Check that benchmarks of the cranelift project build
|
# Check that benchmarks of the cranelift project build
|
||||||
- run: cargo check --benches -p cranelift-codegen
|
- run: cargo check --benches -p cranelift-codegen
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ default = [
|
|||||||
"wasi-nn",
|
"wasi-nn",
|
||||||
"pooling-allocator",
|
"pooling-allocator",
|
||||||
"memory-init-cow",
|
"memory-init-cow",
|
||||||
|
"wasm-backtrace",
|
||||||
]
|
]
|
||||||
jitdump = ["wasmtime/jitdump"]
|
jitdump = ["wasmtime/jitdump"]
|
||||||
vtune = ["wasmtime/vtune"]
|
vtune = ["wasmtime/vtune"]
|
||||||
@@ -109,6 +110,7 @@ memory-init-cow = ["wasmtime/memory-init-cow"]
|
|||||||
pooling-allocator = ["wasmtime/pooling-allocator"]
|
pooling-allocator = ["wasmtime/pooling-allocator"]
|
||||||
all-arch = ["wasmtime/all-arch"]
|
all-arch = ["wasmtime/all-arch"]
|
||||||
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
|
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
|
||||||
|
wasm-backtrace = ["wasmtime/wasm-backtrace"]
|
||||||
|
|
||||||
# Stub feature that does nothing, for Cargo-features compatibility: the new
|
# Stub feature that does nothing, for Cargo-features compatibility: the new
|
||||||
# backend is the default now.
|
# backend is the default now.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ doctest = false
|
|||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
once_cell = "1.3"
|
once_cell = "1.3"
|
||||||
wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift'] }
|
wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift', 'wasm-backtrace'] }
|
||||||
wasmtime-c-api-macros = { path = "macros" }
|
wasmtime-c-api-macros = { path = "macros" }
|
||||||
|
|
||||||
# Optional dependency for the `wat2wasm` API
|
# Optional dependency for the `wat2wasm` API
|
||||||
|
|||||||
@@ -188,9 +188,13 @@ impl wasmtime_environ::Compiler for Compiler {
|
|||||||
|
|
||||||
let stack_maps = mach_stack_maps_to_stack_maps(result.buffer.stack_maps());
|
let stack_maps = mach_stack_maps_to_stack_maps(result.buffer.stack_maps());
|
||||||
|
|
||||||
let unwind_info = context
|
let unwind_info = if isa.flags().unwind_info() {
|
||||||
|
context
|
||||||
.create_unwind_info(isa)
|
.create_unwind_info(isa)
|
||||||
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
|
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let address_transform =
|
let address_transform =
|
||||||
self.get_function_address_map(&context, &input, code_buf.len() as u32, tunables);
|
self.get_function_address_map(&context, &input, code_buf.len() as u32, tunables);
|
||||||
@@ -566,9 +570,13 @@ impl Compiler {
|
|||||||
.relocs()
|
.relocs()
|
||||||
.is_empty());
|
.is_empty());
|
||||||
|
|
||||||
let unwind_info = context
|
let unwind_info = if isa.flags().unwind_info() {
|
||||||
|
context
|
||||||
.create_unwind_info(isa)
|
.create_unwind_info(isa)
|
||||||
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
|
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(CompiledFunction {
|
Ok(CompiledFunction {
|
||||||
body: code_buf,
|
body: code_buf,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ indexmap = "1.0.2"
|
|||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
more-asserts = "0.2.1"
|
more-asserts = "0.2.1"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
backtrace = "0.3.61"
|
backtrace = { version = "0.3.61", optional = true }
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
memfd = { version = "0.4.1", optional = true }
|
memfd = { version = "0.4.1", optional = true }
|
||||||
@@ -46,8 +46,8 @@ cc = "1.0"
|
|||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
|
||||||
memory-init-cow = ['memfd']
|
memory-init-cow = ['memfd']
|
||||||
|
wasm-backtrace = ["backtrace"]
|
||||||
|
|
||||||
async = ["wasmtime-fiber"]
|
async = ["wasmtime-fiber"]
|
||||||
|
|
||||||
|
|||||||
@@ -704,6 +704,7 @@ impl VMExternRefActivationsTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "wasm-backtrace"), allow(dead_code))]
|
||||||
fn insert_precise_stack_root(
|
fn insert_precise_stack_root(
|
||||||
precise_stack_roots: &mut HashSet<VMExternRefWithTraits>,
|
precise_stack_roots: &mut HashSet<VMExternRefWithTraits>,
|
||||||
root: NonNull<VMExternData>,
|
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
|
/// Additionally, you must have registered the stack maps for every Wasm module
|
||||||
/// that has frames on the stack with the given `stack_maps_registry`.
|
/// 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(
|
pub unsafe fn gc(
|
||||||
module_info_lookup: &dyn ModuleInfoLookup,
|
module_info_lookup: &dyn ModuleInfoLookup,
|
||||||
externref_activations_table: &mut VMExternRefActivationsTable,
|
externref_activations_table: &mut VMExternRefActivationsTable,
|
||||||
@@ -893,6 +895,7 @@ pub unsafe fn gc(
|
|||||||
None => {
|
None => {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
// Assert that there aren't any Wasm frames on the stack.
|
// Assert that there aren't any Wasm frames on the stack.
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
backtrace::trace(|frame| {
|
backtrace::trace(|frame| {
|
||||||
assert!(module_info_lookup.lookup(frame.ip() as usize).is_none());
|
assert!(module_info_lookup.lookup(frame.ip() as usize).is_none());
|
||||||
true
|
true
|
||||||
@@ -917,7 +920,7 @@ pub unsafe fn gc(
|
|||||||
// newly-discovered precise set.
|
// newly-discovered precise set.
|
||||||
|
|
||||||
// The SP of the previous (younger) frame we processed.
|
// 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.
|
// Whether we have found our stack canary or not yet.
|
||||||
let mut found_canary = false;
|
let mut found_canary = false;
|
||||||
@@ -934,6 +937,7 @@ pub unsafe fn gc(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
backtrace::trace(|frame| {
|
backtrace::trace(|frame| {
|
||||||
let pc = frame.ip() as usize;
|
let pc = frame.ip() as usize;
|
||||||
let sp = frame.sp() as usize;
|
let sp = frame.sp() as usize;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ pub use crate::mmap_vec::MmapVec;
|
|||||||
pub use crate::table::{Table, TableElement};
|
pub use crate::table::{Table, TableElement};
|
||||||
pub use crate::traphandlers::{
|
pub use crate::traphandlers::{
|
||||||
catch_traps, init_traps, raise_lib_trap, raise_user_trap, resume_panic, tls_eager_initialize,
|
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::{
|
pub use crate::vmcontext::{
|
||||||
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
|
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ use crate::instance::Instance;
|
|||||||
use crate::table::{Table, TableElementType};
|
use crate::table::{Table, TableElementType};
|
||||||
use crate::traphandlers::{raise_lib_trap, resume_panic, Trap};
|
use crate::traphandlers::{raise_lib_trap, resume_panic, Trap};
|
||||||
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
|
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
|
||||||
use backtrace::Backtrace;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ptr::{self, NonNull};
|
use std::ptr::{self, NonNull};
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
@@ -588,10 +587,7 @@ unsafe fn validate_atomic_addr(
|
|||||||
addr: usize,
|
addr: usize,
|
||||||
) -> Result<(), Trap> {
|
) -> Result<(), Trap> {
|
||||||
if addr > instance.get_memory(memory).current_length {
|
if addr > instance.get_memory(memory).current_length {
|
||||||
return Err(Trap::Wasm {
|
return Err(Trap::wasm(TrapCode::HeapOutOfBounds));
|
||||||
trap_code: TrapCode::HeapOutOfBounds,
|
|
||||||
backtrace: Backtrace::new_unresolved(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
use crate::VMContext;
|
use crate::VMContext;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use backtrace::Backtrace;
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::{Cell, UnsafeCell};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
@@ -143,10 +142,9 @@ impl Trap {
|
|||||||
///
|
///
|
||||||
/// Internally saves a backtrace when constructed.
|
/// Internally saves a backtrace when constructed.
|
||||||
pub fn wasm(trap_code: TrapCode) -> Self {
|
pub fn wasm(trap_code: TrapCode) -> Self {
|
||||||
let backtrace = Backtrace::new_unresolved();
|
|
||||||
Trap::Wasm {
|
Trap::Wasm {
|
||||||
trap_code,
|
trap_code,
|
||||||
backtrace,
|
backtrace: Backtrace::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,8 +152,38 @@ impl Trap {
|
|||||||
///
|
///
|
||||||
/// Internally saves a backtrace when constructed.
|
/// Internally saves a backtrace when constructed.
|
||||||
pub fn oom() -> Self {
|
pub fn oom() -> Self {
|
||||||
let backtrace = Backtrace::new_unresolved();
|
Trap::OOM {
|
||||||
Trap::OOM { backtrace }
|
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) {
|
fn capture_backtrace(&self, pc: *const u8) {
|
||||||
let backtrace = Backtrace::new_unresolved();
|
let backtrace = Backtrace::new();
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.unwind.get())
|
(*self.unwind.get())
|
||||||
.as_mut_ptr()
|
.as_mut_ptr()
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ anyhow = "1.0.19"
|
|||||||
region = "2.2.0"
|
region = "2.2.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
backtrace = "0.3.61"
|
backtrace = { version = "0.3.61", optional = true }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
wat = { version = "1.0.36", optional = true }
|
wat = { version = "1.0.36", optional = true }
|
||||||
serde = { version = "1.0.94", features = ["derive"] }
|
serde = { version = "1.0.94", features = ["derive"] }
|
||||||
@@ -61,6 +61,7 @@ default = [
|
|||||||
'pooling-allocator',
|
'pooling-allocator',
|
||||||
'memory-init-cow',
|
'memory-init-cow',
|
||||||
'vtune',
|
'vtune',
|
||||||
|
'wasm-backtrace',
|
||||||
]
|
]
|
||||||
|
|
||||||
# An on-by-default feature enabling runtime compilation of WebAssembly modules
|
# An on-by-default feature enabling runtime compilation of WebAssembly modules
|
||||||
@@ -108,3 +109,9 @@ posix-signals-on-macos = ["wasmtime-runtime/posix-signals-on-macos"]
|
|||||||
# Enabling this feature has no effect on unsupported platforms or when the
|
# Enabling this feature has no effect on unsupported platforms or when the
|
||||||
# `uffd` feature is enabled.
|
# `uffd` feature is enabled.
|
||||||
memory-init-cow = ["wasmtime-runtime/memory-init-cow"]
|
memory-init-cow = ["wasmtime-runtime/memory-init-cow"]
|
||||||
|
|
||||||
|
# Enables runtime support necessary to capture backtraces of WebAssembly code
|
||||||
|
# that is running.
|
||||||
|
#
|
||||||
|
# This is enabled by default.
|
||||||
|
wasm-backtrace = ["wasmtime-runtime/wasm-backtrace", "backtrace"]
|
||||||
|
|||||||
@@ -141,7 +141,9 @@ impl Config {
|
|||||||
ret.cranelift_debug_verifier(false);
|
ret.cranelift_debug_verifier(false);
|
||||||
ret.cranelift_opt_level(OptLevel::Speed);
|
ret.cranelift_opt_level(OptLevel::Speed);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
ret.wasm_reference_types(true);
|
ret.wasm_reference_types(true);
|
||||||
|
ret.features.reference_types = cfg!(feature = "wasm-backtrace");
|
||||||
ret.wasm_multi_value(true);
|
ret.wasm_multi_value(true);
|
||||||
ret.wasm_bulk_memory(true);
|
ret.wasm_bulk_memory(true);
|
||||||
ret.wasm_simd(true);
|
ret.wasm_simd(true);
|
||||||
@@ -502,10 +504,14 @@ impl Config {
|
|||||||
/// Note that enabling the reference types feature will also enable the bulk
|
/// Note that enabling the reference types feature will also enable the bulk
|
||||||
/// memory feature.
|
/// memory feature.
|
||||||
///
|
///
|
||||||
/// This is `true` by default on x86-64, and `false` by default on other
|
/// This feature is `true` by default. If the `wasm-backtrace` feature is
|
||||||
/// architectures.
|
/// disabled at compile time, however, then this is `false` by default and
|
||||||
|
/// it cannot be turned on since GC currently requires backtraces to work.
|
||||||
|
/// Note that the `wasm-backtrace` feature is on by default, however.
|
||||||
///
|
///
|
||||||
/// [proposal]: https://github.com/webassembly/reference-types
|
/// [proposal]: https://github.com/webassembly/reference-types
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))]
|
||||||
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
|
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
|
||||||
self.features.reference_types = enable;
|
self.features.reference_types = enable;
|
||||||
|
|
||||||
@@ -1272,9 +1278,20 @@ impl Config {
|
|||||||
|
|
||||||
#[cfg(compiler)]
|
#[cfg(compiler)]
|
||||||
fn compiler_builder(strategy: Strategy) -> Result<Box<dyn CompilerBuilder>> {
|
fn compiler_builder(strategy: Strategy) -> Result<Box<dyn CompilerBuilder>> {
|
||||||
match strategy {
|
let mut builder = match strategy {
|
||||||
Strategy::Auto | Strategy::Cranelift => Ok(wasmtime_cranelift::builder()),
|
Strategy::Auto | Strategy::Cranelift => wasmtime_cranelift::builder(),
|
||||||
}
|
};
|
||||||
|
builder
|
||||||
|
.set(
|
||||||
|
"unwind_info",
|
||||||
|
if cfg!(feature = "wasm-backtrace") {
|
||||||
|
"true"
|
||||||
|
} else {
|
||||||
|
"false"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Ok(builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn round_up_to_pages(val: u64) -> u64 {
|
fn round_up_to_pages(val: u64) -> u64 {
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ impl Engine {
|
|||||||
// can affect the way the generated code performs or behaves at
|
// can affect the way the generated code performs or behaves at
|
||||||
// runtime.
|
// runtime.
|
||||||
"avoid_div_traps" => *value == FlagValue::Bool(true),
|
"avoid_div_traps" => *value == FlagValue::Bool(true),
|
||||||
"unwind_info" => *value == FlagValue::Bool(true),
|
"unwind_info" => *value == FlagValue::Bool(cfg!(feature = "wasm-backtrace")),
|
||||||
"libcall_call_conv" => *value == FlagValue::Enum("isa_default".into()),
|
"libcall_call_conv" => *value == FlagValue::Enum("isa_default".into()),
|
||||||
|
|
||||||
// Features wasmtime doesn't use should all be disabled, since
|
// Features wasmtime doesn't use should all be disabled, since
|
||||||
|
|||||||
@@ -290,6 +290,14 @@
|
|||||||
//! run-time via [`Config::memory_init_cow`] (which is also enabled by
|
//! run-time via [`Config::memory_init_cow`] (which is also enabled by
|
||||||
//! default).
|
//! default).
|
||||||
//!
|
//!
|
||||||
|
//! * `wasm-backtrace` - Enabled by default, this feature builds in support to
|
||||||
|
//! generate backtraces at runtime for WebAssembly modules. This means that
|
||||||
|
//! unwinding information is compiled into wasm modules and necessary runtime
|
||||||
|
//! dependencies are enabled as well. If this is turned off then some methods
|
||||||
|
//! to look at trap frames will not be available. Additionally at this time
|
||||||
|
//! disabling this feature means that the reference types feature is always
|
||||||
|
//! disabled as well.
|
||||||
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! In addition to the examples below be sure to check out the [online embedding
|
//! In addition to the examples below be sure to check out the [online embedding
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::module::GlobalModuleRegistry;
|
use crate::module::GlobalModuleRegistry;
|
||||||
use crate::FrameInfo;
|
use crate::FrameInfo;
|
||||||
use backtrace::Backtrace;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::TrapCode as EnvTrapCode;
|
use wasmtime_environ::TrapCode as EnvTrapCode;
|
||||||
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
|
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
|
||||||
|
use wasmtime_runtime::Backtrace;
|
||||||
|
|
||||||
/// A struct representing an aborted instruction execution, with a message
|
/// A struct representing an aborted instruction execution, with a message
|
||||||
/// indicating the cause.
|
/// indicating the cause.
|
||||||
@@ -129,8 +129,10 @@ impl fmt::Display for TrapCode {
|
|||||||
|
|
||||||
struct TrapInner {
|
struct TrapInner {
|
||||||
reason: TrapReason,
|
reason: TrapReason,
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
wasm_trace: Vec<FrameInfo>,
|
wasm_trace: Vec<FrameInfo>,
|
||||||
native_trace: Backtrace,
|
native_trace: Backtrace,
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
hint_wasm_backtrace_details_env: bool,
|
hint_wasm_backtrace_details_env: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,18 +150,14 @@ impl Trap {
|
|||||||
#[cold] // traps are exceptional, this helps move handling off the main path
|
#[cold] // traps are exceptional, this helps move handling off the main path
|
||||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||||
let reason = TrapReason::Message(message.into());
|
let reason = TrapReason::Message(message.into());
|
||||||
Trap::new_with_trace(None, reason, Backtrace::new_unresolved())
|
Trap::new_with_trace(None, reason, Backtrace::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `Trap` representing an explicit program exit with a classic `i32`
|
/// Creates a new `Trap` representing an explicit program exit with a classic `i32`
|
||||||
/// exit status value.
|
/// exit status value.
|
||||||
#[cold] // see Trap::new
|
#[cold] // see Trap::new
|
||||||
pub fn i32_exit(status: i32) -> Self {
|
pub fn i32_exit(status: i32) -> Self {
|
||||||
Trap::new_with_trace(
|
Trap::new_with_trace(None, TrapReason::I32Exit(status), Backtrace::new())
|
||||||
None,
|
|
||||||
TrapReason::I32Exit(status),
|
|
||||||
Backtrace::new_unresolved(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold] // see Trap::new
|
#[cold] // see Trap::new
|
||||||
@@ -212,10 +210,12 @@ impl Trap {
|
|||||||
/// * `native_trace` - this is a captured backtrace from when the trap
|
/// * `native_trace` - this is a captured backtrace from when the trap
|
||||||
/// occurred, and this will iterate over the frames to find frames that
|
/// occurred, and this will iterate over the frames to find frames that
|
||||||
/// lie in wasm jit code.
|
/// lie in wasm jit code.
|
||||||
|
#[cfg_attr(not(feature = "wasm-backtrace"), allow(unused_mut, unused_variables))]
|
||||||
fn new_with_trace(trap_pc: Option<usize>, reason: TrapReason, native_trace: Backtrace) -> Self {
|
fn new_with_trace(trap_pc: Option<usize>, reason: TrapReason, native_trace: Backtrace) -> Self {
|
||||||
let mut wasm_trace = Vec::new();
|
let mut wasm_trace = Vec::<FrameInfo>::new();
|
||||||
let mut hint_wasm_backtrace_details_env = false;
|
let mut hint_wasm_backtrace_details_env = false;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
GlobalModuleRegistry::with(|registry| {
|
GlobalModuleRegistry::with(|registry| {
|
||||||
for frame in native_trace.frames() {
|
for frame in native_trace.frames() {
|
||||||
let pc = frame.ip() as usize;
|
let pc = frame.ip() as usize;
|
||||||
@@ -253,8 +253,10 @@ impl Trap {
|
|||||||
Trap {
|
Trap {
|
||||||
inner: Arc::new(TrapInner {
|
inner: Arc::new(TrapInner {
|
||||||
reason,
|
reason,
|
||||||
wasm_trace,
|
|
||||||
native_trace,
|
native_trace,
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
|
wasm_trace,
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
hint_wasm_backtrace_details_env,
|
hint_wasm_backtrace_details_env,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -281,6 +283,8 @@ impl Trap {
|
|||||||
|
|
||||||
/// Returns a list of function frames in WebAssembly code that led to this
|
/// Returns a list of function frames in WebAssembly code that led to this
|
||||||
/// trap happening.
|
/// trap happening.
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))]
|
||||||
pub fn trace(&self) -> &[FrameInfo] {
|
pub fn trace(&self) -> &[FrameInfo] {
|
||||||
&self.inner.wasm_trace
|
&self.inner.wasm_trace
|
||||||
}
|
}
|
||||||
@@ -297,22 +301,29 @@ impl Trap {
|
|||||||
|
|
||||||
impl fmt::Debug for Trap {
|
impl fmt::Debug for Trap {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Trap")
|
let mut f = f.debug_struct("Trap");
|
||||||
.field("reason", &self.inner.reason)
|
f.field("reason", &self.inner.reason);
|
||||||
.field("wasm_trace", &self.inner.wasm_trace)
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
.field("native_trace", &self.inner.native_trace)
|
{
|
||||||
.finish()
|
f.field("wasm_trace", &self.inner.wasm_trace)
|
||||||
|
.field("native_trace", &self.inner.native_trace);
|
||||||
|
}
|
||||||
|
f.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Trap {
|
impl fmt::Display for Trap {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.inner.reason)?;
|
write!(f, "{}", self.inner.reason)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
|
{
|
||||||
let trace = self.trace();
|
let trace = self.trace();
|
||||||
if trace.is_empty() {
|
if trace.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
writeln!(f, "\nwasm backtrace:")?;
|
writeln!(f, "\nwasm backtrace:")?;
|
||||||
|
|
||||||
for (i, frame) in self.trace().iter().enumerate() {
|
for (i, frame) in self.trace().iter().enumerate() {
|
||||||
let name = frame.module_name().unwrap_or("<unknown>");
|
let name = frame.module_name().unwrap_or("<unknown>");
|
||||||
write!(f, " {:>3}: ", i)?;
|
write!(f, " {:>3}: ", i)?;
|
||||||
@@ -322,7 +333,11 @@ impl fmt::Display for Trap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let write_raw_func_name = |f: &mut fmt::Formatter<'_>| {
|
let write_raw_func_name = |f: &mut fmt::Formatter<'_>| {
|
||||||
demangle_function_name_or_index(f, frame.func_name(), frame.func_index() as usize)
|
demangle_function_name_or_index(
|
||||||
|
f,
|
||||||
|
frame.func_name(),
|
||||||
|
frame.func_index() as usize,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
if frame.symbols().is_empty() {
|
if frame.symbols().is_empty() {
|
||||||
write!(f, "{}!", name)?;
|
write!(f, "{}!", name)?;
|
||||||
@@ -357,6 +372,7 @@ impl fmt::Display for Trap {
|
|||||||
if self.inner.hint_wasm_backtrace_details_env {
|
if self.inner.hint_wasm_backtrace_details_env {
|
||||||
writeln!(f, "note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information")?;
|
writeln!(f, "note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information")?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +404,7 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Trap {
|
|||||||
trap.clone()
|
trap.clone()
|
||||||
} else {
|
} else {
|
||||||
let reason = TrapReason::Error(e.into());
|
let reason = TrapReason::Error(e.into());
|
||||||
Trap::new_with_trace(None, reason, Backtrace::new_unresolved())
|
Trap::new_with_trace(None, reason, Backtrace::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,7 +384,9 @@ impl CommonOptions {
|
|||||||
config.wasm_bulk_memory(enable);
|
config.wasm_bulk_memory(enable);
|
||||||
}
|
}
|
||||||
if let Some(enable) = reference_types {
|
if let Some(enable) = reference_types {
|
||||||
|
#[cfg(feature = "wasm-backtrace")]
|
||||||
config.wasm_reference_types(enable);
|
config.wasm_reference_types(enable);
|
||||||
|
drop(enable); // suppress unused warnings
|
||||||
}
|
}
|
||||||
if let Some(enable) = multi_value {
|
if let Some(enable) = multi_value {
|
||||||
config.wasm_multi_value(enable);
|
config.wasm_multi_value(enable);
|
||||||
|
|||||||
Reference in New Issue
Block a user