Skip to content

Commit

Permalink
add basic coredump generation
Browse files Browse the repository at this point in the history
This change adds a basic coredump generation after a WebAssembly trap
was entered. The coredump includes rudimentary stack / process debugging
information.

A new CLI argument is added to enable coredump generation:
```
wasmtime --coredump-on-trap=/path/to/coredump/file module.wasm
```

See ./docs/examples-coredump.md for a working example.

Refs #5732
  • Loading branch information
xtuc committed Feb 28, 2023
1 parent 2dd6064 commit e9f89f1
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 2 deletions.
28 changes: 28 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ wat = { workspace = true }
serde = "1.0.94"
serde_json = "1.0.26"
wasmparser = { workspace = true }
wasm-coredump-builder = { version = "0.1.11" }

[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["mm", "param"] }
Expand Down
62 changes: 62 additions & 0 deletions docs/examples-coredump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Using Wasm coredump

The following steps describe how to debug using Wasm coredump in Wasmtime:

1. Compile your WebAssembly with debug info enabled; for example:

```sh
$ rustc foo.rs --target=wasm32-wasi -C debuginfo=2
```

<details>
<summary>foo.rs</summary>

fn c(v: usize) {
a(v - 3);
}

fn b(v: usize) {
c(v - 3);
}

fn a(v: usize) {
b(v - 3);
}

pub fn main() {
a(10);
}
</details>

2. Run with Wasmtime and Wasm coredump enabled:

```sh
$ wasmtime --coredump-on-trap=/tmp/coredump foo.wasm
thread 'main' panicked at 'attempt to subtract with overflow', foo.rs:10:7
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: failed to run main module `foo.wasm`
Caused by:
0: Core dumped at /tmp/coredump
1: failed to invoke command default
2: error while executing at wasm backtrace:
...
```
3. Use [wasmgdb] to debug:
```sh
$ wasmgdb foo.wasm /tmp/coredump
wasmgdb> bt
...
#13 000175 as panic () at library/core/src/panicking.rs
#12 000010 as a (v=???) at /path/to/foo.rs
#11 000009 as c (v=???) at /path/to/foo.rs
#10 000011 as b (v=???) at /path/to/foo.rs
#9 000010 as a (v=???) at /path/to/foo.rs
#8 000012 as main () at /path/to/foo.rs
...
```
[wasmgdb]: https://crates.io/crates/wasmgdb
73 changes: 71 additions & 2 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use anyhow::{anyhow, bail, Context as _, Result};
use clap::Parser;
use once_cell::sync::Lazy;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;
use std::path::{Component, Path, PathBuf};
use std::thread;
use std::time::Duration;
Expand Down Expand Up @@ -151,6 +153,10 @@ pub struct RunCommand {
)]
wasm_timeout: Option<Duration>,

/// Enable coredump generation after a WebAssembly trap.
#[clap(long = "coredump-on-trap", value_name = "PATH")]
coredump_on_trap: Option<String>,

// NOTE: this must come last for trailing varargs
/// The arguments to pass to the module
#[clap(value_name = "ARGS")]
Expand All @@ -170,6 +176,13 @@ impl RunCommand {

let preopen_sockets = self.compute_preopen_sockets()?;

// Validate coredump-on-trap argument
if let Some(coredump_path) = self.coredump_on_trap.as_ref() {
if coredump_path.contains("%") {
bail!("the coredump-on-trap path does not support patterns yet.")
}
}

// Make wasi available by default.
let preopen_dirs = self.compute_preopen_dirs()?;
let argv = self.compute_argv();
Expand Down Expand Up @@ -376,13 +389,35 @@ impl RunCommand {
// Invoke the function and then afterwards print all the results that came
// out, if there are any.
let mut results = vec![Val::null(); ty.results().len()];
func.call(store, &values, &mut results).with_context(|| {
let invoke_res = func.call(store, &values, &mut results).with_context(|| {
if let Some(name) = name {
format!("failed to invoke `{}`", name)
} else {
format!("failed to invoke command default")
}
})?;
});

if let Err(err) = invoke_res {
let err = if err.is::<wasmtime::Trap>() {
if let Some(coredump_path) = self.coredump_on_trap.as_ref() {
let source_name = self.module.to_str().unwrap_or_else(|| "unknown");

if let Err(coredump_err) = generate_coredump(&err, &source_name, coredump_path)
{
eprintln!("warning: coredump failed to generate: {}", coredump_err);
err
} else {
err.context(format!("core dumped at {}", coredump_path))
}
} else {
err
}
} else {
err
};
return Err(err);
}

if !results.is_empty() {
eprintln!(
"warning: using `--invoke` with a function that returns values \
Expand Down Expand Up @@ -557,3 +592,37 @@ fn ctx_set_listenfd(num_fd: usize, builder: WasiCtxBuilder) -> Result<(usize, Wa

Ok((num_fd, builder))
}

fn generate_coredump(err: &anyhow::Error, source_name: &str, coredump_path: &str) -> Result<()> {
let bt = err
.downcast_ref::<wasmtime::WasmBacktrace>()
.ok_or_else(|| anyhow!("no wasm backtrace found to generate coredump with"))?;

let mut coredump_builder =
wasm_coredump_builder::CoredumpBuilder::new().executable_name(source_name);

{
let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");

for frame in bt.frames() {
let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
.codeoffset(frame.func_offset().unwrap_or(0) as u32)
.funcidx(frame.func_index())
.build();
thread_builder.add_frame(coredump_frame);
}

coredump_builder.add_thread(thread_builder.build());
}

let coredump = coredump_builder
.serialize()
.map_err(|err| anyhow!("failed to serialize coredump: {}", err))?;

let mut f = File::create(coredump_path)
.context(format!("failed to create file at `{}`", coredump_path))?;
f.write_all(&coredump)
.with_context(|| format!("failed to write coredump file at `{}`", coredump_path))?;

Ok(())
}

0 comments on commit e9f89f1

Please sign in to comment.