wasmtime_internal_debugger/
lib.rs1use std::{any::Any, future::Future, pin::Pin, sync::Arc};
13use tokio::sync::{Mutex, mpsc};
14use wasmtime::{
15 AsContextMut, DebugEvent, DebugHandler, Engine, ExnRef, OwnedRooted, Result, Store,
16 StoreContextMut, Trap,
17};
18
19pub struct Debugger<T: Send + 'static> {
31 engine: Engine,
33 state: DebuggerState,
36 store: Option<Store<T>>,
38 in_tx: mpsc::Sender<Command<T>>,
39 out_rx: mpsc::Receiver<Response<T>>,
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76enum DebuggerState {
77 Initial,
79 Running,
83 Paused,
88 Queried,
91 Complete,
95}
96
97enum Command<T: 'static> {
128 Continue,
129 Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
130}
131
132enum Response<T: 'static> {
133 Paused(DebugRunResult),
134 QueryResponse(Box<dyn Any + Send>),
135 Finished(Store<T>),
136}
137
138struct HandlerInner<T: Send + 'static> {
139 in_rx: Mutex<mpsc::Receiver<Command<T>>>,
140 out_tx: mpsc::Sender<Response<T>>,
141}
142
143struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);
144
145impl<T: Send + 'static> std::clone::Clone for Handler<T> {
146 fn clone(&self) -> Self {
147 Handler(self.0.clone())
148 }
149}
150
151impl<T: Send + 'static> DebugHandler for Handler<T> {
152 type Data = T;
153 async fn handle(&self, mut store: StoreContextMut<'_, T>, event: DebugEvent<'_>) {
154 let mut in_rx = self.0.in_rx.lock().await;
155
156 let result = match event {
157 DebugEvent::HostcallError(_) => DebugRunResult::HostcallError,
158 DebugEvent::CaughtExceptionThrown(exn) => DebugRunResult::CaughtExceptionThrown(exn),
159 DebugEvent::UncaughtExceptionThrown(exn) => {
160 DebugRunResult::UncaughtExceptionThrown(exn)
161 }
162 DebugEvent::Trap(trap) => DebugRunResult::Trap(trap),
163 DebugEvent::Breakpoint => DebugRunResult::Breakpoint,
164 DebugEvent::EpochYield => DebugRunResult::EpochYield,
165 };
166 self.0
167 .out_tx
168 .send(Response::Paused(result))
169 .await
170 .expect("outbound channel closed prematurely");
171
172 while let Some(cmd) = in_rx.recv().await {
173 match cmd {
174 Command::Query(closure) => {
175 let result = closure(store.as_context_mut());
176 self.0
177 .out_tx
178 .send(Response::QueryResponse(result))
179 .await
180 .expect("outbound channel closed prematurely");
181 }
182 Command::Continue => {
183 break;
184 }
185 }
186 }
187 }
188}
189
190impl<T: Send + 'static> Debugger<T> {
191 pub fn new<F>(mut store: Store<T>, inner: F) -> Debugger<T>
206 where
207 F: for<'a> FnOnce(
208 &'a mut Store<T>,
209 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
210 + Send
211 + 'static,
212 {
213 let engine = store.engine().clone();
214 let (in_tx, in_rx) = mpsc::channel(1);
215 let (out_tx, out_rx) = mpsc::channel(1);
216
217 tokio::spawn(async move {
218 let out_tx_clone = out_tx.clone();
221 let handler = Handler(Arc::new(HandlerInner {
222 in_rx: Mutex::new(in_rx),
223 out_tx,
224 }));
225
226 log::trace!("inner debuggee task: first breakpoint");
228 handler
229 .handle(store.as_context_mut(), DebugEvent::Breakpoint)
230 .await;
231 log::trace!("inner debuggee task: first breakpoint resumed");
232
233 store.set_debug_handler(handler);
235 log::trace!("inner debuggee task: running `inner`");
236 let result = inner(&mut store).await;
237 log::trace!("inner debuggee task: done with `inner`");
238 let _ = out_tx_clone.send(Response::Finished(store)).await;
239 result
240 });
241
242 Debugger {
243 engine,
244 state: DebuggerState::Initial,
245 store: None,
246 in_tx,
247 out_rx,
248 }
249 }
250
251 pub fn is_complete(&self) -> bool {
253 match self.state {
254 DebuggerState::Complete => true,
255 _ => false,
256 }
257 }
258
259 pub fn engine(&self) -> &Engine {
261 &self.engine
262 }
263
264 async fn wait_for_initial(&mut self) -> Result<()> {
265 if let DebuggerState::Initial = &self.state {
266 let response = self
268 .out_rx
269 .recv()
270 .await
271 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
272 assert!(matches!(response, Response::Paused(_)));
273 self.state = DebuggerState::Paused;
274 }
275 Ok(())
276 }
277
278 pub async fn run(&mut self) -> Result<DebugRunResult> {
282 log::trace!("running: state is {:?}", self.state);
283
284 self.wait_for_initial().await?;
285
286 match self.state {
287 DebuggerState::Initial => unreachable!(),
288 DebuggerState::Paused => {
289 log::trace!("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 log::trace!("sent Continue");
295
296 self.state = DebuggerState::Running;
301 }
302 DebuggerState::Running => {
303 }
306 DebuggerState::Queried => {
307 log::trace!("in Queried; receiving");
311 let response =
312 self.out_rx.recv().await.ok_or_else(|| {
313 wasmtime::format_err!("Premature close of debugger channel")
314 })?;
315 log::trace!("in Queried; received, dropping");
316 assert!(matches!(response, Response::QueryResponse(_)));
317 self.state = DebuggerState::Paused;
318
319 log::trace!("in Paused; sending Continue");
321 self.in_tx
322 .send(Command::Continue)
323 .await
324 .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
325 self.state = DebuggerState::Running;
326 }
327 DebuggerState::Complete => {
328 panic!("Cannot `run()` an already-complete Debugger");
329 }
330 }
331
332 log::trace!("waiting for response");
338 let response = self
339 .out_rx
340 .recv()
341 .await
342 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
343
344 match response {
345 Response::Finished(store) => {
346 log::trace!("got Finished");
347 self.state = DebuggerState::Complete;
348 self.store = Some(store);
349 Ok(DebugRunResult::Finished)
350 }
351 Response::Paused(result) => {
352 log::trace!("got Paused");
353 self.state = DebuggerState::Paused;
354 Ok(result)
355 }
356 Response::QueryResponse(_) => {
357 panic!("Invalid debug response");
358 }
359 }
360 }
361
362 pub async fn finish(&mut self) -> Result<()> {
364 if self.is_complete() {
365 return Ok(());
366 }
367 loop {
368 match self.run().await? {
369 DebugRunResult::Finished => break,
370 e => {
371 log::trace!("finish: event {e:?}");
372 }
373 }
374 }
375 assert!(self.is_complete());
376 Ok(())
377 }
378
379 pub async fn with_store<
391 F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
392 R: Send + 'static,
393 >(
394 &mut self,
395 f: F,
396 ) -> Result<R> {
397 if let Some(store) = self.store.as_mut() {
398 return Ok(f(store.as_context_mut()));
399 }
400
401 self.wait_for_initial().await?;
402
403 match self.state {
404 DebuggerState::Initial => unreachable!(),
405 DebuggerState::Queried => {
406 let response =
408 self.out_rx.recv().await.ok_or_else(|| {
409 wasmtime::format_err!("Premature close of debugger channel")
410 })?;
411 assert!(matches!(response, Response::QueryResponse(_)));
412 self.state = DebuggerState::Paused;
413 }
414 DebuggerState::Running => {
415 panic!("Cannot query in Running state");
418 }
419 DebuggerState::Complete => {
420 panic!("Cannot query when complete");
421 }
422 DebuggerState::Paused => {
423 }
425 }
426
427 log::trace!("sending query in with_store");
428 self.in_tx
429 .send(Command::Query(Box::new(|store| Box::new(f(store)))))
430 .await
431 .map_err(|_| wasmtime::format_err!("Premature close of debugger channel"))?;
432 self.state = DebuggerState::Queried;
433
434 let response = self
435 .out_rx
436 .recv()
437 .await
438 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
439 let Response::QueryResponse(resp) = response else {
440 wasmtime::bail!("Incorrect response from debugger task");
441 };
442 self.state = DebuggerState::Paused;
443
444 Ok(*resp.downcast::<R>().expect("type mismatch"))
445 }
446}
447
448#[derive(Debug)]
454pub enum DebugRunResult {
455 Finished,
457 HostcallError,
459 EpochYield,
461 CaughtExceptionThrown(OwnedRooted<ExnRef>),
464 UncaughtExceptionThrown(OwnedRooted<ExnRef>),
466 Trap(Trap),
468 Breakpoint,
470}
471
472#[cfg(test)]
473mod test {
474 use super::*;
475 use wasmtime::*;
476
477 #[tokio::test]
478 #[cfg_attr(miri, ignore)]
479 async fn basic_debugger() -> wasmtime::Result<()> {
480 let _ = env_logger::try_init();
481
482 let mut config = Config::new();
483 config.guest_debug(true);
484 let engine = Engine::new(&config)?;
485 let module = Module::new(
486 &engine,
487 r#"
488 (module
489 (func (export "main") (param i32 i32) (result i32)
490 local.get 0
491 local.get 1
492 i32.add))
493 "#,
494 )?;
495
496 let mut store = Store::new(&engine, ());
497 let instance = Instance::new_async(&mut store, &module, &[]).await?;
498 let main = instance.get_func(&mut store, "main").unwrap();
499
500 let mut debugger = Debugger::new(store, move |store| {
501 Box::pin(async move {
502 let mut results = [Val::I32(0)];
503 store.edit_breakpoints().unwrap().single_step(true).unwrap();
504 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
505 .await?;
506 assert_eq!(results[0].unwrap_i32(), 3);
507 main.call_async(&mut *store, &[Val::I32(3), Val::I32(4)], &mut results[..])
508 .await?;
509 assert_eq!(results[0].unwrap_i32(), 7);
510 Ok(())
511 })
512 });
513
514 let event = debugger.run().await?;
515 assert!(matches!(event, DebugRunResult::Breakpoint));
516 debugger
518 .with_store(|mut store| {
519 let frame = store.debug_exit_frames().next().unwrap();
520 assert_eq!(
521 frame
522 .wasm_function_index_and_pc(&mut store)
523 .unwrap()
524 .unwrap()
525 .0
526 .as_u32(),
527 0
528 );
529 assert_eq!(
530 frame
531 .wasm_function_index_and_pc(&mut store)
532 .unwrap()
533 .unwrap()
534 .1,
535 36
536 );
537 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
538 assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
539 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
540 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
541 let frame = frame.parent(&mut store).unwrap();
542 assert!(frame.is_none());
543 })
544 .await?;
545
546 let event = debugger.run().await?;
547 assert!(matches!(event, DebugRunResult::Breakpoint));
549 debugger
550 .with_store(|mut store| {
551 let frame = store.debug_exit_frames().next().unwrap();
552 assert_eq!(
553 frame
554 .wasm_function_index_and_pc(&mut store)
555 .unwrap()
556 .unwrap()
557 .0
558 .as_u32(),
559 0
560 );
561 assert_eq!(
562 frame
563 .wasm_function_index_and_pc(&mut store)
564 .unwrap()
565 .unwrap()
566 .1,
567 38
568 );
569 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
570 assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
571 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
572 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
573 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
574 let frame = frame.parent(&mut store).unwrap();
575 assert!(frame.is_none());
576 })
577 .await?;
578
579 let event = debugger.run().await?;
580 assert!(matches!(event, DebugRunResult::Breakpoint));
582 debugger
583 .with_store(|mut store| {
584 let frame = store.debug_exit_frames().next().unwrap();
585 assert_eq!(
586 frame
587 .wasm_function_index_and_pc(&mut store)
588 .unwrap()
589 .unwrap()
590 .0
591 .as_u32(),
592 0
593 );
594 assert_eq!(
595 frame
596 .wasm_function_index_and_pc(&mut store)
597 .unwrap()
598 .unwrap()
599 .1,
600 40
601 );
602 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
603 assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
604 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
605 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
606 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
607 assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
608 let frame = frame.parent(&mut store).unwrap();
609 assert!(frame.is_none());
610 })
611 .await?;
612
613 let event = debugger.run().await?;
614 assert!(matches!(event, DebugRunResult::Breakpoint));
616 debugger
617 .with_store(|mut store| {
618 let frame = store.debug_exit_frames().next().unwrap();
619 assert_eq!(
620 frame
621 .wasm_function_index_and_pc(&mut store)
622 .unwrap()
623 .unwrap()
624 .0
625 .as_u32(),
626 0
627 );
628 assert_eq!(
629 frame
630 .wasm_function_index_and_pc(&mut store)
631 .unwrap()
632 .unwrap()
633 .1,
634 41
635 );
636 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
637 assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
638 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
639 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
640 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
641 let frame = frame.parent(&mut store).unwrap();
642 assert!(frame.is_none());
643 })
644 .await?;
645
646 debugger
648 .with_store(|store| {
649 store
650 .edit_breakpoints()
651 .unwrap()
652 .single_step(false)
653 .unwrap();
654 })
655 .await?;
656
657 let event = debugger.run().await?;
658 assert!(matches!(event, DebugRunResult::Finished));
659
660 assert!(debugger.is_complete());
661
662 Ok(())
663 }
664
665 #[tokio::test]
666 #[cfg_attr(miri, ignore)]
667 async fn early_finish() -> Result<()> {
668 let _ = env_logger::try_init();
669
670 let mut config = Config::new();
671 config.guest_debug(true);
672 let engine = Engine::new(&config)?;
673 let module = Module::new(
674 &engine,
675 r#"
676 (module
677 (func (export "main") (param i32 i32) (result i32)
678 local.get 0
679 local.get 1
680 i32.add))
681 "#,
682 )?;
683
684 let mut store = Store::new(&engine, ());
685 let instance = Instance::new_async(&mut store, &module, &[]).await?;
686 let main = instance.get_func(&mut store, "main").unwrap();
687
688 let mut debugger = Debugger::new(store, move |store| {
689 Box::pin(async move {
690 let mut results = [Val::I32(0)];
691 store.edit_breakpoints().unwrap().single_step(true).unwrap();
692 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
693 .await?;
694 assert_eq!(results[0].unwrap_i32(), 3);
695 Ok(())
696 })
697 });
698
699 debugger.finish().await?;
700 assert!(debugger.is_complete());
701
702 Ok(())
703 }
704
705 #[tokio::test]
706 #[cfg_attr(miri, ignore)]
707 async fn drop_debugger_and_store() -> Result<()> {
708 let _ = env_logger::try_init();
709
710 let mut config = Config::new();
711 config.guest_debug(true);
712 let engine = Engine::new(&config)?;
713 let module = Module::new(
714 &engine,
715 r#"
716 (module
717 (func (export "main") (param i32 i32) (result i32)
718 local.get 0
719 local.get 1
720 i32.add))
721 "#,
722 )?;
723
724 let mut store = Store::new(&engine, ());
725 let instance = Instance::new_async(&mut store, &module, &[]).await?;
726 let main = instance.get_func(&mut store, "main").unwrap();
727
728 let mut debugger = Debugger::new(store, move |store| {
729 Box::pin(async move {
730 let mut results = [Val::I32(0)];
731 store.edit_breakpoints().unwrap().single_step(true).unwrap();
732 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
733 .await?;
734 assert_eq!(results[0].unwrap_i32(), 3);
735 Ok(())
736 })
737 });
738
739 let _ = debugger.run().await?;
744
745 Ok(())
746 }
747}