diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e834ffd61..cca76a9f12 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -137,7 +137,8 @@ jobs: - 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 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 - run: cargo check --benches -p cranelift-codegen diff --git a/Cargo.toml b/Cargo.toml index 0ba738d8fd..824130ea3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ default = [ "wasi-nn", "pooling-allocator", "memory-init-cow", + "wasm-backtrace", ] jitdump = ["wasmtime/jitdump"] vtune = ["wasmtime/vtune"] @@ -109,6 +110,7 @@ memory-init-cow = ["wasmtime/memory-init-cow"] pooling-allocator = ["wasmtime/pooling-allocator"] all-arch = ["wasmtime/all-arch"] 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 # backend is the default now. diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index e431da2c8b..ed11a4bdc7 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -20,7 +20,7 @@ doctest = false env_logger = "0.8" anyhow = "1.0" 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" } # Optional dependency for the `wat2wasm` API diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index f7f0e2d2b2..5290630162 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -188,9 +188,13 @@ impl wasmtime_environ::Compiler for Compiler { let stack_maps = mach_stack_maps_to_stack_maps(result.buffer.stack_maps()); - let unwind_info = context - .create_unwind_info(isa) - .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; + let unwind_info = if isa.flags().unwind_info() { + context + .create_unwind_info(isa) + .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))? + } else { + None + }; let address_transform = self.get_function_address_map(&context, &input, code_buf.len() as u32, tunables); @@ -566,9 +570,13 @@ impl Compiler { .relocs() .is_empty()); - let unwind_info = context - .create_unwind_info(isa) - .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; + let unwind_info = if isa.flags().unwind_info() { + context + .create_unwind_info(isa) + .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))? + } else { + None + }; Ok(CompiledFunction { body: code_buf, diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 1ef8bb5634..2f31f9b63e 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -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"] diff --git a/crates/runtime/src/externref.rs b/crates/runtime/src/externref.rs index 94c177c47a..29a011b6ef 100644 --- a/crates/runtime/src/externref.rs +++ b/crates/runtime/src/externref.rs @@ -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, root: NonNull, @@ -866,6 +867,7 @@ impl std::ops::DerefMut for DebugOnly { /// /// 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 = 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; diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 5e64f65480..d7d7d0ec92 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -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, diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index 8fe7fa3351..8375d9c9e1 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -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(()) } diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 240a03b512..c3b323832b 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -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() diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index e0981952ac..87d2fbb315 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -25,7 +25,7 @@ anyhow = "1.0.19" region = "2.2.0" libc = "0.2" cfg-if = "1.0" -backtrace = "0.3.61" +backtrace = { version = "0.3.61", optional = true } log = "0.4.8" wat = { version = "1.0.36", optional = true } serde = { version = "1.0.94", features = ["derive"] } @@ -61,6 +61,7 @@ default = [ 'pooling-allocator', 'memory-init-cow', 'vtune', + 'wasm-backtrace', ] # 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 # `uffd` feature is enabled. 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"] diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 6de73cf919..ed01cca812 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -141,7 +141,9 @@ impl Config { ret.cranelift_debug_verifier(false); ret.cranelift_opt_level(OptLevel::Speed); } + #[cfg(feature = "wasm-backtrace")] ret.wasm_reference_types(true); + ret.features.reference_types = cfg!(feature = "wasm-backtrace"); ret.wasm_multi_value(true); ret.wasm_bulk_memory(true); ret.wasm_simd(true); @@ -502,10 +504,14 @@ impl Config { /// Note that enabling the reference types feature will also enable the bulk /// memory feature. /// - /// This is `true` by default on x86-64, and `false` by default on other - /// architectures. + /// This feature is `true` by default. If the `wasm-backtrace` feature is + /// 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 + #[cfg(feature = "wasm-backtrace")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))] pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self { self.features.reference_types = enable; @@ -1272,9 +1278,20 @@ impl Config { #[cfg(compiler)] fn compiler_builder(strategy: Strategy) -> Result> { - match strategy { - Strategy::Auto | Strategy::Cranelift => Ok(wasmtime_cranelift::builder()), - } + let mut builder = match strategy { + 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 { diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index c74c067065..1c6d858384 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -304,7 +304,7 @@ impl Engine { // can affect the way the generated code performs or behaves at // runtime. "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()), // Features wasmtime doesn't use should all be disabled, since diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 26bb8a140e..df110a2938 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -290,6 +290,14 @@ //! run-time via [`Config::memory_init_cow`] (which is also enabled by //! 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 //! //! In addition to the examples below be sure to check out the [online embedding diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 4fa0a19813..1f81f788a4 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -1,10 +1,10 @@ use crate::module::GlobalModuleRegistry; use crate::FrameInfo; -use backtrace::Backtrace; use std::fmt; use std::sync::Arc; use wasmtime_environ::TrapCode as EnvTrapCode; 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 /// indicating the cause. @@ -129,8 +129,10 @@ impl fmt::Display for TrapCode { struct TrapInner { reason: TrapReason, + #[cfg(feature = "wasm-backtrace")] wasm_trace: Vec, native_trace: Backtrace, + #[cfg(feature = "wasm-backtrace")] hint_wasm_backtrace_details_env: bool, } @@ -148,18 +150,14 @@ impl Trap { #[cold] // traps are exceptional, this helps move handling off the main path pub fn new>(message: I) -> Self { 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` /// exit status value. #[cold] // see Trap::new pub fn i32_exit(status: i32) -> Self { - Trap::new_with_trace( - None, - TrapReason::I32Exit(status), - Backtrace::new_unresolved(), - ) + Trap::new_with_trace(None, TrapReason::I32Exit(status), Backtrace::new()) } #[cold] // see Trap::new @@ -212,10 +210,12 @@ impl Trap { /// * `native_trace` - this is a captured backtrace from when the trap /// occurred, and this will iterate over the frames to find frames that /// lie in wasm jit code. + #[cfg_attr(not(feature = "wasm-backtrace"), allow(unused_mut, unused_variables))] fn new_with_trace(trap_pc: Option, reason: TrapReason, native_trace: Backtrace) -> Self { - let mut wasm_trace = Vec::new(); + let mut wasm_trace = Vec::::new(); let mut hint_wasm_backtrace_details_env = false; + #[cfg(feature = "wasm-backtrace")] GlobalModuleRegistry::with(|registry| { for frame in native_trace.frames() { let pc = frame.ip() as usize; @@ -253,8 +253,10 @@ impl Trap { Trap { inner: Arc::new(TrapInner { reason, - wasm_trace, native_trace, + #[cfg(feature = "wasm-backtrace")] + wasm_trace, + #[cfg(feature = "wasm-backtrace")] hint_wasm_backtrace_details_env, }), } @@ -281,6 +283,8 @@ impl Trap { /// Returns a list of function frames in WebAssembly code that led to this /// trap happening. + #[cfg(feature = "wasm-backtrace")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))] pub fn trace(&self) -> &[FrameInfo] { &self.inner.wasm_trace } @@ -297,65 +301,77 @@ impl Trap { impl fmt::Debug for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Trap") - .field("reason", &self.inner.reason) - .field("wasm_trace", &self.inner.wasm_trace) - .field("native_trace", &self.inner.native_trace) - .finish() + let mut f = f.debug_struct("Trap"); + f.field("reason", &self.inner.reason); + #[cfg(feature = "wasm-backtrace")] + { + f.field("wasm_trace", &self.inner.wasm_trace) + .field("native_trace", &self.inner.native_trace); + } + f.finish() } } impl fmt::Display for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inner.reason)?; - let trace = self.trace(); - if trace.is_empty() { - return Ok(()); - } - writeln!(f, "\nwasm backtrace:")?; - for (i, frame) in self.trace().iter().enumerate() { - let name = frame.module_name().unwrap_or(""); - write!(f, " {:>3}: ", i)?; - if let Some(offset) = frame.module_offset() { - write!(f, "{:#6x} - ", offset)?; + #[cfg(feature = "wasm-backtrace")] + { + let trace = self.trace(); + if trace.is_empty() { + return Ok(()); } + writeln!(f, "\nwasm backtrace:")?; - let write_raw_func_name = |f: &mut fmt::Formatter<'_>| { - demangle_function_name_or_index(f, frame.func_name(), frame.func_index() as usize) - }; - if frame.symbols().is_empty() { - write!(f, "{}!", name)?; - write_raw_func_name(f)?; - writeln!(f, "")?; - } else { - for (i, symbol) in frame.symbols().iter().enumerate() { - if i > 0 { - write!(f, " - ")?; - } else { - // ... - } - match symbol.name() { - Some(name) => demangle_function_name(f, name)?, - None if i == 0 => write_raw_func_name(f)?, - None => write!(f, "")?, - } + for (i, frame) in self.trace().iter().enumerate() { + let name = frame.module_name().unwrap_or(""); + write!(f, " {:>3}: ", i)?; + + if let Some(offset) = frame.module_offset() { + write!(f, "{:#6x} - ", offset)?; + } + + let write_raw_func_name = |f: &mut fmt::Formatter<'_>| { + demangle_function_name_or_index( + f, + frame.func_name(), + frame.func_index() as usize, + ) + }; + if frame.symbols().is_empty() { + write!(f, "{}!", name)?; + write_raw_func_name(f)?; writeln!(f, "")?; - if let Some(file) = symbol.file() { - write!(f, " at {}", file)?; - if let Some(line) = symbol.line() { - write!(f, ":{}", line)?; - if let Some(col) = symbol.column() { - write!(f, ":{}", col)?; + } else { + for (i, symbol) in frame.symbols().iter().enumerate() { + if i > 0 { + write!(f, " - ")?; + } else { + // ... + } + match symbol.name() { + Some(name) => demangle_function_name(f, name)?, + None if i == 0 => write_raw_func_name(f)?, + None => write!(f, "")?, + } + writeln!(f, "")?; + if let Some(file) = symbol.file() { + write!(f, " at {}", file)?; + if let Some(line) = symbol.line() { + write!(f, ":{}", line)?; + if let Some(col) = symbol.column() { + write!(f, ":{}", col)?; + } } } + writeln!(f, "")?; } - writeln!(f, "")?; } } - } - 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")?; + 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")?; + } } Ok(()) } @@ -388,7 +404,7 @@ impl From> for Trap { trap.clone() } else { let reason = TrapReason::Error(e.into()); - Trap::new_with_trace(None, reason, Backtrace::new_unresolved()) + Trap::new_with_trace(None, reason, Backtrace::new()) } } } diff --git a/src/lib.rs b/src/lib.rs index 1de3ec4fa2..4da060dd70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -384,7 +384,9 @@ impl CommonOptions { config.wasm_bulk_memory(enable); } if let Some(enable) = reference_types { + #[cfg(feature = "wasm-backtrace")] config.wasm_reference_types(enable); + drop(enable); // suppress unused warnings } if let Some(enable) = multi_value { config.wasm_multi_value(enable);