From 8b36f007384deecb60d3e369c3b109520bf02163 Mon Sep 17 00:00:00 2001 From: AlisinaDevelo Date: Thu, 6 Aug 2026 21:56:02 +0200 Subject: [PATCH] debugger: expose shared memories to gdbstub --- crates/debugger/Cargo.toml | 2 +- crates/debugger/src/host/api.rs | 138 ++++++++++++-- crates/debugger/src/host/bindings.rs | 2 +- crates/debugger/src/host/opaque.rs | 269 +++++++++++++++++++-------- tests/all/guest_debug/mod.rs | 65 ++++++- 5 files changed, 376 insertions(+), 100 deletions(-) diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 9aeb815d969a..a795f0bbb25b 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true workspace = true [dependencies] -wasmtime = { workspace = true, features = ["debug", "std", "async", "component-model", "gc", "gc-drc"] } +wasmtime = { workspace = true, features = ["debug", "std", "async", "component-model", "gc", "gc-drc", "threads"] } wasmtime-wasi = { workspace = true } wasmtime-wasi-io = { workspace = true } tokio = { workspace = true, features = ["rt", "sync", "macros"] } diff --git a/crates/debugger/src/host/api.rs b/crates/debugger/src/host/api.rs index 064c6b15d3f9..0888057427aa 100644 --- a/crates/debugger/src/host/api.rs +++ b/crates/debugger/src/host/api.rs @@ -7,11 +7,32 @@ use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use wasmtime::{ - Engine, ExnRef, FrameHandle, Func, Global, Instance, Memory, Module, OwnedRooted, Result, - Table, Tag, Val, component::Resource, component::ResourceTable, + Engine, ExnRef, FrameHandle, Func, Global, Instance, Module, OwnedRooted, Result, Table, Tag, + Val, component::Resource, component::ResourceTable, }; use wasmtime_wasi::p2::{DynPollable, Pollable, subscribe}; +/// A memory exposed through the debugger API. +/// +/// The component-level resource is deliberately shared by both kinds of +/// Wasmtime linear memory. Shared memories use their own host API and do not +/// belong to a `Store`, unlike ordinary memories. +#[derive(Clone)] +pub enum Memory { + Unshared(wasmtime::Memory), + Shared(wasmtime::SharedMemory), +} + +impl Memory { + fn unique_id(&self) -> u64 { + match self { + Memory::Unshared(memory) => memory.debug_index_in_store(), + // A shared memory's base address is stable for its lifetime. + Memory::Shared(memory) => memory.data().as_ptr() as usize as u64, + } + } +} + /// Representation of one debuggee: a store with debugged code inside, /// under the control of the debugger. pub struct Debuggee { @@ -446,7 +467,7 @@ impl wit::HostModule for ResourceTable { impl wit::HostMemory for ResourceTable { async fn size_bytes(&mut self, self_: Resource, d: Resource) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_size_bytes(memory).await } @@ -456,7 +477,7 @@ impl wit::HostMemory for ResourceTable { self_: Resource, d: Resource, ) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_page_size(memory).await } @@ -467,7 +488,7 @@ impl wit::HostMemory for ResourceTable { d: Resource, delta_bytes: u64, ) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_grow(memory, delta_bytes).await } @@ -479,7 +500,7 @@ impl wit::HostMemory for ResourceTable { addr: u64, len: u64, ) -> Result> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; Ok(d.memory_read_bytes(memory, addr, len) .await? @@ -493,7 +514,7 @@ impl wit::HostMemory for ResourceTable { addr: u64, bytes: Vec, ) -> Result<()> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_write_bytes(memory, addr, bytes) .await? @@ -507,7 +528,7 @@ impl wit::HostMemory for ResourceTable { d: Resource, addr: u64, ) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; Ok(d.memory_read_u8(memory, addr) .await? @@ -520,7 +541,7 @@ impl wit::HostMemory for ResourceTable { d: Resource, addr: u64, ) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; Ok(d.memory_read_u16(memory, addr) .await? @@ -533,7 +554,7 @@ impl wit::HostMemory for ResourceTable { d: Resource, addr: u64, ) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; Ok(d.memory_read_u32(memory, addr) .await? @@ -546,7 +567,7 @@ impl wit::HostMemory for ResourceTable { d: Resource, addr: u64, ) -> Result { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; Ok(d.memory_read_u64(memory, addr) .await? @@ -560,7 +581,7 @@ impl wit::HostMemory for ResourceTable { addr: u64, value: u8, ) -> Result<()> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_write_u8(memory, addr, value) .await? @@ -575,7 +596,7 @@ impl wit::HostMemory for ResourceTable { addr: u64, value: u16, ) -> Result<()> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_write_u16(memory, addr, value) .await? @@ -590,7 +611,7 @@ impl wit::HostMemory for ResourceTable { addr: u64, value: u32, ) -> Result<()> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_write_u32(memory, addr, value) .await? @@ -605,7 +626,7 @@ impl wit::HostMemory for ResourceTable { addr: u64, value: u64, ) -> Result<()> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); let d = debugger(self, &d)?; d.memory_write_u64(memory, addr, value) .await? @@ -614,12 +635,12 @@ impl wit::HostMemory for ResourceTable { } async fn clone(&mut self, self_: Resource) -> Result> { - let memory = *self.get(&self_)?; + let memory = self.get(&self_)?.clone(); Ok(self.push(memory)?) } async fn unique_id(&mut self, self_: Resource) -> Result { - Ok(self.get(&self_)?.debug_index_in_store()) + Ok(self.get(&self_)?.unique_id()) } async fn drop(&mut self, rep: Resource) -> Result<()> { @@ -1049,3 +1070,86 @@ impl wit::Host for ResourceTable { err.downcast() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::host::add_debuggee; + use wasmtime::{Config, Engine, Instance, Module, Store}; + + #[cfg_attr(miri, ignore)] + #[cfg(target_pointer_width = "64")] + #[tokio::test] + async fn shared_memory_is_exposed_to_debugger() -> wasmtime::Result<()> { + let mut config = Config::new(); + config.wasm_threads(true); + config.shared_memory(true); + config.guest_debug(true); + let engine = Engine::new(&config)?; + let module = Module::new(&engine, r#"(module (memory (export "memory") 1 2 shared))"#)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new_async(&mut store, &module, &[]).await?; + + let debuggee = crate::Debuggee::new(store, |_store| Box::pin(async { Ok(()) })); + let mut table = ResourceTable::new(); + let debuggee = add_debuggee(&mut table, debuggee)?; + let instance = table.push(instance)?; + let memory = ::get_memory( + &mut table, + instance, + Resource::new_borrow(debuggee.rep()), + 0, + ) + .await?; + let size = ::size_bytes( + &mut table, + Resource::new_borrow(memory.rep()), + Resource::new_borrow(debuggee.rep()), + ) + .await?; + assert_eq!(size, 65536); + + let page_size = ::page_size_bytes( + &mut table, + Resource::new_borrow(memory.rep()), + Resource::new_borrow(debuggee.rep()), + ) + .await?; + assert_eq!(page_size, 65536); + let old_size = ::grow_to_bytes( + &mut table, + Resource::new_borrow(memory.rep()), + Resource::new_borrow(debuggee.rep()), + page_size, + ) + .await?; + assert_eq!(old_size, size); + let size = ::size_bytes( + &mut table, + Resource::new_borrow(memory.rep()), + Resource::new_borrow(debuggee.rep()), + ) + .await?; + assert_eq!(size, 2 * page_size); + + ::set_u32( + &mut table, + Resource::new_borrow(memory.rep()), + Resource::new_borrow(debuggee.rep()), + 0, + 0x1234_5678, + ) + .await?; + let value = ::get_u32( + &mut table, + Resource::new_borrow(memory.rep()), + Resource::new_borrow(debuggee.rep()), + 0, + ) + .await?; + assert_eq!(value, 0x1234_5678); + + ::drop(&mut table, debuggee).await?; + Ok(()) + } +} diff --git a/crates/debugger/src/host/bindings.rs b/crates/debugger/src/host/bindings.rs index fbf698bf2819..91263b8058ad 100644 --- a/crates/debugger/src/host/bindings.rs +++ b/crates/debugger/src/host/bindings.rs @@ -23,7 +23,7 @@ wasmtime::component::bindgen!({ "bytecodealliance:wasmtime/debuggee.module": wasmtime::Module, "bytecodealliance:wasmtime/debuggee.table": wasmtime::Table, "bytecodealliance:wasmtime/debuggee.global": wasmtime::Global, - "bytecodealliance:wasmtime/debuggee.memory": wasmtime::Memory, + "bytecodealliance:wasmtime/debuggee.memory": super::api::Memory, "bytecodealliance:wasmtime/debuggee.wasm-tag": wasmtime::Tag, "bytecodealliance:wasmtime/debuggee.wasm-func": wasmtime::Func, "bytecodealliance:wasmtime/debuggee.wasm-exception": super::api::WasmException, diff --git a/crates/debugger/src/host/opaque.rs b/crates/debugger/src/host/opaque.rs index cce93b9d96c5..75d1c1148c6a 100644 --- a/crates/debugger/src/host/opaque.rs +++ b/crates/debugger/src/host/opaque.rs @@ -2,12 +2,52 @@ //! within a resource. use crate::host::wit; -use crate::host::{api::WasmValue, bindings::val_type_to_wasm_type}; +use crate::host::{ + api::{Memory, WasmValue}, + bindings::val_type_to_wasm_type, +}; +use std::sync::atomic::{AtomicU8, Ordering}; use wasmtime::{ - Engine, ExnRef, ExnRefPre, ExnType, FrameHandle, Func, FuncType, Global, Instance, Memory, - Module, OwnedRooted, Result, Table, Tag, TagType, Val, ValType, + Engine, ExnRef, ExnRefPre, ExnType, FrameHandle, Func, FuncType, Global, Instance, Module, + OwnedRooted, Result, Table, Tag, TagType, Val, ValType, }; +fn shared_memory_read_bytes( + memory: &wasmtime::SharedMemory, + addr: u64, + len: u64, +) -> Option> { + let addr = usize::try_from(addr).ok()?; + let len = usize::try_from(len).ok()?; + let end = addr.checked_add(len)?; + let data = memory.data().get(addr..end)?; + Some( + data.iter() + .map(|byte| { + // SAFETY: `byte` is a valid byte from `SharedMemory::data`, + // and `AtomicU8` has the same alignment requirement as `u8`. + unsafe { AtomicU8::from_ptr(byte.get()).load(Ordering::Relaxed) } + }) + .collect(), + ) +} + +fn shared_memory_write_bytes( + memory: &wasmtime::SharedMemory, + addr: u64, + bytes: &[u8], +) -> Option<()> { + let addr = usize::try_from(addr).ok()?; + let end = addr.checked_add(bytes.len())?; + let data = memory.data().get(addr..end)?; + for (byte, value) in data.iter().zip(bytes) { + // SAFETY: `byte` is a valid byte from `SharedMemory::data`, and + // `AtomicU8` has the same alignment requirement as `u8`. + unsafe { AtomicU8::from_ptr(byte.get()).store(*value, Ordering::Relaxed) }; + } + Some(()) +} + /// Type-erased interface to the `Debugger` implementing all /// functionality necessary for the interfaces here. This needs to be /// type-erased because the host-side resource APIs do not support @@ -168,8 +208,17 @@ impl OpaqueDebugger for crate::Debuggee { instance: Instance, idx: u32, ) -> Result> { - self.with_store(move |mut store| instance.debug_memory(&mut store, idx)) - .await + self.with_store(move |mut store| { + instance + .debug_memory(&mut store, idx) + .map(Memory::Unshared) + .or_else(|| { + instance + .debug_shared_memory(&mut store, idx) + .map(Memory::Shared) + }) + }) + .await } async fn instance_get_global( @@ -197,24 +246,39 @@ impl OpaqueDebugger for crate::Debuggee { } async fn memory_size_bytes(&mut self, memory: Memory) -> Result { - self.with_store(move |store| u64::try_from(memory.data_size(&store)).unwrap()) - .await + self.with_store(move |store| match memory { + Memory::Unshared(memory) => u64::try_from(memory.data_size(&store)).unwrap(), + Memory::Shared(memory) => u64::try_from(memory.data_size()).unwrap(), + }) + .await } async fn memory_page_size(&mut self, memory: Memory) -> Result { - self.with_store(move |store| memory.page_size(&store)).await + self.with_store(move |store| match memory { + Memory::Unshared(memory) => memory.page_size(&store), + Memory::Shared(memory) => memory.page_size(), + }) + .await } async fn memory_grow(&mut self, memory: Memory, delta_bytes: u64) -> Result { self.with_store(move |mut store| -> Result { - let page_size = memory.page_size(&store); + let page_size = match &memory { + Memory::Unshared(memory) => memory.page_size(&store), + Memory::Shared(memory) => memory.page_size(), + }; if delta_bytes & (page_size - 1) != 0 { return Err(wit::Error::MemoryGrowFailure.into()); } let delta_pages = delta_bytes / page_size; - let old_pages = memory - .grow(&mut store, delta_pages) - .map_err(|_| wit::Error::MemoryGrowFailure)?; + let old_pages = match memory { + Memory::Unshared(memory) => memory + .grow(&mut store, delta_pages) + .map_err(|_| wit::Error::MemoryGrowFailure)?, + Memory::Shared(memory) => memory + .grow(delta_pages) + .map_err(|_| wit::Error::MemoryGrowFailure)?, + }; Ok(old_pages * page_size) }) .await? @@ -226,11 +290,15 @@ impl OpaqueDebugger for crate::Debuggee { addr: u64, len: u64, ) -> Result>> { - self.with_store(move |store| { - let data = memory.data(&store); - let addr = usize::try_from(addr).unwrap(); - let len = usize::try_from(len).unwrap(); - data.get(addr..addr + len).map(|s| s.to_vec()) + self.with_store(move |store| match memory { + Memory::Unshared(memory) => { + let data = memory.data(&store); + let addr = usize::try_from(addr).ok()?; + let len = usize::try_from(len).ok()?; + let end = addr.checked_add(len)?; + data.get(addr..end).map(|s| s.to_vec()) + } + Memory::Shared(memory) => shared_memory_read_bytes(&memory, addr, len), }) .await } @@ -241,62 +309,90 @@ impl OpaqueDebugger for crate::Debuggee { addr: u64, bytes: Vec, ) -> Result> { - self.with_store(move |mut store| { - let data = memory.data_mut(&mut store); - let addr = usize::try_from(addr).unwrap(); - let dest = data.get_mut(addr..addr + bytes.len())?; - dest.copy_from_slice(&bytes); - Some(()) + self.with_store(move |mut store| match memory { + Memory::Unshared(memory) => { + let data = memory.data_mut(&mut store); + let addr = usize::try_from(addr).ok()?; + let end = addr.checked_add(bytes.len())?; + let dest = data.get_mut(addr..end)?; + dest.copy_from_slice(&bytes); + Some(()) + } + Memory::Shared(memory) => shared_memory_write_bytes(&memory, addr, &bytes), }) .await } async fn memory_read_u8(&mut self, memory: Memory, addr: u64) -> Result> { - self.with_store(move |store| { - let data = memory.data(&store); - let addr = usize::try_from(addr).unwrap(); - Some(*data.get(addr)?) + self.with_store(move |store| match memory { + Memory::Unshared(memory) => { + let data = memory.data(&store); + let addr = usize::try_from(addr).ok()?; + Some(*data.get(addr)?) + } + Memory::Shared(memory) => Some(shared_memory_read_bytes(&memory, addr, 1)?[0]), }) .await } async fn memory_read_u16(&mut self, memory: Memory, addr: u64) -> Result> { - self.with_store(move |store| { - let data = memory.data(&store); - let addr = usize::try_from(addr).unwrap(); - Some(u16::from_le_bytes([*data.get(addr)?, *data.get(addr + 1)?])) + self.with_store(move |store| match memory { + Memory::Unshared(memory) => { + let data = memory.data(&store); + let addr = usize::try_from(addr).ok()?; + Some(u16::from_le_bytes([*data.get(addr)?, *data.get(addr + 1)?])) + } + Memory::Shared(memory) => Some(u16::from_le_bytes( + shared_memory_read_bytes(&memory, addr, 2)? + .try_into() + .unwrap(), + )), }) .await } async fn memory_read_u32(&mut self, memory: Memory, addr: u64) -> Result> { - self.with_store(move |store| { - let data = memory.data(&store); - let addr = usize::try_from(addr).unwrap(); - Some(u32::from_le_bytes([ - *data.get(addr)?, - *data.get(addr + 1)?, - *data.get(addr + 2)?, - *data.get(addr + 3)?, - ])) + self.with_store(move |store| match memory { + Memory::Unshared(memory) => { + let data = memory.data(&store); + let addr = usize::try_from(addr).ok()?; + Some(u32::from_le_bytes([ + *data.get(addr)?, + *data.get(addr + 1)?, + *data.get(addr + 2)?, + *data.get(addr + 3)?, + ])) + } + Memory::Shared(memory) => Some(u32::from_le_bytes( + shared_memory_read_bytes(&memory, addr, 4)? + .try_into() + .unwrap(), + )), }) .await } async fn memory_read_u64(&mut self, memory: Memory, addr: u64) -> Result> { - self.with_store(move |store| { - let data = memory.data(&store); - let addr = usize::try_from(addr).unwrap(); - Some(u64::from_le_bytes([ - *data.get(addr)?, - *data.get(addr + 1)?, - *data.get(addr + 2)?, - *data.get(addr + 3)?, - *data.get(addr + 4)?, - *data.get(addr + 5)?, - *data.get(addr + 6)?, - *data.get(addr + 7)?, - ])) + self.with_store(move |store| match memory { + Memory::Unshared(memory) => { + let data = memory.data(&store); + let addr = usize::try_from(addr).ok()?; + Some(u64::from_le_bytes([ + *data.get(addr)?, + *data.get(addr + 1)?, + *data.get(addr + 2)?, + *data.get(addr + 3)?, + *data.get(addr + 4)?, + *data.get(addr + 5)?, + *data.get(addr + 6)?, + *data.get(addr + 7)?, + ])) + } + Memory::Shared(memory) => Some(u64::from_le_bytes( + shared_memory_read_bytes(&memory, addr, 8)? + .try_into() + .unwrap(), + )), }) .await } @@ -307,11 +403,14 @@ impl OpaqueDebugger for crate::Debuggee { addr: u64, value: u8, ) -> Result> { - self.with_store(move |mut store| { - let data = memory.data_mut(&mut store); - let addr = usize::try_from(addr).unwrap(); - *data.get_mut(addr)? = value; - Some(()) + self.with_store(move |mut store| match memory { + Memory::Unshared(memory) => { + let data = memory.data_mut(&mut store); + let addr = usize::try_from(addr).ok()?; + *data.get_mut(addr)? = value; + Some(()) + } + Memory::Shared(memory) => shared_memory_write_bytes(&memory, addr, &[value]), }) .await } @@ -322,12 +421,18 @@ impl OpaqueDebugger for crate::Debuggee { addr: u64, value: u16, ) -> Result> { - self.with_store(move |mut store| { - let data = memory.data_mut(&mut store); - let addr = usize::try_from(addr).unwrap(); - data.get_mut(addr..(addr + 2))? - .copy_from_slice(&value.to_le_bytes()); - Some(()) + self.with_store(move |mut store| match memory { + Memory::Unshared(memory) => { + let data = memory.data_mut(&mut store); + let addr = usize::try_from(addr).ok()?; + let end = addr.checked_add(2)?; + data.get_mut(addr..end)? + .copy_from_slice(&value.to_le_bytes()); + Some(()) + } + Memory::Shared(memory) => { + shared_memory_write_bytes(&memory, addr, &value.to_le_bytes()) + } }) .await } @@ -338,12 +443,18 @@ impl OpaqueDebugger for crate::Debuggee { addr: u64, value: u32, ) -> Result> { - self.with_store(move |mut store| { - let data = memory.data_mut(&mut store); - let addr = usize::try_from(addr).unwrap(); - data.get_mut(addr..(addr + 4))? - .copy_from_slice(&value.to_le_bytes()); - Some(()) + self.with_store(move |mut store| match memory { + Memory::Unshared(memory) => { + let data = memory.data_mut(&mut store); + let addr = usize::try_from(addr).ok()?; + let end = addr.checked_add(4)?; + data.get_mut(addr..end)? + .copy_from_slice(&value.to_le_bytes()); + Some(()) + } + Memory::Shared(memory) => { + shared_memory_write_bytes(&memory, addr, &value.to_le_bytes()) + } }) .await } @@ -354,12 +465,18 @@ impl OpaqueDebugger for crate::Debuggee { addr: u64, value: u64, ) -> Result> { - self.with_store(move |mut store| { - let data = memory.data_mut(&mut store); - let addr = usize::try_from(addr).unwrap(); - data.get_mut(addr..(addr + 8))? - .copy_from_slice(&value.to_le_bytes()); - Some(()) + self.with_store(move |mut store| match memory { + Memory::Unshared(memory) => { + let data = memory.data_mut(&mut store); + let addr = usize::try_from(addr).ok()?; + let end = addr.checked_add(8)?; + data.get_mut(addr..end)? + .copy_from_slice(&value.to_le_bytes()); + Some(()) + } + Memory::Shared(memory) => { + shared_memory_write_bytes(&memory, addr, &value.to_le_bytes()) + } }) .await } diff --git a/tests/all/guest_debug/mod.rs b/tests/all/guest_debug/mod.rs index 2a1342e597ad..070a7bd7efde 100644 --- a/tests/all/guest_debug/mod.rs +++ b/tests/all/guest_debug/mod.rs @@ -128,18 +128,23 @@ impl WasmtimeWithGdbstub { fn lldb_with_gdbstub_script(port: u16, script: &str) -> Result { let _ = env_logger::try_init(); - let mut cmd = Command::new(lldb_path()); - cmd.arg("--batch"); - cmd.arg("-o").arg(format!( + // A source file keeps LLDB processing commands after a resume command on + // versions that stop processing repeated `-o` options at the next stop. + let mut script_file = tempfile::Builder::new().suffix(".lldb").tempfile()?; + writeln!( + script_file, "process connect --plugin wasm connect://127.0.0.1:{port}" - )); + )?; for line in script.lines() { let line = line.trim(); if !line.is_empty() { - cmd.arg("-o").arg(line); + writeln!(script_file, "{line}")?; } } + let mut cmd = Command::new(lldb_path()); + cmd.args(["--batch", "-s"]).arg(script_file.path()); + eprintln!("Running LLDB: {cmd:?}"); let output = cmd.output()?; @@ -240,6 +245,56 @@ check: fib Ok(()) } +/// Shared linear memories should be readable through the synthetic Wasm +/// address space after the guest stops. +#[test] +#[ignore] +fn guest_debug_cli_shared_memory() -> Result<()> { + let mut module = tempfile::Builder::new().suffix(".wat").tempfile()?; + module.write_all( + br#"(module + (memory (export "memory") 1 1 shared) + (func (export "_start") + i32.const 0 + i32.const 42 + i32.store + unreachable)) +"#, + )?; + let module = module.into_temp_path(); + let module_path = module + .to_str() + .ok_or_else(|| format_err!("temporary module path is not UTF-8"))?; + + let port = free_port(); + let mut wt = WasmtimeWithGdbstub::spawn( + "run", + port, + &["-Ccache=n", "-W", "threads=y,shared-memory=y", module_path], + Duration::from_secs(30), + )?; + + let output = lldb_with_gdbstub_script( + port, + r#" +continue +process plugin packet send qXfer:memory-map:read::0,ffff +memory read --size 4 --format decimal --count 1 0x0 +"#, + )?; + wt.child.kill().ok(); + wt.child.wait()?; + + check_output( + &output, + r#" +check: Result { let mut tcp = TcpStream::connect_timeout(&addr, Duration::from_secs(5))?;