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:

```
wasmer --coredump-on-trap=/path/to/coredump/file module.wasm
```

See docs/en/examples-coredump.md.

Refs #3578
  • Loading branch information
xtuc committed Feb 27, 2023
1 parent 85c7433 commit ed0951f
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 2 deletions.
37 changes: 37 additions & 0 deletions Cargo.lock

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

62 changes: 62 additions & 0 deletions docs/en/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 Wasmer:

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 Wasmer and Wasm coredump enabled:

```sh
$ wasmer --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
1 change: 1 addition & 0 deletions lib/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ rpassword = "7.2.0"
pathdiff = "0.2.1"
sha2 = "0.10.6"
object = "0.30.0"
wasm-coredump-builder = { version = "0.1.11" }

[build-dependencies]
chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] }
Expand Down
70 changes: 68 additions & 2 deletions lib/cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::suggestions::suggest_function_exports;
use crate::warning;
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use std::fs::File;
use std::io::Write;
use std::ops::Deref;
use std::path::PathBuf;
#[cfg(feature = "cache")]
Expand Down Expand Up @@ -89,6 +91,10 @@ pub struct RunWithoutFile {
#[clap(long = "verbose")]
pub(crate) verbose: Option<u8>,

/// Enable coredump generation after a WebAssembly trap.
#[clap(name = "COREDUMP PATH", long = "coredump-on-trap", parse(from_os_str))]
coredump_on_trap: Option<PathBuf>,

/// Application arguments
#[clap(value_name = "ARGS")]
pub(crate) args: Vec<String>,
Expand Down Expand Up @@ -154,7 +160,7 @@ impl RunWithPathBuf {
if self.debug {
logging::set_up_logging(self_clone.verbose.unwrap_or(0)).unwrap();
}
self_clone.inner_execute().with_context(|| {
let invoke_res = self_clone.inner_execute().with_context(|| {
format!(
"failed to run `{}`{}",
self_clone.path.display(),
Expand All @@ -164,7 +170,23 @@ impl RunWithPathBuf {
""
}
)
})
});

if let Err(err) = invoke_res {
if let Some(coredump_path) = self.coredump_on_trap.as_ref() {
let source_name = self.path.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(err)
} else {
Err(err.context(format!("core dumped at {}", coredump_path.display())))
}
} else {
Err(err)
}
} else {
invoke_res
}
}

fn inner_module_run(&self, store: &mut Store, instance: Instance) -> Result<()> {
Expand Down Expand Up @@ -632,3 +654,47 @@ impl Run {
bail!("binfmt_misc is only available on linux.")
}
}

fn generate_coredump(
err: &anyhow::Error,
source_name: &str,
coredump_path: &PathBuf,
) -> Result<()> {
let err = err
.downcast_ref::<wasmer::RuntimeError>()
.ok_or_else(|| anyhow!("no runtime error 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 err.trace() {
let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
.codeoffset(frame.func_offset() 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.display()
))?;
f.write_all(&coredump).with_context(|| {
format!(
"failed to write coredump file at `{}`",
coredump_path.display()
)
})?;

Ok(())
}

0 comments on commit ed0951f

Please sign in to comment.