add basic coredump generation (#5868)
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 https://github.com/bytecodealliance/wasmtime/issues/5732
This commit is contained in:
28
Cargo.lock
generated
28
Cargo.lock
generated
@@ -3277,6 +3277,33 @@ version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-coredump-builder"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "158180f35c9ba89a3e7763f20be93e77d5e41535c18e22c85d6dd5b5bce18108"
|
||||
dependencies = [
|
||||
"wasm-coredump-encoder",
|
||||
"wasm-coredump-types",
|
||||
"wasm-encoder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-coredump-encoder"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0c99cdf3a88363570f1027e2f337de6647cac9fed5d474f86103d7c45c8700"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
"wasm-coredump-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-coredump-types"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10e35729a021e44c20511e23ac2b215df05da243bdc4bad336fd3686552539fc"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.23.0"
|
||||
@@ -3515,6 +3542,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-programs",
|
||||
"tokio",
|
||||
"wasm-coredump-builder",
|
||||
"wasmparser",
|
||||
"wasmtime",
|
||||
"wasmtime-cache",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
62
docs/examples-coredump.md
Normal file
62
docs/examples-coredump.md
Normal file
@@ -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
|
||||
@@ -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;
|
||||
@@ -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")]
|
||||
@@ -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();
|
||||
@@ -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 \
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user