Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 271 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ wasmtime-wasi-http = { workspace = true, optional = true }
wasmtime-unwinder = { workspace = true }
wasmtime-wizer = { workspace = true, optional = true, features = ['clap', 'wasmtime'] }
wasmtime-debugger = { workspace = true, optional = true }
gdbstub-component-artifact = { workspace = true, optional = true }
clap = { workspace = true }
clap_complete = { workspace = true, optional = true }
target-lexicon = { workspace = true }
Expand Down Expand Up @@ -168,6 +169,8 @@ members = [
"crates/wasi-tls-nativetls",
"crates/wasi-tls-openssl",
"crates/debugger",
"crates/gdbstub-component",
"crates/gdbstub-component/artifact",
"crates/wizer/fuzz",
"crates/wizer/tests/regex-test",
"crates/wizer/benches/regex-bench",
Expand Down Expand Up @@ -280,6 +283,7 @@ wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version
wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=44.0.0", package = 'wasmtime-internal-wit-bindgen' }
wasmtime-unwinder = { path = "crates/unwinder", version = "=44.0.0", package = 'wasmtime-internal-unwinder' }
wasmtime-debugger = { path = "crates/debugger", version = "=44.0.0", package = "wasmtime-internal-debugger" }
gdbstub-component-artifact = { path = "crates/gdbstub-component/artifact", package = "wasmtime-internal-gdbstub-component-artifact" }
wasmtime-wizer = { path = "crates/wizer", version = "44.0.0" }

# Miscellaneous crates without a `wasmtime-*` prefix in their name but still
Expand Down Expand Up @@ -358,6 +362,9 @@ wasm-wave = "0.245.0"
wasm-compose = "0.245.0"
json-from-wast = "0.245.0"

wstd = "0.6.5"
wasip2 = "1.0"

# Non-Bytecode Alliance maintained dependencies:
# --------------------------
arbitrary = "1.4.2"
Expand Down Expand Up @@ -441,6 +448,9 @@ rayon = "1.5.3"
regex = "1.9.1"
pin-project-lite = "0.2.14"
sha2 = { version = "0.10.2", default-features = false }
structopt = "0.3.26"
gdbstub = "0.7.10"
gdbstub_arch = "0.3.3"

# =============================================================================
#
Expand Down Expand Up @@ -566,7 +576,7 @@ gc-drc = ["gc", "wasmtime/gc-drc", "wasmtime-cli-flags/gc-drc"]
gc-null = ["gc", "wasmtime/gc-null", "wasmtime-cli-flags/gc-null"]
pulley = ["wasmtime-cli-flags/pulley"]
stack-switching = ["wasmtime/stack-switching", "wasmtime-cli-flags/stack-switching"]
debug = ["wasmtime-cli-flags/debug", "wasmtime/debug", "component-model"]
debug = ["wasmtime-cli-flags/debug", "wasmtime/debug", "component-model", "dep:gdbstub-component-artifact"]

# CLI subcommands for the `wasmtime` executable. See `wasmtime $cmd --help`
# for more information on each subcommand.
Expand Down
26 changes: 26 additions & 0 deletions crates/gdbstub-component/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "wasmtime-internal-gdbstub-component"
version.workspace = true
authors.workspace = true
edition.workspace = true
license = "Apache-2.0 WITH LLVM-exception"
description = "gdbstub debug-component adapter"
publish = false

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = { workspace = true, features = ["macros"] }
anyhow = { workspace = true }
structopt = { workspace = true }
wstd = { workspace = true }
wasip2 = { workspace = true }
futures = { workspace = true, default-features = true }
gdbstub = { workspace = true }
gdbstub_arch = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }

[lints]
workspace = true
10 changes: 10 additions & 0 deletions crates/gdbstub-component/artifact/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "wasmtime-internal-gdbstub-component-artifact"
version.workspace = true
authors.workspace = true
edition.workspace = true
license = "Apache-2.0 WITH LLVM-exception"
publish = false

[lints]
workspace = true
63 changes: 63 additions & 0 deletions crates/gdbstub-component/artifact/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::env;
use std::path::PathBuf;
use std::process::Command;

fn main() {
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());

let mut cmd = cargo();
cmd.arg("build")
.arg("--release")
.arg("--target=wasm32-wasip2")
.arg("--package=wasmtime-internal-gdbstub-component")
.env("CARGO_TARGET_DIR", &out_dir)
.env("RUSTFLAGS", rustflags())
.env_remove("CARGO_ENCODED_RUSTFLAGS");
eprintln!("running: {cmd:?}");
let status = cmd.status().unwrap();
assert!(status.success());

