-
Notifications
You must be signed in to change notification settings - Fork 1.8k
debugger: expose shared memories to gdbstub #14087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<Memory>, d: Resource<Debuggee>) -> Result<u64> { | ||
| 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<Memory>, | ||
| d: Resource<Debuggee>, | ||
| ) -> Result<u64> { | ||
| 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<Debuggee>, | ||
| delta_bytes: u64, | ||
| ) -> Result<u64> { | ||
| 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<Vec<u8>> { | ||
| 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<u8>, | ||
| ) -> 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<Debuggee>, | ||
| addr: u64, | ||
| ) -> Result<u8> { | ||
| 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<Debuggee>, | ||
| addr: u64, | ||
| ) -> Result<u16> { | ||
| 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<Debuggee>, | ||
| addr: u64, | ||
| ) -> Result<u32> { | ||
| 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<Debuggee>, | ||
| addr: u64, | ||
| ) -> Result<u64> { | ||
| 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<Memory>) -> Result<Resource<Memory>> { | ||
| let memory = *self.get(&self_)?; | ||
| let memory = self.get(&self_)?.clone(); | ||
| Ok(self.push(memory)?) | ||
| } | ||
|
|
||
| async fn unique_id(&mut self, self_: Resource<Memory>) -> Result<u64> { | ||
| Ok(self.get(&self_)?.debug_index_in_store()) | ||
| Ok(self.get(&self_)?.unique_id()) | ||
| } | ||
|
|
||
| async fn drop(&mut self, rep: Resource<Memory>) -> Result<()> { | ||
|
|
@@ -1049,3 +1070,86 @@ impl wit::Host for ResourceTable { | |
| err.downcast() | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the wrong level at which to test; it's extremely verbose and a lot of machinery to poke inside resource-tables and such. We have an integration test that starts up Wasmtime with the gdbstub component and runs LLDB against it. Perhaps you can write a test there with a custom-in-WAT debuggee that has a shared memory, and then show that you can print its memory contents with LLDB?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (On reading further, I see that you did that -- so, please just delete this whole unit test) |
||
| 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 = <ResourceTable as wit::HostInstance>::get_memory( | ||
| &mut table, | ||
| instance, | ||
| Resource::new_borrow(debuggee.rep()), | ||
| 0, | ||
| ) | ||
| .await?; | ||
| let size = <ResourceTable as wit::HostMemory>::size_bytes( | ||
| &mut table, | ||
| Resource::new_borrow(memory.rep()), | ||
| Resource::new_borrow(debuggee.rep()), | ||
| ) | ||
| .await?; | ||
| assert_eq!(size, 65536); | ||
|
|
||
| let page_size = <ResourceTable as wit::HostMemory>::page_size_bytes( | ||
| &mut table, | ||
| Resource::new_borrow(memory.rep()), | ||
| Resource::new_borrow(debuggee.rep()), | ||
| ) | ||
| .await?; | ||
| assert_eq!(page_size, 65536); | ||
| let old_size = <ResourceTable as wit::HostMemory>::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 = <ResourceTable as wit::HostMemory>::size_bytes( | ||
| &mut table, | ||
| Resource::new_borrow(memory.rep()), | ||
| Resource::new_borrow(debuggee.rep()), | ||
| ) | ||
| .await?; | ||
| assert_eq!(size, 2 * page_size); | ||
|
|
||
| <ResourceTable as wit::HostMemory>::set_u32( | ||
| &mut table, | ||
| Resource::new_borrow(memory.rep()), | ||
| Resource::new_borrow(debuggee.rep()), | ||
| 0, | ||
| 0x1234_5678, | ||
| ) | ||
| .await?; | ||
| let value = <ResourceTable as wit::HostMemory>::get_u32( | ||
| &mut table, | ||
| Resource::new_borrow(memory.rep()), | ||
| Resource::new_borrow(debuggee.rep()), | ||
| 0, | ||
| ) | ||
| .await?; | ||
| assert_eq!(value, 0x1234_5678); | ||
|
|
||
| <ResourceTable as wit::HostDebuggee>::drop(&mut table, debuggee).await?; | ||
| Ok(()) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to expose an internal pointer to the debug component like this -- it feels like an encapsulation violation. Instead we should add
debug_index_in_storetoSharedMemoryas we did forMemory.