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:
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -136,9 +136,8 @@ jobs:
|
|||||||
- run: cargo check -p wasmtime --no-default-features --features async
|
- run: cargo check -p wasmtime --no-default-features --features async
|
||||||
- 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 wasm-backtrace
|
|
||||||
- run: cargo check -p wasmtime --no-default-features --features component-model
|
- run: cargo check -p wasmtime --no-default-features --features component-model
|
||||||
- run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache,wasm-backtrace
|
- run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache
|
||||||
- run: cargo check --features component-model
|
- run: cargo check --features component-model
|
||||||
|
|
||||||
# Check that benchmarks of the cranelift project build
|
# Check that benchmarks of the cranelift project build
|
||||||
|
|||||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1871,9 +1871,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.10.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ 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"]
|
||||||
@@ -108,7 +107,6 @@ memory-init-cow = ["wasmtime/memory-init-cow", "wasmtime-cli-flags/memory-init-c
|
|||||||
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
|
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/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", "wasmtime-cli-flags/wasm-backtrace"]
|
|
||||||
component-model = ["wasmtime/component-model", "wasmtime-wast/component-model", "wasmtime-cli-flags/component-model"]
|
component-model = ["wasmtime/component-model", "wasmtime-wast/component-model", "wasmtime-cli-flags/component-model"]
|
||||||
|
|
||||||
# Stub feature that does nothing, for Cargo-features compatibility: the new
|
# Stub feature that does nothing, for Cargo-features compatibility: the new
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ doctest = false
|
|||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
once_cell = "1.3"
|
once_cell = "1.3"
|
||||||
wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift', 'wasm-backtrace'] }
|
wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift'] }
|
||||||
wasmtime-c-api-macros = { path = "macros" }
|
wasmtime-c-api-macros = { path = "macros" }
|
||||||
|
|
||||||
# Optional dependency for the `wat2wasm` API
|
# Optional dependency for the `wat2wasm` API
|
||||||
|
|||||||
@@ -64,7 +64,13 @@ pub extern "C" fn wasm_trap_message(trap: &wasm_trap_t, out: &mut wasm_message_t
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option<Box<wasm_frame_t>> {
|
pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option<Box<wasm_frame_t>> {
|
||||||
if raw.trap.trace().len() > 0 {
|
if raw
|
||||||
|
.trap
|
||||||
|
.trace()
|
||||||
|
.expect("backtraces are always enabled")
|
||||||
|
.len()
|
||||||
|
> 0
|
||||||
|
{
|
||||||
Some(Box::new(wasm_frame_t {
|
Some(Box::new(wasm_frame_t {
|
||||||
trap: raw.trap.clone(),
|
trap: raw.trap.clone(),
|
||||||
idx: 0,
|
idx: 0,
|
||||||
@@ -78,7 +84,11 @@ pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option<Box<wasm_frame_t
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wasm_trap_trace(raw: &wasm_trap_t, out: &mut wasm_frame_vec_t) {
|
pub extern "C" fn wasm_trap_trace(raw: &wasm_trap_t, out: &mut wasm_frame_vec_t) {
|
||||||
let vec = (0..raw.trap.trace().len())
|
let vec = (0..raw
|
||||||
|
.trap
|
||||||
|
.trace()
|
||||||
|
.expect("backtraces are always enabled")
|
||||||
|
.len())
|
||||||
.map(|idx| {
|
.map(|idx| {
|
||||||
Some(Box::new(wasm_frame_t {
|
Some(Box::new(wasm_frame_t {
|
||||||
trap: raw.trap.clone(),
|
trap: raw.trap.clone(),
|
||||||
@@ -128,7 +138,7 @@ pub extern "C" fn wasmtime_trap_exit_status(raw: &wasm_trap_t, status: &mut i32)
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t) -> u32 {
|
pub extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t) -> u32 {
|
||||||
frame.trap.trace()[frame.idx].func_index()
|
frame.trap.trace().expect("backtraces are always enabled")[frame.idx].func_index()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -136,7 +146,7 @@ pub extern "C" fn wasmtime_frame_func_name(frame: &wasm_frame_t) -> Option<&wasm
|
|||||||
frame
|
frame
|
||||||
.func_name
|
.func_name
|
||||||
.get_or_init(|| {
|
.get_or_init(|| {
|
||||||
frame.trap.trace()[frame.idx]
|
frame.trap.trace().expect("backtraces are always enabled")[frame.idx]
|
||||||
.func_name()
|
.func_name()
|
||||||
.map(|s| wasm_name_t::from(s.to_string().into_bytes()))
|
.map(|s| wasm_name_t::from(s.to_string().into_bytes()))
|
||||||
})
|
})
|
||||||
@@ -148,7 +158,7 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa
|
|||||||
frame
|
frame
|
||||||
.module_name
|
.module_name
|
||||||
.get_or_init(|| {
|
.get_or_init(|| {
|
||||||
frame.trap.trace()[frame.idx]
|
frame.trap.trace().expect("backtraces are always enabled")[frame.idx]
|
||||||
.module_name()
|
.module_name()
|
||||||
.map(|s| wasm_name_t::from(s.to_string().into_bytes()))
|
.map(|s| wasm_name_t::from(s.to_string().into_bytes()))
|
||||||
})
|
})
|
||||||
@@ -157,7 +167,7 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize {
|
pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize {
|
||||||
frame.trap.trace()[frame.idx]
|
frame.trap.trace().expect("backtraces are always enabled")[frame.idx]
|
||||||
.func_offset()
|
.func_offset()
|
||||||
.unwrap_or(usize::MAX)
|
.unwrap_or(usize::MAX)
|
||||||
}
|
}
|
||||||
@@ -169,7 +179,7 @@ pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t) -> *mut wasm_i
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize {
|
pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize {
|
||||||
frame.trap.trace()[frame.idx]
|
frame.trap.trace().expect("backtraces are always enabled")[frame.idx]
|
||||||
.module_offset()
|
.module_offset()
|
||||||
.unwrap_or(usize::MAX)
|
.unwrap_or(usize::MAX)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,5 +25,4 @@ default = [
|
|||||||
]
|
]
|
||||||
pooling-allocator = []
|
pooling-allocator = []
|
||||||
memory-init-cow = []
|
memory-init-cow = []
|
||||||
wasm-backtrace = []
|
|
||||||
component-model = []
|
component-model = []
|
||||||
|
|||||||
@@ -346,9 +346,7 @@ 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);
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ pub struct WasmtimeConfig {
|
|||||||
codegen: CodegenSettings,
|
codegen: CodegenSettings,
|
||||||
padding_between_functions: Option<u16>,
|
padding_between_functions: Option<u16>,
|
||||||
generate_address_map: bool,
|
generate_address_map: bool,
|
||||||
|
wasm_backtraces: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for linear memories in Wasmtime.
|
/// Configuration for linear memories in Wasmtime.
|
||||||
@@ -387,6 +388,7 @@ impl Config {
|
|||||||
.wasm_multi_memory(self.module_config.config.max_memories > 1)
|
.wasm_multi_memory(self.module_config.config.max_memories > 1)
|
||||||
.wasm_simd(self.module_config.config.simd_enabled)
|
.wasm_simd(self.module_config.config.simd_enabled)
|
||||||
.wasm_memory64(self.module_config.config.memory64_enabled)
|
.wasm_memory64(self.module_config.config.memory64_enabled)
|
||||||
|
.wasm_backtrace(self.wasmtime.wasm_backtraces)
|
||||||
.cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans)
|
.cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans)
|
||||||
.cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime())
|
.cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime())
|
||||||
.consume_fuel(self.wasmtime.consume_fuel)
|
.consume_fuel(self.wasmtime.consume_fuel)
|
||||||
|
|||||||
@@ -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 = { version = "0.3.61", optional = true }
|
backtrace = { version = "0.3.61" }
|
||||||
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 }
|
||||||
@@ -44,7 +44,6 @@ maintenance = { status = "actively-developed" }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
memory-init-cow = ['memfd']
|
memory-init-cow = ['memfd']
|
||||||
wasm-backtrace = ["backtrace"]
|
|
||||||
|
|
||||||
async = ["wasmtime-fiber"]
|
async = ["wasmtime-fiber"]
|
||||||
|
|
||||||
|
|||||||
@@ -704,7 +704,6 @@ 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>,
|
||||||
@@ -867,7 +866,6 @@ 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,
|
||||||
@@ -895,7 +893,6 @@ 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
|
||||||
@@ -937,7 +934,6 @@ 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;
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ pub unsafe extern "C" fn memory_atomic_notify(
|
|||||||
// just to be sure.
|
// just to be sure.
|
||||||
let addr_to_check = addr.checked_add(4).unwrap();
|
let addr_to_check = addr.checked_add(4).unwrap();
|
||||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
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",
|
"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
|
// but we still double-check
|
||||||
let addr_to_check = addr.checked_add(4).unwrap();
|
let addr_to_check = addr.checked_add(4).unwrap();
|
||||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
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",
|
"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
|
// but we still double-check
|
||||||
let addr_to_check = addr.checked_add(8).unwrap();
|
let addr_to_check = addr.checked_add(8).unwrap();
|
||||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
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",
|
"unimplemented: wasm atomics (fn memory_atomic_wait64) unsupported",
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use std::sync::Once;
|
|||||||
use wasmtime_environ::TrapCode;
|
use wasmtime_environ::TrapCode;
|
||||||
|
|
||||||
pub use self::tls::{tls_eager_initialize, TlsRestore};
|
pub use self::tls::{tls_eager_initialize, TlsRestore};
|
||||||
|
pub use backtrace::Backtrace;
|
||||||
|
|
||||||
#[link(name = "wasmtime-helpers")]
|
#[link(name = "wasmtime-helpers")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -112,14 +113,19 @@ pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Trap {
|
pub enum Trap {
|
||||||
/// A user-raised trap through `raise_user_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
|
/// A trap raised from jit code
|
||||||
Jit {
|
Jit {
|
||||||
/// The program counter in JIT code where this trap happened.
|
/// The program counter in JIT code where this trap happened.
|
||||||
pc: usize,
|
pc: usize,
|
||||||
/// Native stack backtrace at the time the trap occurred
|
/// Native stack backtrace at the time the trap occurred
|
||||||
backtrace: Backtrace,
|
backtrace: Option<Backtrace>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A trap raised from a wasm libcall
|
/// A trap raised from a wasm libcall
|
||||||
@@ -127,63 +133,53 @@ pub enum Trap {
|
|||||||
/// Code of the trap.
|
/// Code of the trap.
|
||||||
trap_code: TrapCode,
|
trap_code: TrapCode,
|
||||||
/// Native stack backtrace at the time the trap occurred
|
/// 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.
|
/// A trap indicating that the runtime was unable to allocate sufficient memory.
|
||||||
OOM {
|
OOM {
|
||||||
/// Native stack backtrace at the time the OOM occurred
|
/// Native stack backtrace at the time the OOM occurred
|
||||||
backtrace: Backtrace,
|
backtrace: Option<Backtrace>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Trap {
|
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 {
|
pub fn wasm(trap_code: TrapCode) -> Self {
|
||||||
Trap::Wasm {
|
Trap::Wasm {
|
||||||
trap_code,
|
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 {
|
pub fn oom() -> Self {
|
||||||
Trap::OOM {
|
Trap::OOM { backtrace: None }
|
||||||
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
|
fn insert_backtrace(&mut self, bt: Backtrace) {
|
||||||
/// this is conditionally defined and not present when `wasm-backtrace` is
|
match self {
|
||||||
/// not present.
|
Trap::User { backtrace, .. } => *backtrace = Some(bt),
|
||||||
#[cfg(feature = "wasm-backtrace")]
|
Trap::Jit { backtrace, .. } => *backtrace = Some(bt),
|
||||||
pub fn frames(&self) -> &[backtrace::BacktraceFrame] {
|
Trap::Wasm { backtrace, .. } => *backtrace = Some(bt),
|
||||||
self.trace.frames()
|
Trap::OOM { backtrace, .. } => *backtrace = Some(bt),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,13 +189,14 @@ impl Backtrace {
|
|||||||
/// Highly unsafe since `closure` won't have any dtors run.
|
/// Highly unsafe since `closure` won't have any dtors run.
|
||||||
pub unsafe fn catch_traps<'a, F>(
|
pub unsafe fn catch_traps<'a, F>(
|
||||||
signal_handler: Option<*const SignalHandler<'static>>,
|
signal_handler: Option<*const SignalHandler<'static>>,
|
||||||
|
capture_backtrace: bool,
|
||||||
callee: *mut VMContext,
|
callee: *mut VMContext,
|
||||||
mut closure: F,
|
mut closure: F,
|
||||||
) -> Result<(), Box<Trap>>
|
) -> Result<(), Box<Trap>>
|
||||||
where
|
where
|
||||||
F: FnMut(*mut VMContext),
|
F: FnMut(*mut VMContext),
|
||||||
{
|
{
|
||||||
return CallThreadState::new(signal_handler).with(|cx| {
|
return CallThreadState::new(signal_handler, capture_backtrace).with(|cx| {
|
||||||
wasmtime_setjmp(
|
wasmtime_setjmp(
|
||||||
cx.jmp_buf.as_ptr(),
|
cx.jmp_buf.as_ptr(),
|
||||||
call_closure::<F>,
|
call_closure::<F>,
|
||||||
@@ -219,29 +216,34 @@ where
|
|||||||
/// Temporary state stored on the stack which is registered in the `tls` module
|
/// Temporary state stored on the stack which is registered in the `tls` module
|
||||||
/// below for calls into wasm.
|
/// below for calls into wasm.
|
||||||
pub struct CallThreadState {
|
pub struct CallThreadState {
|
||||||
unwind: UnsafeCell<MaybeUninit<UnwindReason>>,
|
unwind: UnsafeCell<MaybeUninit<(UnwindReason, Option<Backtrace>)>>,
|
||||||
jmp_buf: Cell<*const u8>,
|
jmp_buf: Cell<*const u8>,
|
||||||
handling_trap: Cell<bool>,
|
handling_trap: Cell<bool>,
|
||||||
signal_handler: Option<*const SignalHandler<'static>>,
|
signal_handler: Option<*const SignalHandler<'static>>,
|
||||||
prev: Cell<tls::Ptr>,
|
prev: Cell<tls::Ptr>,
|
||||||
|
capture_backtrace: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UnwindReason {
|
enum UnwindReason {
|
||||||
Panic(Box<dyn Any + Send>),
|
Panic(Box<dyn Any + Send>),
|
||||||
UserTrap(Error),
|
UserTrap(Error),
|
||||||
LibTrap(Trap),
|
LibTrap(Trap),
|
||||||
JitTrap { backtrace: Backtrace, pc: usize },
|
JitTrap { pc: usize }, // Removed a backtrace here
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallThreadState {
|
impl CallThreadState {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn new(signal_handler: Option<*const SignalHandler<'static>>) -> CallThreadState {
|
fn new(
|
||||||
|
signal_handler: Option<*const SignalHandler<'static>>,
|
||||||
|
capture_backtrace: bool,
|
||||||
|
) -> CallThreadState {
|
||||||
CallThreadState {
|
CallThreadState {
|
||||||
unwind: UnsafeCell::new(MaybeUninit::uninit()),
|
unwind: UnsafeCell::new(MaybeUninit::uninit()),
|
||||||
jmp_buf: Cell::new(ptr::null()),
|
jmp_buf: Cell::new(ptr::null()),
|
||||||
handling_trap: Cell::new(false),
|
handling_trap: Cell::new(false),
|
||||||
signal_handler,
|
signal_handler,
|
||||||
prev: Cell::new(ptr::null()),
|
prev: Cell::new(ptr::null()),
|
||||||
|
capture_backtrace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,16 +259,26 @@ impl CallThreadState {
|
|||||||
#[cold]
|
#[cold]
|
||||||
unsafe fn read_trap(&self) -> Box<Trap> {
|
unsafe fn read_trap(&self) -> Box<Trap> {
|
||||||
Box::new(match (*self.unwind.get()).as_ptr().read() {
|
Box::new(match (*self.unwind.get()).as_ptr().read() {
|
||||||
UnwindReason::UserTrap(data) => Trap::User(data),
|
(UnwindReason::UserTrap(error), backtrace) => Trap::User { error, backtrace },
|
||||||
UnwindReason::LibTrap(trap) => trap,
|
(UnwindReason::LibTrap(mut trap), backtrace) => {
|
||||||
UnwindReason::JitTrap { backtrace, pc } => Trap::Jit { pc, backtrace },
|
if let Some(backtrace) = backtrace {
|
||||||
UnwindReason::Panic(panic) => std::panic::resume_unwind(panic),
|
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) -> ! {
|
fn unwind_with(&self, reason: UnwindReason) -> ! {
|
||||||
|
let backtrace = if self.capture_backtrace {
|
||||||
|
Some(Backtrace::new())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.unwind.get()).as_mut_ptr().write(reason);
|
(*self.unwind.get()).as_mut_ptr().write((reason, backtrace));
|
||||||
wasmtime_longjmp(self.jmp_buf.get());
|
wasmtime_longjmp(self.jmp_buf.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,14 +339,15 @@ impl CallThreadState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn capture_backtrace(&self, pc: *const u8) {
|
fn capture_backtrace(&self, pc: *const u8) {
|
||||||
let backtrace = Backtrace::new();
|
let backtrace = if self.capture_backtrace {
|
||||||
|
Some(Backtrace::new())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.unwind.get())
|
(*self.unwind.get())
|
||||||
.as_mut_ptr()
|
.as_mut_ptr()
|
||||||
.write(UnwindReason::JitTrap {
|
.write((UnwindReason::JitTrap { pc: pc as usize }, backtrace));
|
||||||
backtrace,
|
|
||||||
pc: pc as usize,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = { version = "0.3.61", optional = true }
|
backtrace = { version = "0.3.61" }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
wat = { version = "1.0.43", optional = true }
|
wat = { version = "1.0.43", optional = true }
|
||||||
serde = { version = "1.0.94", features = ["derive"] }
|
serde = { version = "1.0.94", features = ["derive"] }
|
||||||
@@ -37,7 +37,7 @@ lazy_static = "1.4"
|
|||||||
rayon = { version = "1.0", optional = true }
|
rayon = { version = "1.0", optional = true }
|
||||||
object = { version = "0.28", default-features = false, features = ['read_core', 'elf'] }
|
object = { version = "0.28", default-features = false, features = ['read_core', 'elf'] }
|
||||||
async-trait = { version = "0.1.51", optional = true }
|
async-trait = { version = "0.1.51", optional = true }
|
||||||
once_cell = "1.9"
|
once_cell = "1.12"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = "0.3.7"
|
winapi = "0.3.7"
|
||||||
@@ -61,7 +61,6 @@ 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
|
||||||
@@ -106,12 +105,6 @@ posix-signals-on-macos = ["wasmtime-runtime/posix-signals-on-macos"]
|
|||||||
# Enabling this feature has no effect on unsupported platforms.
|
# Enabling this feature has no effect on unsupported platforms.
|
||||||
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"]
|
|
||||||
|
|
||||||
# Enables in-progress support for the component model. Note that this feature is
|
# Enables in-progress support for the component model. Note that this feature is
|
||||||
# in-progress, buggy, and incomplete. This is primarily here for internal
|
# in-progress, buggy, and incomplete. This is primarily here for internal
|
||||||
# testing purposes.
|
# testing purposes.
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ pub struct Config {
|
|||||||
pub(crate) allocation_strategy: InstanceAllocationStrategy,
|
pub(crate) allocation_strategy: InstanceAllocationStrategy,
|
||||||
pub(crate) max_wasm_stack: usize,
|
pub(crate) max_wasm_stack: usize,
|
||||||
pub(crate) features: WasmFeatures,
|
pub(crate) features: WasmFeatures,
|
||||||
|
pub(crate) wasm_backtrace: bool,
|
||||||
pub(crate) wasm_backtrace_details_env_used: bool,
|
pub(crate) wasm_backtrace_details_env_used: bool,
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub(crate) async_stack_size: usize,
|
pub(crate) async_stack_size: usize,
|
||||||
@@ -124,6 +125,7 @@ impl Config {
|
|||||||
// 1` forces this), or at least it passed when this change was
|
// 1` forces this), or at least it passed when this change was
|
||||||
// committed.
|
// committed.
|
||||||
max_wasm_stack: 512 * 1024,
|
max_wasm_stack: 512 * 1024,
|
||||||
|
wasm_backtrace: true,
|
||||||
wasm_backtrace_details_env_used: false,
|
wasm_backtrace_details_env_used: false,
|
||||||
features: WasmFeatures::default(),
|
features: WasmFeatures::default(),
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
@@ -140,9 +142,7 @@ 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);
|
||||||
@@ -279,6 +279,35 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures whether backtraces exist in a `Trap`.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// When disabled, wasm backtrace details are ignored, and [`crate::Trap::trace()`]
|
||||||
|
/// will always return `None`.
|
||||||
|
|
||||||
|
pub fn wasm_backtrace(&mut self, enable: bool) -> &mut Self {
|
||||||
|
self.wasm_backtrace = enable;
|
||||||
|
#[cfg(compiler)]
|
||||||
|
{
|
||||||
|
// unwind_info must be enabled when either backtraces or reference types are enabled:
|
||||||
|
self.compiler
|
||||||
|
.set(
|
||||||
|
"unwind_info",
|
||||||
|
if enable || self.features.reference_types {
|
||||||
|
"true"
|
||||||
|
} else {
|
||||||
|
"false"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Configures whether backtraces in `Trap` will parse debug info in the wasm file to
|
/// Configures whether backtraces in `Trap` will parse debug info in the wasm file to
|
||||||
/// have filename/line number information.
|
/// have filename/line number information.
|
||||||
///
|
///
|
||||||
@@ -508,14 +537,9 @@ 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 feature is `true` by default. If the `wasm-backtrace` feature is
|
/// This feature is `true` by default.
|
||||||
/// 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;
|
||||||
|
|
||||||
@@ -524,6 +548,17 @@ impl Config {
|
|||||||
self.compiler
|
self.compiler
|
||||||
.set("enable_safepoints", if enable { "true" } else { "false" })
|
.set("enable_safepoints", if enable { "true" } else { "false" })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
// unwind_info must be enabled when either backtraces or reference types are enabled:
|
||||||
|
self.compiler
|
||||||
|
.set(
|
||||||
|
"unwind_info",
|
||||||
|
if enable || self.wasm_backtrace {
|
||||||
|
"true"
|
||||||
|
} else {
|
||||||
|
"false"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The reference types proposal depends on the bulk memory proposal.
|
// The reference types proposal depends on the bulk memory proposal.
|
||||||
@@ -1288,20 +1323,9 @@ impl Config {
|
|||||||
|
|
||||||
#[cfg(compiler)]
|
#[cfg(compiler)]
|
||||||
fn compiler_builder(strategy: Strategy) -> Result<Box<dyn CompilerBuilder>> {
|
fn compiler_builder(strategy: Strategy) -> Result<Box<dyn CompilerBuilder>> {
|
||||||
let mut builder = match strategy {
|
match strategy {
|
||||||
Strategy::Auto | Strategy::Cranelift => wasmtime_cranelift::builder(),
|
Strategy::Auto | Strategy::Cranelift => Ok(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 {
|
||||||
@@ -1331,6 +1355,7 @@ impl Clone for Config {
|
|||||||
mem_creator: self.mem_creator.clone(),
|
mem_creator: self.mem_creator.clone(),
|
||||||
allocation_strategy: self.allocation_strategy.clone(),
|
allocation_strategy: self.allocation_strategy.clone(),
|
||||||
max_wasm_stack: self.max_wasm_stack,
|
max_wasm_stack: self.max_wasm_stack,
|
||||||
|
wasm_backtrace: self.wasm_backtrace,
|
||||||
wasm_backtrace_details_env_used: self.wasm_backtrace_details_env_used,
|
wasm_backtrace_details_env_used: self.wasm_backtrace_details_env_used,
|
||||||
async_support: self.async_support,
|
async_support: self.async_support,
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
|
|||||||
@@ -303,7 +303,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(cfg!(feature = "wasm-backtrace")),
|
"unwind_info" => *value == FlagValue::Bool(true),
|
||||||
"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
|
||||||
|
|||||||
@@ -1235,6 +1235,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
|
|||||||
}
|
}
|
||||||
let result = wasmtime_runtime::catch_traps(
|
let result = wasmtime_runtime::catch_traps(
|
||||||
store.0.signal_handler(),
|
store.0.signal_handler(),
|
||||||
|
store.0.engine().config().wasm_backtrace,
|
||||||
store.0.default_callee(),
|
store.0.default_callee(),
|
||||||
closure,
|
closure,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -290,14 +290,6 @@
|
|||||||
//! 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
|
||||||
|
|||||||
@@ -1914,11 +1914,7 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
|||||||
fn new_epoch(&mut self) -> Result<u64, anyhow::Error> {
|
fn new_epoch(&mut self) -> Result<u64, anyhow::Error> {
|
||||||
return match &mut self.epoch_deadline_behavior {
|
return match &mut self.epoch_deadline_behavior {
|
||||||
EpochDeadline::Trap => {
|
EpochDeadline::Trap => {
|
||||||
let trap = Trap::new_wasm(
|
let trap = Trap::new_wasm(wasmtime_environ::TrapCode::Interrupt, None);
|
||||||
None,
|
|
||||||
wasmtime_environ::TrapCode::Interrupt,
|
|
||||||
wasmtime_runtime::Backtrace::new(),
|
|
||||||
);
|
|
||||||
Err(anyhow::Error::from(trap))
|
Err(anyhow::Error::from(trap))
|
||||||
}
|
}
|
||||||
EpochDeadline::Callback(callback) => {
|
EpochDeadline::Callback(callback) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::module::GlobalModuleRegistry;
|
use crate::module::GlobalModuleRegistry;
|
||||||
use crate::FrameInfo;
|
use crate::FrameInfo;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
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;
|
||||||
@@ -127,95 +128,18 @@ impl fmt::Display for TrapCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrapInner {
|
#[derive(Debug)]
|
||||||
reason: TrapReason,
|
pub(crate) struct TrapBacktrace {
|
||||||
#[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,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
impl TrapBacktrace {
|
||||||
(t, t)
|
pub fn new(native_trace: Backtrace, trap_pc: Option<usize>) -> Self {
|
||||||
}
|
|
||||||
|
|
||||||
impl Trap {
|
|
||||||
/// Creates a new `Trap` with `message`.
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// let trap = wasmtime::Trap::new("unexpected error");
|
|
||||||
/// assert!(trap.to_string().contains("unexpected error"));
|
|
||||||
/// ```
|
|
||||||
#[cold] // traps are exceptional, this helps move handling off the main path
|
|
||||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
|
||||||
let reason = TrapReason::Message(message.into());
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold] // see Trap::new
|
|
||||||
pub(crate) fn from_runtime_box(runtime_trap: Box<wasmtime_runtime::Trap>) -> Self {
|
|
||||||
Self::from_runtime(*runtime_trap)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold] // see Trap::new
|
|
||||||
pub(crate) fn from_runtime(runtime_trap: wasmtime_runtime::Trap) -> Self {
|
|
||||||
match runtime_trap {
|
|
||||||
wasmtime_runtime::Trap::User(error) => Trap::from(error),
|
|
||||||
wasmtime_runtime::Trap::Jit { pc, backtrace } => {
|
|
||||||
let code = GlobalModuleRegistry::with(|modules| {
|
|
||||||
modules
|
|
||||||
.lookup_trap_code(pc)
|
|
||||||
.unwrap_or(EnvTrapCode::StackOverflow)
|
|
||||||
});
|
|
||||||
Trap::new_wasm(Some(pc), code, backtrace)
|
|
||||||
}
|
|
||||||
wasmtime_runtime::Trap::Wasm {
|
|
||||||
trap_code,
|
|
||||||
backtrace,
|
|
||||||
} => Trap::new_wasm(None, trap_code, backtrace),
|
|
||||||
wasmtime_runtime::Trap::OOM { backtrace } => {
|
|
||||||
let reason = TrapReason::Message("out of memory".to_string());
|
|
||||||
Trap::new_with_trace(None, reason, backtrace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold] // see Trap::new
|
|
||||||
pub(crate) fn new_wasm(
|
|
||||||
trap_pc: Option<usize>,
|
|
||||||
code: EnvTrapCode,
|
|
||||||
backtrace: Backtrace,
|
|
||||||
) -> Self {
|
|
||||||
let code = TrapCode::from_non_user(code);
|
|
||||||
Trap::new_with_trace(trap_pc, TrapReason::InstructionTrap(code), backtrace)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new `Trap`.
|
|
||||||
///
|
|
||||||
/// * `trap_pc` - this is the precise program counter, if available, that
|
|
||||||
/// wasm trapped at. This is used when learning about the wasm stack trace
|
|
||||||
/// to ensure we assign the correct source to every frame.
|
|
||||||
///
|
|
||||||
/// * `reason` - this is the wasmtime-internal reason for why this trap is
|
|
||||||
/// being created.
|
|
||||||
///
|
|
||||||
/// * `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<usize>, reason: TrapReason, native_trace: Backtrace) -> Self {
|
|
||||||
let mut wasm_trace = Vec::<FrameInfo>::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;
|
||||||
@@ -250,15 +174,103 @@ impl Trap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Trap {
|
Self {
|
||||||
inner: Arc::new(TrapInner {
|
|
||||||
reason,
|
|
||||||
native_trace,
|
|
||||||
#[cfg(feature = "wasm-backtrace")]
|
|
||||||
wasm_trace,
|
wasm_trace,
|
||||||
#[cfg(feature = "wasm-backtrace")]
|
native_trace,
|
||||||
hint_wasm_backtrace_details_env,
|
hint_wasm_backtrace_details_env,
|
||||||
}),
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TrapInner {
|
||||||
|
reason: TrapReason,
|
||||||
|
backtrace: OnceCell<TrapBacktrace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||||
|
(t, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Trap {
|
||||||
|
/// Creates a new `Trap` with `message`.
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let trap = wasmtime::Trap::new("unexpected error");
|
||||||
|
/// assert!(trap.to_string().contains("unexpected error"));
|
||||||
|
/// ```
|
||||||
|
#[cold] // traps are exceptional, this helps move handling off the main path
|
||||||
|
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||||
|
let reason = TrapReason::Message(message.into());
|
||||||
|
Trap::new_with_trace(reason, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(TrapReason::I32Exit(status), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold] // see Trap::new
|
||||||
|
pub(crate) fn from_runtime_box(runtime_trap: Box<wasmtime_runtime::Trap>) -> Self {
|
||||||
|
Self::from_runtime(*runtime_trap)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold] // see Trap::new
|
||||||
|
pub(crate) fn from_runtime(runtime_trap: wasmtime_runtime::Trap) -> Self {
|
||||||
|
match runtime_trap {
|
||||||
|
wasmtime_runtime::Trap::User { error, backtrace } => {
|
||||||
|
let trap = Trap::from(error);
|
||||||
|
if let Some(backtrace) = backtrace {
|
||||||
|
trap.record_backtrace(TrapBacktrace::new(backtrace, None));
|
||||||
|
}
|
||||||
|
trap
|
||||||
|
}
|
||||||
|
wasmtime_runtime::Trap::Jit { pc, backtrace } => {
|
||||||
|
let code = GlobalModuleRegistry::with(|modules| {
|
||||||
|
modules
|
||||||
|
.lookup_trap_code(pc)
|
||||||
|
.unwrap_or(EnvTrapCode::StackOverflow)
|
||||||
|
});
|
||||||
|
let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, Some(pc)));
|
||||||
|
Trap::new_wasm(code, backtrace)
|
||||||
|
}
|
||||||
|
wasmtime_runtime::Trap::Wasm {
|
||||||
|
trap_code,
|
||||||
|
backtrace,
|
||||||
|
} => {
|
||||||
|
let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, None));
|
||||||
|
Trap::new_wasm(trap_code, backtrace)
|
||||||
|
}
|
||||||
|
wasmtime_runtime::Trap::OOM { backtrace } => {
|
||||||
|
let reason = TrapReason::Message("out of memory".to_string());
|
||||||
|
let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, None));
|
||||||
|
Trap::new_with_trace(reason, backtrace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold] // see Trap::new
|
||||||
|
pub(crate) fn new_wasm(code: EnvTrapCode, backtrace: Option<TrapBacktrace>) -> Self {
|
||||||
|
let code = TrapCode::from_non_user(code);
|
||||||
|
Trap::new_with_trace(TrapReason::InstructionTrap(code), backtrace)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Trap`.
|
||||||
|
/// * `reason` - this is the wasmtime-internal reason for why this trap is
|
||||||
|
/// being created.
|
||||||
|
///
|
||||||
|
/// * `backtrace` - this is a captured backtrace from when the trap
|
||||||
|
/// occurred. Contains the native backtrace, and the backtrace of
|
||||||
|
/// WebAssembly frames.
|
||||||
|
fn new_with_trace(reason: TrapReason, backtrace: Option<TrapBacktrace>) -> Self {
|
||||||
|
let backtrace = if let Some(bt) = backtrace {
|
||||||
|
OnceCell::with_value(bt)
|
||||||
|
} else {
|
||||||
|
OnceCell::new()
|
||||||
|
};
|
||||||
|
Trap {
|
||||||
|
inner: Arc::new(TrapInner { reason, backtrace }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,10 +295,12 @@ 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")]
|
pub fn trace(&self) -> Option<&[FrameInfo]> {
|
||||||
#[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))]
|
self.inner
|
||||||
pub fn trace(&self) -> &[FrameInfo] {
|
.backtrace
|
||||||
&self.inner.wasm_trace
|
.get()
|
||||||
|
.as_ref()
|
||||||
|
.map(|bt| bt.wasm_trace.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Code of a trap that happened while executing a WASM instruction.
|
/// Code of a trap that happened while executing a WASM instruction.
|
||||||
@@ -297,16 +311,26 @@ impl Trap {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_backtrace(&self, backtrace: TrapBacktrace) {
|
||||||
|
// When a trap is created on top of the wasm stack, the trampoline will
|
||||||
|
// re-raise it via
|
||||||
|
// `wasmtime_runtime::raise_user_trap(trap.into::<Box<dyn Error>>())`
|
||||||
|
// after panic::catch_unwind. We don't want to overwrite the first
|
||||||
|
// backtrace recorded, as it is most precise.
|
||||||
|
// FIXME: make sure backtraces are only created once per trap! they are
|
||||||
|
// actually kinda expensive to create.
|
||||||
|
let _ = self.inner.backtrace.try_insert(backtrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
let mut f = f.debug_struct("Trap");
|
let mut f = f.debug_struct("Trap");
|
||||||
f.field("reason", &self.inner.reason);
|
f.field("reason", &self.inner.reason);
|
||||||
#[cfg(feature = "wasm-backtrace")]
|
if let Some(backtrace) = self.inner.backtrace.get() {
|
||||||
{
|
f.field("wasm_trace", &backtrace.wasm_trace)
|
||||||
f.field("wasm_trace", &self.inner.wasm_trace)
|
.field("native_trace", &backtrace.native_trace);
|
||||||
.field("native_trace", &self.inner.native_trace);
|
|
||||||
}
|
}
|
||||||
f.finish()
|
f.finish()
|
||||||
}
|
}
|
||||||
@@ -316,15 +340,13 @@ 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")]
|
if let Some(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 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)?;
|
||||||
|
|
||||||
@@ -369,7 +391,13 @@ impl fmt::Display for Trap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.inner.hint_wasm_backtrace_details_env {
|
if self
|
||||||
|
.inner
|
||||||
|
.backtrace
|
||||||
|
.get()
|
||||||
|
.map(|t| t.hint_wasm_backtrace_details_env)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
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")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +432,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())
|
Trap::new_with_trace(reason, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ fn test_trap_trace() -> Result<()> {
|
|||||||
.err()
|
.err()
|
||||||
.expect("error calling function");
|
.expect("error calling function");
|
||||||
|
|
||||||
let trace = e.trace();
|
let trace = e.trace().expect("backtrace is available");
|
||||||
assert_eq!(trace.len(), 2);
|
assert_eq!(trace.len(), 2);
|
||||||
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
||||||
assert_eq!(trace[0].func_index(), 1);
|
assert_eq!(trace[0].func_index(), 1);
|
||||||
@@ -70,6 +70,32 @@ fn test_trap_trace() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trap_backtrace_disabled() -> Result<()> {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.wasm_backtrace(false);
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let mut store = Store::<()>::new(&engine, ());
|
||||||
|
let wat = r#"
|
||||||
|
(module $hello_mod
|
||||||
|
(func (export "run") (call $hello))
|
||||||
|
(func $hello (unreachable))
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let module = Module::new(store.engine(), wat)?;
|
||||||
|
let instance = Instance::new(&mut store, &module, &[])?;
|
||||||
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
||||||
|
|
||||||
|
let e = run_func
|
||||||
|
.call(&mut store, ())
|
||||||
|
.err()
|
||||||
|
.expect("error calling function");
|
||||||
|
|
||||||
|
assert!(e.trace().is_none(), "backtraces should be disabled");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
||||||
fn test_trap_trace_cb() -> Result<()> {
|
fn test_trap_trace_cb() -> Result<()> {
|
||||||
@@ -94,7 +120,7 @@ fn test_trap_trace_cb() -> Result<()> {
|
|||||||
.err()
|
.err()
|
||||||
.expect("error calling function");
|
.expect("error calling function");
|
||||||
|
|
||||||
let trace = e.trace();
|
let trace = e.trace().expect("backtrace is available");
|
||||||
assert_eq!(trace.len(), 2);
|
assert_eq!(trace.len(), 2);
|
||||||
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
||||||
assert_eq!(trace[0].func_index(), 2);
|
assert_eq!(trace[0].func_index(), 2);
|
||||||
@@ -124,7 +150,7 @@ fn test_trap_stack_overflow() -> Result<()> {
|
|||||||
.err()
|
.err()
|
||||||
.expect("error calling function");
|
.expect("error calling function");
|
||||||
|
|
||||||
let trace = e.trace();
|
let trace = e.trace().expect("backtrace is available");
|
||||||
assert!(trace.len() >= 32);
|
assert!(trace.len() >= 32);
|
||||||
for i in 0..trace.len() {
|
for i in 0..trace.len() {
|
||||||
assert_eq!(trace[i].module_name().unwrap(), "rec_mod");
|
assert_eq!(trace[i].module_name().unwrap(), "rec_mod");
|
||||||
@@ -429,8 +455,9 @@ fn present_after_module_drop() -> Result<()> {
|
|||||||
|
|
||||||
fn assert_trap(t: Trap) {
|
fn assert_trap(t: Trap) {
|
||||||
println!("{}", t);
|
println!("{}", t);
|
||||||
assert_eq!(t.trace().len(), 1);
|
let trace = t.trace().expect("backtrace is available");
|
||||||
assert_eq!(t.trace()[0].func_index(), 0);
|
assert_eq!(trace.len(), 1);
|
||||||
|
assert_eq!(trace[0].func_index(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +552,7 @@ fn parse_dwarf_info() -> Result<()> {
|
|||||||
.downcast::<Trap>()?;
|
.downcast::<Trap>()?;
|
||||||
|
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for frame in trap.trace() {
|
for frame in trap.trace().expect("backtrace is available") {
|
||||||
for symbol in frame.symbols() {
|
for symbol in frame.symbols() {
|
||||||
if let Some(file) = symbol.file() {
|
if let Some(file) = symbol.file() {
|
||||||
if file.ends_with("input.rs") {
|
if file.ends_with("input.rs") {
|
||||||
@@ -661,7 +688,7 @@ fn traps_without_address_map() -> Result<()> {
|
|||||||
.err()
|
.err()
|
||||||
.expect("error calling function");
|
.expect("error calling function");
|
||||||
|
|
||||||
let trace = e.trace();
|
let trace = e.trace().expect("backtrace is available");
|
||||||
assert_eq!(trace.len(), 2);
|
assert_eq!(trace.len(), 2);
|
||||||
assert_eq!(trace[0].func_name(), Some("hello"));
|
assert_eq!(trace[0].func_name(), Some("hello"));
|
||||||
assert_eq!(trace[0].func_index(), 1);
|
assert_eq!(trace[0].func_index(), 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user