Skip to content

Commit 4cc7687

Browse files
cfallinbongjunj
authored andcommitted
Debugging: add the debug-main world. (bytecodealliance#12756)
* Debugging: add the debug-main world. This PR "draws the rest of the owl" for the debug-main world (bytecodealliance/rfcs#45). This includes a WIT world that hosts debug components that have access to "host debug powers" via a debugging API, and the ability to load such a debug-component and give it control of the main program as a debuggee when using `wasmtime run`. The WIT is namespaced to `bytecodealliance:wasmtime` and is slightly aspirational in places: for example, the host does not yet implement injection of early return values or exception-throws. I intend to fill out a series of TODO issues once this all lands to track followup ("post-MVP") work. This PR does not include any debug components. I separately have a gdbstub component, with which I tested and co-developed this host-side implementation. My plan is to land it in a followup PR as a component that will be embedded in/shipped with the Wasmtime CLI and available under an easy-to-use CLI option. Once we have that gdbstub component, we can also implement end-to-end integration tests that boot up LLDB and run through an expected interaction. (Separately, those integration tests will require a release of wasi-sdk to ship an LLDB binary that we can use.) As such, there are no real tests in this PR: interesting behaviors only really occur with a full end-to-end flow. The integration with the CLI is a little awkward (we internally build another `wasmtime run` command that invokes the debug component, and tie it together with the debuggee via a special `invoke_debugger` API; this seemed less bad than reworking all of the WASI setup to be more reusable). Happy to take more ideas here. * Review feedback. * Review feedback. * Review feedback: update vendor-wit.sh. * Review feedback: -Ddebugger-arg= -> -Darg=. * Review feedback. * Review feedback. * Review feedback: factor host.rs into several submodules. * Review feedback: rename Debugger to Debuggee on host side. * Review feedback: split inherit_stdin_stdout, and add corresponding options for the debug component. * Review feedback. * Review feedback. * Add simple debug-component tests. * Add wasm32-wasip2 target in a few places in CI * Cargo vets for wstd dependency. * Add wasm32-wasip2 in more places * fix debug-component test dependence on componentization byte offsets * Review feedback. * Fix cancel-safety of EventFuture. * Fix: Interrupted events should only occur after interrupt(), not on every epoch yield. * Review feedback. * Review feedback: strip down WASI imports in debugger world. * fold debugger test component back into wasip1 + adapter test artifact compilation flow
1 parent 61bc9e1 commit 4cc7687

30 files changed

Lines changed: 5226 additions & 123 deletions

File tree

.github/actions/install-rust/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@ runs:
7979

8080
- name: Install the WASI target
8181
shell: bash
82-
run: rustup target add wasm32-wasip1 wasm32-unknown-unknown
82+
run: rustup target add wasm32-wasip2 wasm32-wasip1 wasm32-unknown-unknown

.github/workflows/main.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ jobs:
739739
toolchain: ${{ matrix.rust }}
740740

741741
# Install targets in order to build various tests throughout the repo
742-
- run: rustup target add wasm32-wasip1 wasm32-unknown-unknown ${{ matrix.target }}
742+
- run: rustup target add wasm32-wasip1 wasm32-unknown-unknown wasm32-wasip2 ${{ matrix.target }}
743743
- run: echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV
744744
if: matrix.target != ''
745745

@@ -937,7 +937,7 @@ jobs:
937937
if: (runner.os == 'Windows') && (matrix.feature == 'winml')
938938

939939
# Install Rust targets.
940-
- run: rustup target add wasm32-wasip1
940+
- run: rustup target add wasm32-wasip1 wasm32-wasip2
941941

942942
# Run the tests!
943943
- run: cargo test -p wasmtime-wasi-nn --features ${{ matrix.feature }}
@@ -1009,7 +1009,7 @@ jobs:
10091009
submodules: true
10101010
- uses: ./.github/actions/install-rust
10111011
- run: |
1012-
rustup target add wasm32-wasip1 wasm32-unknown-unknown
1012+
rustup target add wasm32-wasip2 wasm32-wasip1 wasm32-unknown-unknown
10131013
cd /tmp
10141014
curl --retry 5 --retry-all-errors -OL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-30/wasi-sdk-30.0-x86_64-linux.tar.gz
10151015
tar -xzf wasi-sdk-30.0-x86_64-linux.tar.gz
@@ -1038,7 +1038,7 @@ jobs:
10381038
with:
10391039
submodules: true
10401040
- uses: ./.github/actions/install-rust
1041-
- run: rustup target add wasm32-wasip1 wasm32-unknown-unknown
1041+
- run: rustup target add wasm32-wasip2 wasm32-wasip1 wasm32-unknown-unknown
10421042

10431043
- name: Install wasm-tools
10441044
run: |
@@ -1118,7 +1118,7 @@ jobs:
11181118
with:
11191119
submodules: true
11201120
- uses: ./.github/actions/install-rust
1121-
- run: rustup target add wasm32-wasip1
1121+
- run: rustup target add wasm32-wasip2 wasm32-wasip1
11221122
- run: cargo test --benches --release
11231123

11241124
# Verify that cranelift's code generation is deterministic

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ wasmtime-wasi-threads = { workspace = true, optional = true }
6060
wasmtime-wasi-http = { workspace = true, optional = true }
6161
wasmtime-unwinder = { workspace = true }
6262
wasmtime-wizer = { workspace = true, optional = true, features = ['clap', 'wasmtime'] }
63+
wasmtime-debugger = { workspace = true, optional = true }
6364
clap = { workspace = true }
6465
clap_complete = { workspace = true, optional = true }
6566
target-lexicon = { workspace = true }
@@ -565,7 +566,7 @@ gc-drc = ["gc", "wasmtime/gc-drc", "wasmtime-cli-flags/gc-drc"]
565566
gc-null = ["gc", "wasmtime/gc-null", "wasmtime-cli-flags/gc-null"]
566567
pulley = ["wasmtime-cli-flags/pulley"]
567568
stack-switching = ["wasmtime/stack-switching", "wasmtime-cli-flags/stack-switching"]
568-
debug = ["wasmtime-cli-flags/debug", "wasmtime/debug"]
569+
debug = ["wasmtime-cli-flags/debug", "wasmtime/debug", "component-model"]
569570

570571
# CLI subcommands for the `wasmtime` executable. See `wasmtime $cmd --help`
571572
# for more information on each subcommand.
@@ -591,6 +592,7 @@ run = [
591592
"dep:tokio",
592593
"wasmtime-cli-flags/async",
593594
"wasmtime-wasi-http?/p2",
595+
"dep:wasmtime-debugger",
594596
]
595597
completion = ["dep:clap_complete"]
596598
objdump = [

ci/vendor-wit.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ wkg get --format wit --overwrite "wasi:random@$p3" -o "crates/wasi-http/src/p3/w
9090
wkg get --format wit --overwrite "wasi:sockets@$p3" -o "crates/wasi-http/src/p3/wit/deps/sockets.wit"
9191
wkg get --format wit --overwrite "wasi:http@$p3" -o "crates/wasi-http/src/p3/wit/deps/http.wit"
9292

93+
rm -rf crates/debugger/wit/deps
94+
mkdir -p crates/debugger/wit/deps
95+
wkg get --format wit --overwrite "wasi:io@$p2" -o "crates/debugger/wit/deps/io.wit"
96+
wkg get --format wit --overwrite "wasi:clocks@$p2" -o "crates/debugger/wit/deps/clocks.wit"
97+
wkg get --format wit --overwrite "wasi:cli@$p2" -o "crates/debugger/wit/deps/cli.wit"
98+
wkg get --format wit --overwrite "wasi:filesystem@$p2" -o "crates/debugger/wit/deps/filesystem.wit"
99+
wkg get --format wit --overwrite "wasi:random@$p2" -o "crates/debugger/wit/deps/random.wit"
100+
wkg get --format wit --overwrite "wasi:sockets@$p2" -o "crates/debugger/wit/deps/sockets.wit"
101+
93102
# wasi-nn is fetched separately since it's not in the standard WASI registry
94103
repo=https://raw.githubusercontent.com/WebAssembly/wasi-nn
95104
revision=0.2.0-rc-2024-10-28

crates/cli-flags/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,22 @@ wasmtime_option_group! {
273273
pub log_to_files: Option<bool>,
274274
/// Enable coredump generation to this file after a WebAssembly trap.
275275
pub coredump: Option<String>,
276+
/// Load the given debugger component and attach it to the
277+
/// main module or component.
278+
pub debugger: Option<PathBuf>,
279+
/// Pass the given command-line arguments to the debugger
280+
/// component. May be specified multiple times.
281+
#[serde(default)]
282+
pub arg: Vec<String>,
283+
/// Allow the debugger component to inherit stdin.Off by
284+
/// default.
285+
pub inherit_stdin: Option<bool>,
286+
/// Allow the debugger component to inherit stdout. Off by
287+
/// default.
288+
pub inherit_stdout: Option<bool>,
289+
/// Allow the debugger component to inherit stderr. Off by
290+
/// default.
291+
pub inherit_stderr: Option<bool>,
276292
}
277293

278294
enum Debug {
@@ -491,6 +507,12 @@ wasmtime_option_group! {
491507
///
492508
/// This option can be further overwritten with `--env` flags.
493509
pub inherit_env: Option<bool>,
510+
/// Inherit stdin from the parent process. On by default.
511+
pub inherit_stdin: Option<bool>,
512+
/// Inherit stdout from the parent process. On by default.
513+
pub inherit_stdout: Option<bool>,
514+
/// Inherit stderr from the parent process. On by default.
515+
pub inherit_stderr: Option<bool>,
494516
/// Pass a wasi config variable to the program.
495517
#[serde(skip)]
496518
pub config_var: Vec<KeyValuePair>,

crates/cli-flags/src/opt.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::{KeyValuePair, WasiNnGraph};
99
use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
1010
use clap::error::{Error, ErrorKind};
1111
use serde::de::{self, Visitor};
12+
use std::path::PathBuf;
13+
use std::str::FromStr;
1214
use std::time::Duration;
1315
use std::{fmt, marker};
1416
use wasmtime::{Result, bail};
@@ -344,6 +346,20 @@ impl WasmtimeOptionValue for String {
344346
}
345347
}
346348

349+
impl WasmtimeOptionValue for PathBuf {
350+
const VAL_HELP: &'static str = "=path";
351+
fn parse(val: Option<&str>) -> Result<Self> {
352+
match val {
353+
Some(val) => Ok(PathBuf::from_str(val)?),
354+
None => bail!("value must be specified with key=val syntax"),
355+
}
356+
}
357+
358+
fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359+
write!(f, "{self:?}")
360+
}
361+
}
362+
347363
impl WasmtimeOptionValue for u32 {
348364
const VAL_HELP: &'static str = "=N";
349365
fn parse(val: Option<&str>) -> Result<Self> {

crates/debugger/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ rust-version.workspace = true
1212
workspace = true
1313

1414
[dependencies]
15-
wasmtime = { workspace = true, features = ["debug", "std", "async"] }
15+
wasmtime = { workspace = true, features = ["debug", "std", "async", "component-model", "gc", "gc-drc"] }
16+
wasmtime-wasi = { workspace = true }
17+
wasmtime-wasi-io = { workspace = true }
1618
tokio = { workspace = true, features = ["rt", "sync", "macros"] }
1719
log = { workspace = true }
20+
async-trait = { workspace = true }
1821

1922
[dev-dependencies]
2023
# Depend on `wasmtime` again to get `cranelift` and `wat` so we can

crates/debugger/src/host.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Host implementation for the debugger world.
2+
3+
use wasmtime::{
4+
Result,
5+
component::{Resource, ResourceTable},
6+
};
7+
8+
mod api;
9+
mod bindings;
10+
mod opaque;
11+
12+
pub use api::Debuggee;
13+
pub use bindings::DebugMain as DebuggerComponent;
14+
pub use bindings::bytecodealliance::wasmtime::debuggee as wit;
15+
use opaque::OpaqueDebugger;
16+
17+
/// Register a debuggee in a resource table.
18+
pub fn add_debuggee<T: Send + 'static>(
19+
table: &mut ResourceTable,
20+
debuggee: crate::Debuggee<T>,
21+
) -> Result<Resource<Debuggee>> {
22+
let engine = debuggee.engine().clone();
23+
let interrupt_pending = debuggee.interrupt_pending().clone();
24+
let inner: Option<Box<dyn OpaqueDebugger + Send + 'static>> = Some(Box::new(debuggee));
25+
Ok(table.push(Debuggee {
26+
inner,
27+
engine,
28+
interrupt_pending,
29+
})?)
30+
}
31+
32+
/// Add the debugger world's host functions to a [`wasmtime::component::Linker`].
33+
pub fn add_to_linker<T: Send + 'static>(
34+
linker: &mut wasmtime::component::Linker<T>,
35+
f: fn(&mut T) -> &mut ResourceTable,
36+
) -> wasmtime::Result<()> {
37+
wit::add_to_linker::<_, wasmtime::component::HasSelf<ResourceTable>>(linker, f)
38+
}

0 commit comments

Comments
 (0)