From 3d06eda1cfab70992bc406791b956eb676cf8bf6 Mon Sep 17 00:00:00 2001 From: Sven Sauleau Date: Thu, 23 Feb 2023 08:52:27 +0000 Subject: [PATCH] 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: ``` 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 --- Cargo.lock | 43 +++++++++++++++++++++++++-- Cargo.toml | 1 + docs/examples-coredump.md | 62 +++++++++++++++++++++++++++++++++++++++ src/commands/run.rs | 60 +++++++++++++++++++++++++++++++++++-- supply-chain/audits.toml | 15 ++++++++++ supply-chain/imports.lock | 15 ++++++++++ 6 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 docs/examples-coredump.md diff --git a/Cargo.lock b/Cargo.lock index d9aa04d192f6..57956a4ce4b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1418,6 +1418,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.0" @@ -1875,11 +1884,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.2.6", "libc", ] @@ -3268,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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbb06cb8a29340498b1e3a8f1ea1c7ef577594960da204c392c794ee54757b9" +dependencies = [ + "wasm-coredump-encoder", + "wasm-coredump-types", + "wasm-encoder", +] + +[[package]] +name = "wasm-coredump-encoder" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07032be7b990939441f09a68232fe5a7c75056438b9721226dc56985e48ccdd" +dependencies = [ + "leb128", + "wasm-coredump-types", +] + +[[package]] +name = "wasm-coredump-types" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70ce904d36ee12c9953398359e8765b8519bf26730c450406ce07098cf58318" + [[package]] name = "wasm-encoder" version = "0.23.0" @@ -3506,6 +3542,7 @@ dependencies = [ "tempfile", "test-programs", "tokio", + "wasm-coredump-builder", "wasmparser", "wasmtime", "wasmtime-cache", diff --git a/Cargo.toml b/Cargo.toml index f052cbe1d628..83b2809f8cc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.10" } [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["mm", "param"] } diff --git a/docs/examples-coredump.md b/docs/examples-coredump.md new file mode 100644 index 000000000000..6a78dd5176f2 --- /dev/null +++ b/docs/examples-coredump.md @@ -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 + ``` + +
+ 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 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 diff --git a/src/commands/run.rs b/src/commands/run.rs index 4f5751c3befe..1a242d316be5 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -4,10 +4,12 @@ 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; -use wasmtime::{Engine, Func, Linker, Module, Store, Val, ValType}; +use wasmtime::{Engine, Func, Linker, Module, Store, Val, ValType, WasmBacktrace}; use wasmtime_cli_flags::{CommonOptions, WasiModules}; use wasmtime_wasi::maybe_exit_on_error; use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder}; @@ -151,6 +153,10 @@ pub struct RunCommand { )] wasm_timeout: Option, + /// Enable coredump generation after a WebAssembly trap. + #[clap(long = "coredump-on-trap", value_name = "PATH")] + coredump_on_trap: Option, + // 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,54 @@ 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") } - })?; + }); + match invoke_res { + Ok(_) => {} + Err(err) => { + if let Some(coredump_path) = self.coredump_on_trap.as_ref() { + let bt = err + .downcast_ref::() + .ok_or_else(|| anyhow!("failed to downcast to WasmBacktrace"))?; + + let mut coredump_builder = wasm_coredump_builder::CoredumpBuilder::new() + .executable_name(&self.module.to_str().unwrap_or_else(|| "unknown")); + + { + 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() + .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) + .context("failed to write coredump file")?; + + return Err(err.context(format!("Core dumped at {}", coredump_path))); + } else { + return Err(err); + } + } + }; + if !results.is_empty() { eprintln!( "warning: using `--invoke` with a function that returns values \ diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index e4126cf0b1b7..6ad92f85f227 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -762,6 +762,21 @@ is similar to what it once was back then. Skimming over the crate there is nothing suspicious and it's everything you'd expect a Rust URL parser to be. """ +[[audits.wasm-coredump-builder]] +who = "Sven Sauleau " +criteria = "safe-to-deploy" +version = "0.1.10" + +[[audits.wasm-coredump-encoder]] +who = "Sven Sauleau " +criteria = "safe-to-deploy" +version = "0.1.10" + +[[audits.wasm-coredump-types]] +who = "Sven Sauleau " +criteria = "safe-to-deploy" +version = "0.1.10" + [[audits.wasm-encoder]] who = "Alex Crichton " criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 5d7eac22f243..e11a1d35146a 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -185,6 +185,11 @@ criteria = "safe-to-deploy" version = "0.12.3" notes = "This version is used in rust's libstd, so effectively we're already trusting it" +[[audits.mozilla.audits.hermit-abi]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.1.19 -> 0.2.6" + [[audits.mozilla.audits.indexmap]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -238,6 +243,16 @@ criteria = "safe-to-deploy" version = "0.2.15" notes = "All code written or reviewed by Josh Stone." +[[audits.mozilla.audits.num_cpus]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.13.1 -> 1.14.0" + +[[audits.mozilla.audits.num_cpus]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.14.0 -> 1.15.0" + [[audits.mozilla.audits.once_cell]] who = "Mike Hommey " criteria = "safe-to-deploy"