Skip to content

Commit

Permalink
Add core dump support to the runtime
Browse files Browse the repository at this point in the history
This adds the machinery to capture a core dump when a trap occurs
and attach it to the resulting anyhow::Error that gets bubbled up to
the caller. I've created a CoreDumpStack structure in the runtime, which is
currently just a backtrace until we design a way to recover the locals
stack values when a trap occurs. When that CoreDumpStack gets converted to
a wasmtime::WasmCoreDump, we add additional information from the Store such
as globals, memories, and instance information.

A lot of this is mechanistically similar to how backtraces
are captured and attached to errors. Given that they both are attached as
context to anyhow::Errors, setting coredump_on_trap to true will supercede
any setting for wasm_backtrace.
  • Loading branch information
itsrainy committed Jun 21, 2023
1 parent 561013d commit 61f67b3
Show file tree
Hide file tree
Showing 16 changed files with 618 additions and 430 deletions.
774 changes: 375 additions & 399 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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 @@ -172,6 +174,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 @@ -251,6 +255,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 @@ -259,19 +264,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 @@ -290,10 +300,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 @@ -327,13 +339,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 @@ -385,7 +399,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 @@ -395,12 +409,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 @@ -412,11 +426,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 @@ -468,13 +484,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 @@ -491,6 +509,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
7 changes: 5 additions & 2 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1452,8 +1452,11 @@ impl Config {
self
}

/// Whether or not a coredump should be generated and attached to the Error when
/// a trap is raised.
/// Configures whether or not a coredump should be generated and attached to
/// the anyhow::Error when a trap is raised. Because a coredump is a
/// superset of a backtrace, setting this to true will override the setting
/// for `wasm_backtrace`, resulting in a WasmCoreDump being attached to the
/// raised error rather than a WasmBacktrace.
///
/// This option is disabled by default.
pub fn coredump_on_trap(&mut self, enable: bool) -> &mut Self {
Expand Down
80 changes: 80 additions & 0 deletions crates/wasmtime/src/coredump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::fmt;

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

/// Representation of a core dump of a WebAssembly module
///
/// 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.
///
/// TODO: Notably absent from this structure at the moment are any locals or
/// operand values for the stack frames. More work is needed to be able to
/// recover those when a trap occurs, at which point they will be added here.
///
/// 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`] type.
///
/// [`Func::call`]: crate::Func::call
/// [`Instance::new`]: crate::Instance::new
pub struct WasmCoreDump {
name: String,
modules: Vec<Module>,
instances: Vec<Instance>,
memories: Vec<Memory>,
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 memories: Vec<Memory> = store.all_memories().collect();
let globals: Vec<Global> = store.all_globals().collect();
WasmCoreDump {
name: String::from("store_name"),
modules,
instances,
memories,
globals,
backtrace,
}
}
}

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_default())?;
}

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

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

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

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

Ok(())
}
}
5 changes: 5 additions & 0 deletions crates/wasmtime/src/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
SharedMemory, TableType, Val, ValType,
};
use anyhow::{anyhow, bail, Result};
use runtime::ExportGlobal;
use std::mem;
use std::ptr;
use wasmtime_runtime::{self as runtime};
Expand Down Expand Up @@ -248,6 +249,10 @@ impl Global {
}
}

pub(crate) fn from_stored(stored: Stored<ExportGlobal>) -> Global {
Global(stored)
}

/// Returns the underlying type of this `global`.
///
/// # Panics
Expand Down
1 change: 1 addition & 0 deletions crates/wasmtime/src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
let result = wasmtime_runtime::catch_traps(
store.0.signal_handler(),
store.0.engine().config().wasm_backtrace,
store.0.engine().config().coredump_on_trap,
store.0.default_caller(),
closure,
);
Expand Down
3 changes: 3 additions & 0 deletions crates/wasmtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ impl Instance {

Ok((instance, compiled_module.module().start_func))
}
pub(crate) fn from_stored(stored: Stored<InstanceData>) -> Instance {
Instance(stored)
}

pub(crate) fn from_wasmtime(handle: InstanceData, store: &mut StoreOpaque) -> Instance {
Instance(store.store_data_mut().insert(handle))
Expand Down
1 change: 1 addition & 0 deletions crates/wasmtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ mod compiler;

mod code;
mod config;
mod coredump;
mod engine;
mod externals;
mod instance;
Expand Down
6 changes: 5 additions & 1 deletion crates/wasmtime/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::ops::Range;
use std::slice;
use std::time::Instant;
use wasmtime_environ::MemoryPlan;
use wasmtime_runtime::{RuntimeLinearMemory, VMMemoryImport};
use wasmtime_runtime::{ExportMemory, RuntimeLinearMemory, VMMemoryImport};

pub use wasmtime_runtime::WaitResult;

Expand Down Expand Up @@ -271,6 +271,10 @@ impl Memory {
}
}

pub(crate) fn from_stored(stored: Stored<ExportMemory>) -> Memory {
Memory(stored)
}

/// Returns the underlying type of this memory.
///
/// # Panics
Expand Down
Loading

0 comments on commit 61f67b3

Please sign in to comment.