wasmtime_internal_debugger/
lib.rs1use std::{any::Any, sync::Arc};
13use tokio::{
14 sync::{Mutex, mpsc},
15 task::JoinHandle,
16};
17use wasmtime::{
18 AsContextMut, DebugEvent, DebugHandler, ExnRef, OwnedRooted, Result, Store, StoreContextMut,
19 Trap,
20};
21
22pub struct Debugger<T: Send + 'static> {
34 inner: Option<JoinHandle<Result<Store<T>>>>,
36 state: DebuggerState,
39 in_tx: mpsc::Sender<Command<T>>,
40 out_rx: mpsc::Receiver<Response>,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77enum DebuggerState {
78 Running,
82 Paused,
87 Queried,
90 Complete,
94}
95
96enum Command<T: 'static> {
127 Continue,
128 Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
129}
130
131enum Response {
132 Paused(DebugRunResult),
133 QueryResponse(Box<dyn Any + Send>),
134 Finished,
135}
136
137struct HandlerInner<T: Send + 'static> {
138 in_rx: Mutex<mpsc::Receiver<Command<T>>>,
139 out_tx: mpsc::Sender<Response>,
140}
141
142struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);
143
144impl<T: Send + 'static> std::clone::Clone for Handler<T> {
145 fn clone(&self) -> Self {
146 Handler(self.0.clone())
147 }
148}
149
150impl<T: Send + 'static> DebugHandler for Handler<T> {
151 type Data = T;
152 async fn handle(&self, mut store: StoreContextMut<'_, T>, event: DebugEvent<'_>) {
153 let mut in_rx = self.0.in_rx.lock().await;
154
155 let result = match event {
156 DebugEvent::HostcallError(_) => DebugRunResult::HostcallError,
157 DebugEvent::CaughtExceptionThrown(exn) => DebugRunResult::CaughtExceptionThrown(exn),
158 DebugEvent::UncaughtExceptionThrown(exn) => {
159 DebugRunResult::UncaughtExceptionThrown(exn)
160 }
161 DebugEvent::Trap(trap) => DebugRunResult::Trap(trap),
162 DebugEvent::Breakpoint => DebugRunResult::Breakpoint,
163 DebugEvent::EpochYield => DebugRunResult::EpochYield,
164 };
165 self.0
166 .out_tx
167 .send(Response::Paused(result))
168 .await
169 .expect("outbound channel closed prematurely");
170
171 while let Some(cmd) = in_rx.recv().await {
172 match cmd {
173 Command::Query(closure) => {
174 let result = closure(store.as_context_mut());
175 self.0
176 .out_tx
177 .send(Response::QueryResponse(result))
178 .await
179 .expect("outbound channel closed prematurely");
180 }
181 Command::Continue => {
182 break;
183 }
184 }
185 }
186 }
187}
188
189impl<T: Send + 'static> Debugger<T> {
190 pub fn new<F, I>(mut store: Store<T>, inner: F) -> Debugger<T>
205 where
206 I: Future<Output = Result<Store<T>>> + Send + 'static,
207 F: for<'a> FnOnce(Store<T>) -> I + Send + 'static,
208 {
209 let (in_tx, mut in_rx) = mpsc::channel(1);
210 let (out_tx, out_rx) = mpsc::channel(1);
211
212 let inner = tokio::spawn(async move {
213 match in_rx.recv().await {
216 Some(cmd) => {
217 assert!(matches!(cmd, Command::Continue));
218 }
219 None => {
220 wasmtime::bail!("Debugger channel dropped");
222 }
223 }
224
225 let out_tx_clone = out_tx.clone();
226 store.set_debug_handler(Handler(Arc::new(HandlerInner {
227 in_rx: Mutex::new(in_rx),
228 out_tx,
229 })));
230 let result = inner(store).await;
231 let _ = out_tx_clone.send(Response::Finished).await;
232 result
233 });
234
235 Debugger {
236 inner: Some(inner),
237 state: DebuggerState::Paused,
238 in_tx,
239 out_rx,
240 }
241 }
242
243 pub fn is_complete(&self) -> bool {
245 match self.state {
246 DebuggerState::Complete => true,
247 _ => false,
248 }
249 }
250
251 pub async fn run(&mut self) -> Result<DebugRunResult> {
255 log::trace!("running: state is {:?}", self.state);
256 match self.state {
257 DebuggerState::Paused => {
258 log::trace!("sending Continue");
259 self.in_tx
260 .send(Command::Continue)
261 .await
262 .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
263 log::trace!("sent Continue");
264
265 self.state = DebuggerState::Running;
270 }
271 DebuggerState::Running => {
272 }
275 DebuggerState::Queried => {
276 log::trace!("in Queried; receiving");
280 let response =
281 self.out_rx.recv().await.ok_or_else(|| {
282 wasmtime::format_err!("Premature close of debugger channel")
283 })?;
284 log::trace!("in Queried; received, dropping");
285 assert!(matches!(response, Response::QueryResponse(_)));
286 self.state = DebuggerState::Paused;
287
288 log::trace!("in Paused; sending Continue");
290 self.in_tx
291 .send(Command::Continue)
292 .await
293 .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
294 self.state = DebuggerState::Running;
295 }
296 DebuggerState::Complete => {
297 panic!("Cannot `run()` an already-complete Debugger");
298 }
299 }
300
301 log::trace!("waiting for response");
307 let response = self
308 .out_rx
309 .recv()
310 .await
311 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
312
313 match response {
314 Response::Finished => {
315 log::trace!("got Finished");
316 self.state = DebuggerState::Complete;
317 Ok(DebugRunResult::Finished)
318 }
319 Response::Paused(result) => {
320 log::trace!("got Paused");
321 self.state = DebuggerState::Paused;
322 Ok(result)
323 }
324 Response::QueryResponse(_) => {
325 panic!("Invalid debug response");
326 }
327 }
328 }
329
330 pub async fn finish(&mut self) -> Result<()> {
332 if self.is_complete() {
333 return Ok(());
334 }
335 loop {
336 match self.run().await? {
337 DebugRunResult::Finished => break,
338 e => {
339 log::trace!("finish: event {e:?}");
340 }
341 }
342 }
343 assert!(self.is_complete());
344 Ok(())
345 }
346
347 pub async fn with_store<
359 F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
360 R: Send + 'static,
361 >(
362 &mut self,
363 f: F,
364 ) -> Result<R> {
365 assert!(!self.is_complete());
366
367 match self.state {
368 DebuggerState::Queried => {
369 let response =
371 self.out_rx.recv().await.ok_or_else(|| {
372 wasmtime::format_err!("Premature close of debugger channel")
373 })?;
374 assert!(matches!(response, Response::QueryResponse(_)));
375 self.state = DebuggerState::Paused;
376 }
377 DebuggerState::Running => {
378 panic!("Cannot query in Running state");
381 }
382 DebuggerState::Complete => {
383 panic!("Cannot query when complete");
384 }
385 DebuggerState::Paused => {
386 }
388 }
389
390 self.in_tx
391 .send(Command::Query(Box::new(|store| Box::new(f(store)))))
392 .await
393 .map_err(|_| wasmtime::format_err!("Premature close of debugger channel"))?;
394 self.state = DebuggerState::Queried;
395
396 let response = self
397 .out_rx
398 .recv()
399 .await
400 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
401 let Response::QueryResponse(resp) = response else {
402 wasmtime::bail!("Incorrect response from debugger task");
403 };
404 self.state = DebuggerState::Paused;
405
406 Ok(*resp.downcast::<R>().expect("type mismatch"))
407 }
408
409 pub async fn take_store(&mut self) -> Result<Option<Store<T>>> {
418 match self.state {
419 DebuggerState::Complete => {
420 let inner = match self.inner.take() {
421 Some(inner) => inner,
422 None => return Ok(None),
423 };
424 let mut store = inner.await??;
425 store.clear_debug_handler();
426 Ok(Some(store))
427 }
428 _ => panic!("Invalid state: debugger not yet complete"),
429 }
430 }
431}
432
433#[derive(Debug)]
439pub enum DebugRunResult {
440 Finished,
442 HostcallError,
444 EpochYield,
446 CaughtExceptionThrown(OwnedRooted<ExnRef>),
449 UncaughtExceptionThrown(OwnedRooted<ExnRef>),
451 Trap(Trap),
453 Breakpoint,
455}
456
457#[cfg(test)]
458mod test {
459 use super::*;
460 use wasmtime::*;
461
462 #[tokio::test]
463 #[cfg_attr(miri, ignore)]
464 async fn basic_debugger() -> wasmtime::Result<()> {
465 let _ = env_logger::try_init();
466
467 let mut config = Config::new();
468 config.guest_debug(true);
469 let engine = Engine::new(&config)?;
470 let module = Module::new(
471 &engine,
472 r#"
473 (module
474 (func (export "main") (param i32 i32) (result i32)
475 local.get 0
476 local.get 1
477 i32.add))
478 "#,
479 )?;
480
481 let mut store = Store::new(&engine, ());
482 let instance = Instance::new_async(&mut store, &module, &[]).await?;
483 let main = instance.get_func(&mut store, "main").unwrap();
484
485 let mut debugger = Debugger::new(store, move |mut store| async move {
486 let mut results = [Val::I32(0)];
487 store.edit_breakpoints().unwrap().single_step(true).unwrap();
488 main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
489 .await?;
490 assert_eq!(results[0].unwrap_i32(), 3);
491 main.call_async(&mut store, &[Val::I32(3), Val::I32(4)], &mut results[..])
492 .await?;
493 assert_eq!(results[0].unwrap_i32(), 7);
494 Ok(store)
495 });
496
497 let event = debugger.run().await?;
498 assert!(matches!(event, DebugRunResult::Breakpoint));
499 debugger
501 .with_store(|store| {
502 let mut frame = store.debug_frames().unwrap();
503 assert!(!frame.done());
504 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
505 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 36);
506 assert_eq!(frame.num_locals(), 2);
507 assert_eq!(frame.num_stacks(), 0);
508 assert_eq!(frame.local(0).unwrap_i32(), 1);
509 assert_eq!(frame.local(1).unwrap_i32(), 2);
510 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
511 assert!(frame.done());
512 })
513 .await?;
514
515 let event = debugger.run().await?;
516 assert!(matches!(event, DebugRunResult::Breakpoint));
518 debugger
519 .with_store(|store| {
520 let mut frame = store.debug_frames().unwrap();
521 assert!(!frame.done());
522 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
523 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 38);
524 assert_eq!(frame.num_locals(), 2);
525 assert_eq!(frame.num_stacks(), 1);
526 assert_eq!(frame.local(0).unwrap_i32(), 1);
527 assert_eq!(frame.local(1).unwrap_i32(), 2);
528 assert_eq!(frame.stack(0).unwrap_i32(), 1);
529 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
530 assert!(frame.done());
531 })
532 .await?;
533
534 let event = debugger.run().await?;
535 assert!(matches!(event, DebugRunResult::Breakpoint));
537 debugger
538 .with_store(|store| {
539 let mut frame = store.debug_frames().unwrap();
540 assert!(!frame.done());
541 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
542 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 40);
543 assert_eq!(frame.num_locals(), 2);
544 assert_eq!(frame.num_stacks(), 2);
545 assert_eq!(frame.local(0).unwrap_i32(), 1);
546 assert_eq!(frame.local(1).unwrap_i32(), 2);
547 assert_eq!(frame.stack(0).unwrap_i32(), 1);
548 assert_eq!(frame.stack(1).unwrap_i32(), 2);
549 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
550 assert!(frame.done());
551 })
552 .await?;
553
554 let event = debugger.run().await?;
555 assert!(matches!(event, DebugRunResult::Breakpoint));
557 debugger
558 .with_store(|store| {
559 let mut frame = store.debug_frames().unwrap();
560 assert!(!frame.done());
561 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
562 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 41);
563 assert_eq!(frame.num_locals(), 2);
564 assert_eq!(frame.num_stacks(), 1);
565 assert_eq!(frame.local(0).unwrap_i32(), 1);
566 assert_eq!(frame.local(1).unwrap_i32(), 2);
567 assert_eq!(frame.stack(0).unwrap_i32(), 3);
568 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
569 assert!(frame.done());
570 })
571 .await?;
572
573 debugger
575 .with_store(|store| {
576 store
577 .edit_breakpoints()
578 .unwrap()
579 .single_step(false)
580 .unwrap();
581 })
582 .await?;
583
584 let event = debugger.run().await?;
585 assert!(matches!(event, DebugRunResult::Finished));
586
587 assert!(debugger.is_complete());
588
589 let mut store = debugger.take_store().await?.unwrap();
592 let mut results = [Val::I32(0)];
593 main.call_async(&mut store, &[Val::I32(10), Val::I32(20)], &mut results[..])
594 .await?;
595 assert_eq!(results[0].unwrap_i32(), 30);
596
597 Ok(())
598 }
599
600 #[tokio::test]
601 #[cfg_attr(miri, ignore)]
602 async fn early_finish() -> Result<()> {
603 let _ = env_logger::try_init();
604
605 let mut config = Config::new();
606 config.guest_debug(true);
607 let engine = Engine::new(&config)?;
608 let module = Module::new(
609 &engine,
610 r#"
611 (module
612 (func (export "main") (param i32 i32) (result i32)
613 local.get 0
614 local.get 1
615 i32.add))
616 "#,
617 )?;
618
619 let mut store = Store::new(&engine, ());
620 let instance = Instance::new_async(&mut store, &module, &[]).await?;
621 let main = instance.get_func(&mut store, "main").unwrap();
622
623 let mut debugger = Debugger::new(store, move |mut store| async move {
624 let mut results = [Val::I32(0)];
625 store.edit_breakpoints().unwrap().single_step(true).unwrap();
626 main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
627 .await?;
628 assert_eq!(results[0].unwrap_i32(), 3);
629 Ok(store)
630 });
631
632 debugger.finish().await?;
633 assert!(debugger.is_complete());
634
635 Ok(())
636 }
637
638 #[tokio::test]
639 #[cfg_attr(miri, ignore)]
640 async fn drop_debugger_and_store() -> Result<()> {
641 let _ = env_logger::try_init();
642
643 let mut config = Config::new();
644 config.guest_debug(true);
645 let engine = Engine::new(&config)?;
646 let module = Module::new(
647 &engine,
648 r#"
649 (module
650 (func (export "main") (param i32 i32) (result i32)
651 local.get 0
652 local.get 1
653 i32.add))
654 "#,
655 )?;
656
657 let mut store = Store::new(&engine, ());
658 let instance = Instance::new_async(&mut store, &module, &[]).await?;
659 let main = instance.get_func(&mut store, "main").unwrap();
660
661 let mut debugger = Debugger::new(store, move |mut store| async move {
662 let mut results = [Val::I32(0)];
663 store.edit_breakpoints().unwrap().single_step(true).unwrap();
664 main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
665 .await?;
666 assert_eq!(results[0].unwrap_i32(), 3);
667 Ok(store)
668 });
669
670 let _ = debugger.run().await?;
675
676 Ok(())
677 }
678}