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/directory module.wasm
```

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

Refs bytecodealliance#5732
  • Loading branch information
xtuc committed Feb 23, 2023
1 parent f6c6bc2 commit e71fc5b
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 6 deletions.
70 changes: 67 additions & 3 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.9" }

[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
62 changes: 59 additions & 3 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ 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;
use wasmtime::{Engine, Func, Linker, Module, Store, Val, ValType};
use wasmtime::{Engine, Func, Linker, Module, Store, Val, ValType, WasmBacktrace};
use wasmtime_cli_flags::{CommonOptions, WasiModules};
use wasmtime_wasi::maybe_exit_on_error;
use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
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,15 @@ 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("%") {
return Err(anyhow!(
"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 +391,54 @@ 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")
}
})?;
});
match invoke_res {
Ok(_) => {}
Err(err) => {
if let Some(coredump_path) = self.coredump_on_trap.as_ref() {
let bt = err
.downcast_ref::<WasmBacktrace>()
.ok_or_else(|| anyhow!("failed to downcast to WasmBacktrace"))?;

let mut coredump_builder = wasm_coredump_builder::CoredumpBuilder::new()
.executable_name(&self.module.to_str().unwrap_or_else(|| "unknown"));

{
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()
.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)
.context("failed to write coredump file")?;

return Err(err.context(format!("Core dumped at {}", coredump_path)));
} else {
return Err(err);
}
}
};

if !results.is_empty() {
eprintln!(
"warning: using `--invoke` with a function that returns values \
Expand Down

0 comments on commit e71fc5b

Please sign in to comment.