Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ memfd = "0.6.2"
paste = "1.0.3"
encoding_rs = { version = "0.8.31", optional = true }
sptr = "0.3.2"
wasm-encoder = { workspace = true }

[target.'cfg(target_os = "macos")'.dependencies]
mach = "0.3.2"
Expand Down
63 changes: 46 additions & 17 deletions crates/runtime/src/traphandlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! signalhandling mechanisms.

mod backtrace;
mod coredump;

use crate::{Instance, VMContext, VMRuntimeLimits};
use anyhow::Error;
Expand All @@ -12,6 +13,7 @@ use std::ptr;
use std::sync::Once;

pub use self::backtrace::{Backtrace, Frame};
pub use self::coredump::CoreDumpStack;
pub use self::tls::{tls_eager_initialize, AsyncWasmCallState, PreviousAsyncWasmCallState};

cfg_if::cfg_if! {
Expand Down Expand Up @@ -176,6 +178,8 @@ pub struct Trap {
pub reason: TrapReason,
/// Wasm backtrace of the trap, if any.
pub backtrace: Option<Backtrace>,
/// The Wasm Coredump, if any.
pub coredumpstack: Option<CoreDumpStack>,
}

/// Enumeration of different methods of raising a trap.
Expand Down Expand Up @@ -255,6 +259,7 @@ impl From<wasmtime_environ::Trap> for TrapReason {
pub unsafe fn catch_traps<'a, F>(
signal_handler: Option<*const SignalHandler<'static>>,
capture_backtrace: bool,
capture_coredump: bool,
caller: *mut VMContext,
mut closure: F,
) -> Result<(), Box<Trap>>
Expand All @@ -263,19 +268,24 @@ where
{
let limits = Instance::from_vmctx(caller, |i| i.runtime_limits());

let result = CallThreadState::new(signal_handler, capture_backtrace, *limits).with(|cx| {
wasmtime_setjmp(
cx.jmp_buf.as_ptr(),
call_closure::<F>,
&mut closure as *mut F as *mut u8,
caller,
)
});
let result = CallThreadState::new(signal_handler, capture_backtrace, capture_coredump, *limits)
.with(|cx| {
wasmtime_setjmp(
cx.jmp_buf.as_ptr(),
call_closure::<F>,
&mut closure as *mut F as *mut u8,
caller,
)
});

return match result {
Ok(x) => Ok(x),
Err((UnwindReason::Trap(reason), backtrace)) => Err(Box::new(Trap { reason, backtrace })),
Err((UnwindReason::Panic(panic), _)) => std::panic::resume_unwind(panic),
Err((UnwindReason::Trap(reason), backtrace, coredumpstack)) => Err(Box::new(Trap {
reason,
backtrace,
coredumpstack,
})),
Err((UnwindReason::Panic(panic), _, _)) => std::panic::resume_unwind(panic),
};

extern "C" fn call_closure<F>(payload: *mut u8, caller: *mut VMContext)
Expand All @@ -294,10 +304,12 @@ mod call_thread_state {
/// Temporary state stored on the stack which is registered in the `tls` module
/// below for calls into wasm.
pub struct CallThreadState {
pub(super) unwind: UnsafeCell<MaybeUninit<(UnwindReason, Option<Backtrace>)>>,
pub(super) unwind:
UnsafeCell<MaybeUninit<(UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)>>,
pub(super) jmp_buf: Cell<*const u8>,
pub(super) signal_handler: Option<*const SignalHandler<'static>>,
pub(super) capture_backtrace: bool,
pub(super) capture_coredump: bool,

pub(crate) limits: *const VMRuntimeLimits,

Expand Down Expand Up @@ -331,13 +343,15 @@ mod call_thread_state {
pub(super) fn new(
signal_handler: Option<*const SignalHandler<'static>>,
capture_backtrace: bool,
capture_coredump: bool,
limits: *const VMRuntimeLimits,
) -> CallThreadState {
CallThreadState {
unwind: UnsafeCell::new(MaybeUninit::uninit()),
jmp_buf: Cell::new(ptr::null()),
signal_handler,
capture_backtrace,
capture_coredump,
limits,
prev: Cell::new(ptr::null()),
old_last_wasm_exit_fp: Cell::new(unsafe { *(*limits).last_wasm_exit_fp.get() }),
Expand Down Expand Up @@ -389,7 +403,7 @@ impl CallThreadState {
fn with(
mut self,
closure: impl FnOnce(&CallThreadState) -> i32,
) -> Result<(), (UnwindReason, Option<Backtrace>)> {
) -> Result<(), (UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)> {
let ret = tls::set(&mut self, |me| closure(me));
if ret != 0 {
Ok(())
Expand All @@ -399,12 +413,12 @@ impl CallThreadState {
}

#[cold]
unsafe fn read_unwind(&self) -> (UnwindReason, Option<Backtrace>) {
unsafe fn read_unwind(&self) -> (UnwindReason, Option<Backtrace>, Option<CoreDumpStack>) {
(*self.unwind.get()).as_ptr().read()
}

fn unwind_with(&self, reason: UnwindReason) -> ! {
let backtrace = match reason {
let (backtrace, coredump) = match reason {
// Panics don't need backtraces. There is nowhere to attach the
// hypothetical backtrace to and it doesn't really make sense to try
// in the first place since this is a Rust problem rather than a
Expand All @@ -416,11 +430,13 @@ impl CallThreadState {
| UnwindReason::Trap(TrapReason::User {
needs_backtrace: false,
..
}) => None,
UnwindReason::Trap(_) => self.capture_backtrace(self.limits, None),
}) => (None, None),
UnwindReason::Trap(_) => (self.capture_backtrace(self.limits, None), self.capture_coredump(self.limits, None)),
};
unsafe {
(*self.unwind.get()).as_mut_ptr().write((reason, backtrace));
(*self.unwind.get())
.as_mut_ptr()
.write((reason, backtrace, coredump));
wasmtime_longjmp(self.jmp_buf.get());
}
}
Expand Down Expand Up @@ -472,13 +488,15 @@ impl CallThreadState {

fn set_jit_trap(&self, pc: *const u8, fp: usize, faulting_addr: Option<usize>) {
let backtrace = self.capture_backtrace(self.limits, Some((pc as usize, fp)));
let coredump = self.capture_coredump(self.limits, Some((pc as usize, fp)));
unsafe {
(*self.unwind.get()).as_mut_ptr().write((
UnwindReason::Trap(TrapReason::Jit {
pc: pc as usize,
faulting_addr,
}),
backtrace,
coredump,
));
}
}
Expand All @@ -495,6 +513,17 @@ impl CallThreadState {
Some(unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) })
}

fn capture_coredump(
&self,
limits: *const VMRuntimeLimits,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Option<CoreDumpStack> {
if !self.capture_coredump {
return None;
}
Some(CoreDumpStack::new(&self, limits, trap_pc_and_fp))
}

pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = &Self> + 'a {
let mut state = Some(self);
std::iter::from_fn(move || {
Expand Down
38 changes: 38 additions & 0 deletions crates/runtime/src/traphandlers/coredump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use wasm_encoder::CoreDumpValue;

use crate::{Backtrace, VMRuntimeLimits};

use super::CallThreadState;

/// A WebAssembly Coredump
#[derive(Debug)]
pub struct CoreDumpStack {
/// The backtrace containing the stack frames for the CoreDump
pub bt: Backtrace,

/// Unimplemented
/// The indices of the locals and operand_stack all map to each other (ie.
/// index 0 is the locals for the first frame in the backtrace, etc)
pub locals: Vec<Vec<CoreDumpValue>>,

/// Unimplemented
/// The operands for each stack frame
pub operand_stack: Vec<Vec<CoreDumpValue>>,
}

impl CoreDumpStack {
/// Capture a core dump of the current wasm state
pub fn new(
cts: &CallThreadState,
limits: *const VMRuntimeLimits,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Self {
let bt = unsafe { Backtrace::new_with_trap_state(limits, cts, trap_pc_and_fp) };

Self {
bt,
locals: vec![],
operand_stack: vec![],
}
}
}
1 change: 1 addition & 0 deletions crates/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ wasmtime-component-macro = { workspace = true, optional = true }
wasmtime-component-util = { workspace = true, optional = true }
target-lexicon = { workspace = true }
wasmparser = { workspace = true }
wasm-encoder = { workspace = true }
anyhow = { workspace = true }
libc = "0.2"
cfg-if = { workspace = true }
Expand Down
11 changes: 11 additions & 0 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub struct Config {
pub(crate) memory_init_cow: bool,
pub(crate) memory_guaranteed_dense_image_size: u64,
pub(crate) force_memory_init_memfd: bool,
pub(crate) coredump_on_trap: bool,
}

/// User-provided configuration for the compiler.
Expand Down Expand Up @@ -199,6 +200,7 @@ impl Config {
memory_init_cow: true,
memory_guaranteed_dense_image_size: 16 << 20,
force_memory_init_memfd: false,
coredump_on_trap: false,
};
#[cfg(any(feature = "cranelift", feature = "winch"))]
{
Expand Down Expand Up @@ -1450,6 +1452,15 @@ impl Config {
self
}

/// Configures whether or not a coredump should be generated and attached to
/// the anyhow::Error when a trap is raised.
///
/// This option is disabled by default.
pub fn coredump_on_trap(&mut self, enable: bool) -> &mut Self {
self.coredump_on_trap = enable;
self
}

/// Configures the "guaranteed dense image size" for copy-on-write
/// initialized memories.
///
Expand Down
115 changes: 115 additions & 0 deletions crates/wasmtime/src/coredump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::fmt;

use crate::{store::StoreOpaque, FrameInfo, Global, Instance, Memory, Module, WasmBacktrace};

/// Representation of a core dump of a WebAssembly module
///
/// When the Config::coredump_on_trap option is enabled this structure is
/// attached to the [`anyhow::Error`] returned from many Wasmtime functions that
/// execute WebAssembly such as [`Instance::new`] or [`Func::call`]. This can be
/// acquired with the [`anyhow::Error::downcast`] family of methods to
/// programmatically inspect the coredump. Otherwise since it's part of the
/// error returned this will get printed along with the rest of the error when
/// the error is logged.
///
/// Note that some state, such as Wasm locals or values on the operand stack,
/// may be optimized away by the compiler or otherwise not recovered in the
/// coredump.
///
/// Capturing of wasm coredumps can be configured through the
/// [`Config::coredump_on_trap`][crate::Config::coredump_on_trap] method.
///
/// For more information about errors in wasmtime see the documentation of the
/// [`Trap`][crate::Trap] type.
///
/// [`Func::call`]: crate::Func::call
/// [`Instance::new`]: crate::Instance::new
pub struct WasmCoreDump {
name: String,
modules: Vec<Module>,
instances: Vec<Instance>,
store_memories: Vec<Memory>,
store_globals: Vec<Global>,
backtrace: WasmBacktrace,
}

impl WasmCoreDump {
pub(crate) fn new(store: &StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump {
let modules: Vec<_> = store.modules().all_modules().cloned().collect();
let instances: Vec<Instance> = store.all_instances().collect();
let store_memories: Vec<Memory> = store.all_memories().collect();
let store_globals: Vec<Global> = store.all_globals().collect();

WasmCoreDump {
name: String::from("store_name"),
modules,
instances,
store_memories,
store_globals,
backtrace,
}
}

/// The stack frames for the CoreDump
pub fn frames(&self) -> &[FrameInfo] {
self.backtrace.frames()
}

/// The names of the modules involved in the CoreDump
pub fn modules(&self) -> &[Module] {
self.modules.as_ref()
}

/// The instances involved in the CoreDump
pub fn instances(&self) -> &[Instance] {
self.instances.as_ref()
}

/// The imported globals that belong to the store, rather than a specific
/// instance
pub fn store_globals(&self) -> &[Global] {
self.store_globals.as_ref()
}

/// The imported memories that belong to the store, rather than a specific
/// instance.
pub fn store_memories(&self) -> &[Memory] {
self.store_memories.as_ref()
}
}

impl fmt::Display for WasmCoreDump {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
writeln!(f, "modules:")?;
for module in self.modules.iter() {
writeln!(f, " {}", module.name().unwrap_or("<module>"))?;
}

writeln!(f, "instances:")?;
for instance in self.instances.iter() {
writeln!(f, " {:?}", instance)?;
}

writeln!(f, "memories:")?;
for memory in self.store_memories.iter() {
writeln!(f, " {:?}", memory)?;
}

writeln!(f, "globals:")?;
for global in self.store_globals.iter() {
writeln!(f, " {:?}", global)?;
}

writeln!(f, "backtrace:")?;
write!(f, "{}", self.backtrace)?;

Ok(())
}
}

impl fmt::Debug for WasmCoreDump {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<wasm core dump>")
}
}
Loading