let wasm = out_dir
.join("wasm32-wasip2")
.join("release")
.join("wasmtime_internal_gdbstub_component.wasm");

// Read dep-info to get proper rerun-if-changed directives.
let deps_file = wasm.with_extension("d");
if let Ok(contents) = std::fs::read_to_string(&deps_file) {
for line in contents.lines() {
let Some(pos) = line.find(": ") else {
continue;
};
let line = &line[pos + 2..];
let mut parts = line.split_whitespace();
while let Some(part) = parts.next() {
let mut file = part.to_string();
while file.ends_with('\\') {
file.pop();
file.push(' ');
file.push_str(parts.next().unwrap());
}
println!("cargo:rerun-if-changed={file}");
}
}
}

let generated = format!("pub const GDBSTUB_COMPONENT: &[u8] = include_bytes!({wasm:?});\n");
std::fs::write(out_dir.join("gen.rs"), generated).unwrap();
}

fn cargo() -> Command {
let mut cargo = Command::new("cargo");
if std::env::var("CARGO_CFG_MIRI").is_ok() {
cargo.env_remove("RUSTC").env_remove("RUSTC_WRAPPER");
}
cargo
}

fn rustflags() -> &'static str {
match option_env!("RUSTFLAGS") {
Some(s) if s.contains("-D warnings") => "-D warnings",
_ => "",
}
}
1 change: 1 addition & 0 deletions crates/gdbstub-component/artifact/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/gen.rs"));
186 changes: 186 additions & 0 deletions crates/gdbstub-component/src/addr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! Synthetic Wasm address space expected by the gdbstub Wasm
//! extensions.

use crate::api::{Debuggee, Frame, Memory, Module};
use anyhow::Result;
use gdbstub_arch::wasm::addr::{WasmAddr, WasmAddrType};
use std::collections::{HashMap, hash_map::Entry};

/// Representation of the synthesized Wasm address space.
pub struct AddrSpace {
module_ids: HashMap<u64, u32>,
memory_ids: HashMap<u64, u32>,
modules: Vec<Module>,
module_bytecode: Vec<Vec<u8>>,
memories: Vec<Memory>,
}

/// The result of a lookup in the address space.
pub enum AddrSpaceLookup<'a> {
Module {
module: &'a Module,
bytecode: &'a [u8],
offset: u32,
},
Memory {
memory: &'a Memory,
offset: u32,
},
Empty,
}

impl AddrSpace {
pub fn new() -> Self {
AddrSpace {
module_ids: HashMap::new(),
modules: vec![],
module_bytecode: vec![],
memory_ids: HashMap::new(),
memories: vec![],
}
}

fn module_id(&mut self, m: &Module) -> u32 {
match self.module_ids.entry(m.unique_id()) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => {
let id = u32::try_from(self.modules.len()).unwrap();
let bytecode = m.bytecode().unwrap_or(vec![]);
self.module_bytecode.push(bytecode);
self.modules.push(m.clone());
*v.insert(id)
}
}
}

fn memory_id(&mut self, m: &Memory) -> u32 {
match self.memory_ids.entry(m.unique_id()) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => {
let id = u32::try_from(self.memories.len()).unwrap();
self.memories.push(m.clone());
*v.insert(id)
}
}
}

/// Update/create new mappings so that all modules and instances'
/// memories in the debuggee have mappings.
pub fn update(&mut self, d: &Debuggee) -> Result<()> {
for module in d.all_modules() {
let _ = self.module_id(&module);
}
for instance in d.all_instances() {
let mut idx = 0;
loop {
if let Ok(m) = instance.get_memory(d, idx) {
let _ = self.memory_id(&m);
idx += 1;
} else {
break;
}
}
}
Ok(())
}

/// Iterate over the base `WasmAddr` of every registered module.
pub fn module_base_addrs(&self) -> impl Iterator<Item = WasmAddr> + '_ {
(0..self.modules.len())
.map(|idx| WasmAddr::new(WasmAddrType::Object, u32::try_from(idx).unwrap(), 0).unwrap())
}

