Skip to content

Commit ed0951f

Browse files
committed
add basic coredump generation
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
1 parent 85c7433 commit ed0951f

File tree

4 files changed

+168
-2
lines changed

4 files changed

+168
-2
lines changed

Cargo.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/en/examples-coredump.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Using Wasm coredump
2+
3+
The following steps describe how to debug using Wasm coredump in Wasmer:
4+
5+
1. Compile your WebAssembly with debug info enabled; for example:
6+
7+
```sh
8+
$ rustc foo.rs --target=wasm32-wasi -C debuginfo=2
9+
```
10+
11+
<details>
12+
<summary>foo.rs</summary>
13+
14+
fn c(v: usize) {
15+
a(v - 3);
16+
}
17+
18+
fn b(v: usize) {
19+
c(v - 3);
20+
}
21+
22+
fn a(v: usize) {
23+
b(v - 3);
24+
}
25+
26+
pub fn main() {
27+
a(10);
28+
}
29+
</details>
30+
31+
2. Run with Wasmer and Wasm coredump enabled:
32+
33+
```sh
34+
$ wasmer --coredump-on-trap=/tmp/coredump foo.wasm
35+
36+
thread 'main' panicked at 'attempt to subtract with overflow', foo.rs:10:7
37+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
38+
Error: failed to run main module `foo.wasm`
39+
40+
Caused by:
41+
0: Core dumped at /tmp/coredump
42+
1: failed to invoke command default
43+
2: error while executing at wasm backtrace:
44+
...
45+
```
46+
47+
3. Use [wasmgdb] to debug:
48+
```sh
49+
$ wasmgdb foo.wasm /tmp/coredump
50+
51+
wasmgdb> bt
52+
...
53+
#13 000175 as panic () at library/core/src/panicking.rs
54+
#12 000010 as a (v=???) at /path/to/foo.rs
55+
#11 000009 as c (v=???) at /path/to/foo.rs
56+
#10 000011 as b (v=???) at /path/to/foo.rs
57+
#9 000010 as a (v=???) at /path/to/foo.rs
58+
#8 000012 as main () at /path/to/foo.rs
59+
...
60+
```
61+
62+
[wasmgdb]: https://crates.io/crates/wasmgdb

lib/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ rpassword = "7.2.0"
9191
pathdiff = "0.2.1"
9292
sha2 = "0.10.6"
9393
object = "0.30.0"
94+
wasm-coredump-builder = { version = "0.1.11" }
9495

9596
[build-dependencies]
9697
chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] }

lib/cli/src/commands/run.rs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use crate::suggestions::suggest_function_exports;
88
use crate::warning;
99
use anyhow::{anyhow, Context, Result};
1010
use clap::Parser;
11+
use std::fs::File;
12+
use std::io::Write;
1113
use std::ops::Deref;
1214
use std::path::PathBuf;
1315
#[cfg(feature = "cache")]
@@ -89,6 +91,10 @@ pub struct RunWithoutFile {
8991
#[clap(long = "verbose")]
9092
pub(crate) verbose: Option<u8>,
9193

94+
/// Enable coredump generation after a WebAssembly trap.
95+
#[clap(name = "COREDUMP PATH", long = "coredump-on-trap", parse(from_os_str))]
96+
coredump_on_trap: Option<PathBuf>,
97+
9298
/// Application arguments
9399
#[clap(value_name = "ARGS")]
94100
pub(crate) args: Vec<String>,
@@ -154,7 +160,7 @@ impl RunWithPathBuf {
154160
if self.debug {
155161
logging::set_up_logging(self_clone.verbose.unwrap_or(0)).unwrap();
156162
}
157-
self_clone.inner_execute().with_context(|| {
163+
let invoke_res = self_clone.inner_execute().with_context(|| {
158164
format!(
159165
"failed to run `{}`{}",
160166
self_clone.path.display(),
@@ -164,7 +170,23 @@ impl RunWithPathBuf {
164170
""
165171
}
166172
)
167-
})
173+
});
174+
175+
if let Err(err) = invoke_res {
176+
if let Some(coredump_path) = self.coredump_on_trap.as_ref() {
177+
let source_name = self.path.to_str().unwrap_or_else(|| "unknown");
178+
if let Err(coredump_err) = generate_coredump(&err, &source_name, &coredump_path) {
179+
eprintln!("warning: coredump failed to generate: {}", coredump_err);
180+
Err(err)
181+
} else {
182+
Err(err.context(format!("core dumped at {}", coredump_path.display())))
183+
}
184+
} else {
185+
Err(err)
186+
}
187+
} else {
188+
invoke_res
189+
}
168190
}
169191

170192
fn inner_module_run(&self, store: &mut Store, instance: Instance) -> Result<()> {
@@ -632,3 +654,47 @@ impl Run {
632654
bail!("binfmt_misc is only available on linux.")
633655
}
634656
}
657+
658+
fn generate_coredump(
659+
err: &anyhow::Error,
660+
source_name: &str,
661+
coredump_path: &PathBuf,
662+
) -> Result<()> {
663+
let err = err
664+
.downcast_ref::<wasmer::RuntimeError>()
665+
.ok_or_else(|| anyhow!("no runtime error found to generate coredump with"))?;
666+
667+
let mut coredump_builder =
668+
wasm_coredump_builder::CoredumpBuilder::new().executable_name(source_name);
669+
670+
{
671+
let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
672+
673+
for frame in err.trace() {
674+
let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
675+
.codeoffset(frame.func_offset() as u32)
676+
.funcidx(frame.func_index())
677+
.build();
678+
thread_builder.add_frame(coredump_frame);
679+
}
680+
681+
coredump_builder.add_thread(thread_builder.build());
682+
}
683+
684+
let coredump = coredump_builder
685+
.serialize()
686+
.map_err(|err| anyhow!("failed to serialize coredump: {}", err))?;
687+
688+
let mut f = File::create(coredump_path).context(format!(
689+
"failed to create file at `{}`",
690+
coredump_path.display()
691+
))?;
692+
f.write_all(&coredump).with_context(|| {
693+
format!(
694+
"failed to write coredump file at `{}`",
695+
coredump_path.display()
696+
)
697+
})?;
698+
699+
Ok(())
700+
}

0 commit comments

Comments
 (0)