diff --git a/Cargo.lock b/Cargo.lock index 5136a372a4f..6d4a57595b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4705,6 +4705,33 @@ dependencies = [ "quote", ] +[[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 0.23.0", +] + +[[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.4.1" @@ -4723,6 +4750,15 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c3e4bc09095436c8e7584d86d33e6c3ee67045af8fb262cbb9cc321de553428" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-smith" version = "0.4.5" @@ -4908,6 +4944,7 @@ dependencies = [ "unix_mode", "url", "walkdir", + "wasm-coredump-builder", "wasmer", "wasmer-cache", "wasmer-compiler", diff --git a/docs/en/examples-coredump.md b/docs/en/examples-coredump.md new file mode 100644 index 00000000000..19a7754b101 --- /dev/null +++ b/docs/en/examples-coredump.md @@ -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 + ``` + +
+ foo.rs + + 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); + } +
+ +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 diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index d60553d1ef3..786d20e3610 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -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" ] } diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index b80e920f8eb..e3c1c416969 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -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")] @@ -89,6 +91,10 @@ pub struct RunWithoutFile { #[clap(long = "verbose")] pub(crate) verbose: Option, + /// Enable coredump generation after a WebAssembly trap. + #[clap(name = "COREDUMP PATH", long = "coredump-on-trap", parse(from_os_str))] + coredump_on_trap: Option, + /// Application arguments #[clap(value_name = "ARGS")] pub(crate) args: Vec, @@ -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(), @@ -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<()> { @@ -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::() + .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(()) +}