Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add basic coredump generation #5868

Merged
merged 1 commit into from
Feb 28, 2023
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
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
xtuc marked this conversation as resolved.
Show resolved Hide resolved
} 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)
xtuc marked this conversation as resolved.
Show resolved Hide resolved
.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))?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the .map_err here become .context(..)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is from wasm-coredump-builder and is type Box<dyn std::error::Error + Sync + Send> instead of the usual anyhow::Error. Is that a problem?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a problem insofar as the error's contexet is all lost here, only displaying one error in a possible chain of errors contained within the trait object. Whether your library exercises that I'm not sure, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a problem for my library, for now, as it doesn't add context to errors.


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(())
}