/// Build the GDB memory-map XML describing all known regions.
///
/// Module bytecode regions are reported as `rom` (read-only), and
/// linear memories as `ram` (read-write).
pub fn memory_map_xml(&self, debuggee: &Debuggee) -> String {
use std::fmt::Write;
let mut xml = String::from(
"<?xml version=\"1.0\"?><!DOCTYPE memory-map SYSTEM \"memory-map.dtd\"><memory-map>",
);
for (idx, bc) in self.module_bytecode.iter().enumerate() {
let start =
WasmAddr::new(WasmAddrType::Object, u32::try_from(idx).unwrap(), 0).unwrap();
let len = bc.len();
if len > 0 {
write!(
xml,
"<memory type=\"rom\" start=\"0x{:x}\" length=\"0x{:x}\"/>",
start.as_raw(),
len
)
.unwrap();
}
}
for (idx, mem) in self.memories.iter().enumerate() {
let start =
WasmAddr::new(WasmAddrType::Memory, u32::try_from(idx).unwrap(), 0).unwrap();
let len = mem.size_bytes(debuggee);
if len > 0 {
write!(
xml,
"<memory type=\"ram\" start=\"0x{:x}\" length=\"0x{:x}\"/>",
start.as_raw(),
len
)
.unwrap();
}
}
xml.push_str("</memory-map>");
xml
}

pub fn frame_to_pc(&self, frame: &Frame, debuggee: &Debuggee) -> WasmAddr {
let module = frame.get_instance(debuggee).unwrap().get_module(debuggee);
let &module_id = self
.module_ids
.get(&module.unique_id())
.expect("module not found in addr space");
let pc = frame.get_pc(debuggee).unwrap();
WasmAddr::new(WasmAddrType::Object, module_id, pc).unwrap()
}

pub fn frame_to_return_addr(&self, frame: &Frame, debuggee: &Debuggee) -> Option<WasmAddr> {
let module = frame.get_instance(debuggee).unwrap().get_module(debuggee);
let &module_id = self
.module_ids
.get(&module.unique_id())
.expect("module not found in addr space");
let ret_pc = frame.get_pc(debuggee).ok()?;
Some(WasmAddr::new(WasmAddrType::Object, module_id, ret_pc).unwrap())
}

pub fn lookup(&self, addr: WasmAddr, d: &Debuggee) -> AddrSpaceLookup<'_> {
let index = usize::try_from(addr.module_index()).unwrap();
match addr.addr_type() {
WasmAddrType::Object => {
if index >= self.modules.len() {
return AddrSpaceLookup::Empty;
}
let bytecode = &self.module_bytecode[index];
if addr.offset() >= u32::try_from(bytecode.len()).unwrap() {
return AddrSpaceLookup::Empty;
}
AddrSpaceLookup::Module {
module: &self.modules[index],
bytecode,
offset: addr.offset(),
}
}
WasmAddrType::Memory => {
if index >= self.memories.len() {
return AddrSpaceLookup::Empty;
}
let size = self.memories[index].size_bytes(d);
if u64::from(addr.offset()) >= size {
return AddrSpaceLookup::Empty;
}
AddrSpaceLookup::Memory {
memory: &self.memories[index],
offset: addr.offset(),
}
}
}
}
}
44 changes: 44 additions & 0 deletions crates/gdbstub-component/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Bindings for Wasmtime's debugger API.

use wstd::runtime::AsyncPollable;

wit_bindgen::generate!({
world: "bytecodealliance:wasmtime/debug-main",
path: "../debugger/wit",
with: {
"wasi:io/poll@0.2.6": wasip2::io::poll,
}
});
pub(crate) use bytecodealliance::wasmtime::debuggee::*;

/// One "resumption", or period of execution, in the debuggee.
pub struct Resumption {
future: EventFuture,
pollable: Option<AsyncPollable>,
}

impl Resumption {
pub fn continue_(d: &Debuggee, r: ResumptionValue) -> Self {
let future = d.continue_(r);
let pollable = Some(AsyncPollable::new(future.subscribe()));
Resumption { future, pollable }
}

pub fn single_step(d: &Debuggee, r: ResumptionValue) -> Self {
let future = d.single_step(r);
let pollable = Some(AsyncPollable::new(future.subscribe()));
Resumption { future, pollable }
}

pub async fn wait(&mut self) {
if let Some(pollable) = self.pollable.as_mut() {
pollable.wait_for().await;
}
}

pub fn result(mut self, d: &Debuggee) -> std::result::Result<Event, Error> {
// Drop the pollable first, since it's a child resource.
let _ = self.pollable.take();
EventFuture::finish(self.future, d)
}
}
Loading
Loading