1 //! gdbstub protocol implementation in Wasmtime's debug-main world.
2 
3 mod addr;
4 mod api;
5 mod target;
6 
7 use crate::{
8     addr::AddrSpace,
9     api::{WasmType, WasmValue},
10 };
11 use anyhow::Result;
12 use clap::Parser;
13 use futures::{FutureExt, select};
14 use gdbstub::{
15     common::{Signal, Tid},
16     conn::Connection,
17     stub::{
18         MultiThreadStopReason,
19         state_machine::{GdbStubStateMachine, GdbStubStateMachineInner, state::Running},
20     },
21 };
22 use gdbstub_arch::wasm::addr::WasmAddr;
23 use log::trace;
24 use wstd::{
25     io::{AsyncRead, AsyncWrite},
26     iter::AsyncIterator,
27     net::{TcpListener, TcpStream},
28 };
29 
30 /// Command-line options.
31 #[derive(Parser)]
32 struct Options {
33     /// The TCP address to listen on, in `<addr>:<port>` format.
34     tcp_address: String,
35     /// Verbose logging.
36     #[clap(short = 'v')]
37     verbose: bool,
38 }
39 
40 struct Component;
41 api::export!(Component with_types_in api);
42 
43 impl api::exports::bytecodealliance::wasmtime::debugger::Guest for Component {
debug(d: &api::Debuggee, args: Vec<String>)44     fn debug(d: &api::Debuggee, args: Vec<String>) {
45         let options = Options::parse_from(args);
46         if options.verbose {
47             env_logger::Builder::new()
48                 .filter_level(log::LevelFilter::Trace)
49                 .init();
50         }
51         let mut debugger = Debugger {
52             debuggee: d,
53             tid: Tid::new(1).unwrap(),
54             options,
55             running: None,
56             current_pc: WasmAddr::from_raw(0).unwrap(),
57             interrupt: false,
58             single_stepping: false,
59             frame_cache: vec![],
60             addr_space: AddrSpace::new(),
61         };
62         wstd::runtime::block_on(async {
63             if let Err(e) = debugger.run().await {
64                 trace!("debugger exited with error: {e}");
65             }
66         });
67     }
68 }
69 
70 struct Debugger<'a> {
71     debuggee: &'a api::Debuggee,
72     tid: Tid,
73     options: Options,
74     running: Option<api::Resumption>,
75     addr_space: AddrSpace,
76     interrupt: bool,
77     single_stepping: bool,
78     current_pc: WasmAddr,
79     frame_cache: Vec<api::Frame>,
80 }
81 
82 impl<'a> Debugger<'a> {
run(&mut self) -> Result<()>83     async fn run(&mut self) -> Result<()> {
84         // Load module info from the debuggee. Modules are
85         // pre-registered on the debuggee store before the Debuggee is
86         // created, so they are visible here without needing to
87         // execute any Wasm.
88         self.update_on_stop();
89 
90         let listener = TcpListener::bind(&self.options.tcp_address)
91             .await
92             .expect("Could not bind to TCP port");
93 
94         api::print_debugger_info(&format!(
95             "Debugger listening on {}",
96             self.options.tcp_address,
97         ));
98         api::print_debugger_info(&format!(
99             "In LLDB, attach with: process connect --plugin wasm connect://{}",
100             self.options.tcp_address,
101         ));
102 
103         // Only accept one connection for the run; once the debugger
104         // disconnects, we'll just continue.
105         let Some(connection) = listener.incoming().next().await else {
106             return Ok(());
107         };
108 
109         let gdbconn = Conn::new(connection?);
110         let mut stub = gdbstub::stub::GdbStub::new(gdbconn).run_state_machine(&mut *self)?;
111 
112         // Main loop.
113         'mainloop: loop {
114             match stub {
115                 GdbStubStateMachine::Idle(mut inner) => {
116                     if inner.borrow_conn().flush().await.is_err() {
117                         // Connection closed or other outbound error.
118                         break 'mainloop;
119                     }
120 
121                     // Wait for an inbound network byte.
122                     let Some(byte) = inner.borrow_conn().read_byte().await? else {
123                         inner.borrow_conn().flush().await?;
124                         log::info!("Connection closed; debugger detached.");
125                         break 'mainloop;
126                     };
127 
128                     stub = inner.incoming_data(self, byte)?;
129                 }
130 
131                 GdbStubStateMachine::Running(mut inner) => {
132                     if inner.borrow_conn().flush().await.is_err() {
133                         // Connection closed or other outbound error.
134                         break 'mainloop;
135                     }
136 
137                     // Wait for either a resumption or a byte from the
138                     // connection.
139                     let resumption = self
140                         .running
141                         .as_mut()
142                         .expect("In Running state, we must have a resumption future");
143                     select! {
144                         _ = resumption.wait().fuse() => {
145                             let resumption = self.running.take().unwrap();
146                             let event = resumption.result(self.debuggee)?;
147                             stub = self.handle_event(event, inner).await?;
148                         }
149                         byte = inner.borrow_conn().read_byte().fuse() => {
150                             let Some(byte) = byte? else {
151                                 // Eat any connection-closed errors on
152                                 // the outbound flush.
153                                 let _ = inner.borrow_conn().flush().await;
154                                 // Connection closed.
155                                 log::info!("Connection closed; debugger detached.");
156                                 break 'mainloop;
157                             };
158                             stub = inner.incoming_data(&mut *self, byte)?;
159                         }
160                     }
161                 }
162                 GdbStubStateMachine::CtrlCInterrupt(mut inner) => {
163                     if inner.borrow_conn().flush().await.is_err() {
164                         // Connection error: break.
165                         break 'mainloop;
166                     }
167                     stub = inner.interrupt_handled(self, None::<MultiThreadStopReason<u64>>)?;
168                 }
169                 GdbStubStateMachine::Disconnected(mut inner) => {
170                     // Eat any connection-closed errors -- we are
171                     // already in Disconnected state.
172                     let _ = inner.borrow_conn().flush().await;
173                     break 'mainloop;
174                 }
175             }
176         }
177 
178         Ok(())
179     }
180 
start_continue(&mut self, resumption: api::ResumptionValue)181     fn start_continue(&mut self, resumption: api::ResumptionValue) {
182         assert!(self.running.is_none());
183         trace!("continuing");
184         self.single_stepping = false;
185         self.running = Some(api::Resumption::continue_(self.debuggee, resumption));
186     }
187 
start_single_step(&mut self, resumption: api::ResumptionValue)188     fn start_single_step(&mut self, resumption: api::ResumptionValue) {
189         assert!(self.running.is_none());
190         trace!("single-stepping");
191         self.single_stepping = true;
192         self.running = Some(api::Resumption::single_step(self.debuggee, resumption));
193     }
194 
update_on_stop(&mut self)195     fn update_on_stop(&mut self) {
196         self.addr_space.update(self.debuggee).unwrap();
197 
198         // Cache all frame handles for the duration of this stop.
199         // The Wasm trait methods take `&self` and need access to
200         // frames by depth, so we eagerly walk the full stack here.
201         self.frame_cache.clear();
202         let mut next = self.debuggee.exit_frames().into_iter().next();
203         while let Some(f) = next {
204             next = f.parent_frame(self.debuggee).unwrap();
205             self.frame_cache.push(f);
206         }
207 
208         if let Some(f) = self.frame_cache.first() {
209             self.current_pc = self.addr_space.frame_to_pc(f, self.debuggee);
210         } else {
211             self.current_pc = WasmAddr::from_raw(0).unwrap();
212         }
213     }
214 
handle_event<'b>( &mut self, event: api::Event, inner: GdbStubStateMachineInner<'b, Running, Self, Conn>, ) -> Result<GdbStubStateMachine<'b, Self, Conn>>215     async fn handle_event<'b>(
216         &mut self,
217         event: api::Event,
218         inner: GdbStubStateMachineInner<'b, Running, Self, Conn>,
219     ) -> Result<GdbStubStateMachine<'b, Self, Conn>> {
220         match event {
221             api::Event::Complete => {
222                 trace!("Event::Complete");
223                 let pc_bytes = self.current_pc.as_raw().to_le_bytes();
224                 let mut regs = core::iter::once((
225                     gdbstub_arch::wasm::reg::id::WasmRegId::Pc,
226                     pc_bytes.as_slice(),
227                 ));
228                 Ok(inner.report_stop_with_regs(
229                     self,
230                     MultiThreadStopReason::Exited(0),
231                     &mut regs,
232                 )?)
233             }
234             api::Event::Breakpoint => {
235                 trace!(
236                     "Event::Breakpoint; single_stepping = {}",
237                     self.single_stepping
238                 );
239                 self.update_on_stop();
240                 let stop_reason = if self.single_stepping {
241                     MultiThreadStopReason::SignalWithThread {
242                         tid: self.tid,
243                         signal: Signal::SIGTRAP,
244                     }
245                 } else {
246                     MultiThreadStopReason::SwBreak(self.tid)
247                 };
248                 let pc_bytes = self.current_pc.as_raw().to_le_bytes();
249                 let mut regs = core::iter::once((
250                     gdbstub_arch::wasm::reg::id::WasmRegId::Pc,
251                     pc_bytes.as_slice(),
252                 ));
253                 Ok(inner.report_stop_with_regs(self, stop_reason, &mut regs)?)
254             }
255             api::Event::Trap => {
256                 trace!("Event::Trap");
257                 self.update_on_stop();
258                 let pc_bytes = self.current_pc.as_raw().to_le_bytes();
259                 let mut regs = core::iter::once((
260                     gdbstub_arch::wasm::reg::id::WasmRegId::Pc,
261                     pc_bytes.as_slice(),
262                 ));
263                 Ok(inner.report_stop_with_regs(
264                     self,
265                     // We report all traps as SIGSEGV for now.
266                     MultiThreadStopReason::SignalWithThread {
267                         tid: self.tid,
268                         signal: Signal::SIGSEGV,
269                     },
270                     &mut regs,
271                 )?)
272             }
273             _ => {
274                 trace!("other event: {event:?}");
275                 if self.interrupt {
276                     self.interrupt = false;
277                     self.update_on_stop();
278                     let pc_bytes = self.current_pc.as_raw().to_le_bytes();
279                     let mut regs = core::iter::once((
280                         gdbstub_arch::wasm::reg::id::WasmRegId::Pc,
281                         pc_bytes.as_slice(),
282                     ));
283                     Ok(inner.report_stop_with_regs(
284                         self,
285                         MultiThreadStopReason::Signal(Signal::SIGINT),
286                         &mut regs,
287                     )?)
288                 } else {
289                     if self.single_stepping {
290                         self.start_single_step(api::ResumptionValue::Normal);
291                     } else {
292                         self.start_continue(api::ResumptionValue::Normal);
293                     }
294                     Ok(GdbStubStateMachine::Running(inner))
295                 }
296             }
297         }
298     }
299 
value_to_bytes(&self, value: WasmValue) -> Vec<u8>300     fn value_to_bytes(&self, value: WasmValue) -> Vec<u8> {
301         match value.get_type() {
302             WasmType::WasmI32 => value.unwrap_i32().to_le_bytes().to_vec(),
303             WasmType::WasmI64 => value.unwrap_i64().to_le_bytes().to_vec(),
304             WasmType::WasmF32 => value.unwrap_f32().to_le_bytes().to_vec(),
305             WasmType::WasmF64 => value.unwrap_f64().to_le_bytes().to_vec(),
306             WasmType::WasmV128 => value.unwrap_v128(),
307             WasmType::WasmFuncref => 0u32.to_le_bytes().to_vec(),
308             WasmType::WasmExnref => 0u32.to_le_bytes().to_vec(),
309         }
310     }
311 }
312 
313 struct Conn {
314     buf: Vec<u8>,
315     conn: TcpStream,
316 }
317 
318 impl Conn {
new(conn: TcpStream) -> Self319     fn new(conn: TcpStream) -> Self {
320         Conn { buf: vec![], conn }
321     }
322 
flush(&mut self) -> anyhow::Result<()>323     async fn flush(&mut self) -> anyhow::Result<()> {
324         self.conn.write_all(&self.buf).await?;
325         self.buf.clear();
326         Ok(())
327     }
328 
read_byte(&mut self) -> Result<Option<u8>>329     async fn read_byte(&mut self) -> Result<Option<u8>> {
330         let mut buf = [0u8];
331         let len = self.conn.read(&mut buf).await?;
332         if len == 1 { Ok(Some(buf[0])) } else { Ok(None) }
333     }
334 }
335 
336 impl Drop for Conn {
drop(&mut self)337     fn drop(&mut self) {
338         assert!(
339             self.buf.is_empty(),
340             "failed to async-flush before dropping connection write buffer"
341         );
342     }
343 }
344 
345 impl Connection for Conn {
346     type Error = anyhow::Error;
347 
write(&mut self, byte: u8) -> std::result::Result<(), Self::Error>348     fn write(&mut self, byte: u8) -> std::result::Result<(), Self::Error> {
349         self.buf.push(byte);
350         Ok(())
351     }
352 
flush(&mut self) -> std::result::Result<(), Self::Error>353     fn flush(&mut self) -> std::result::Result<(), Self::Error> {
354         // We cannot flush synchronously; we leave this to the `async
355         // fn flush` method called within the main loop. Fortunately
356         // the gdbstub cannot wait for a response before returning to
357         // the main loop, so we cannot introduce any deadlocks by
358         // failing to flush synchronously here.
359         Ok(())
360     }
361 }
362