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 anyhow::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(|_| anyhow::anyhow!("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 = self
281 .out_rx
282 .recv()
283 .await
284 .ok_or_else(|| anyhow::anyhow!("Premature close of debugger channel"))?;
285 log::trace!("in Queried; received, dropping");
286 assert!(matches!(response, Response::QueryResponse(_)));
287 self.state = DebuggerState::Paused;
288
289 log::trace!("in Paused; sending Continue");
291 self.in_tx
292 .send(Command::Continue)
293 .await
294 .map_err(|_| anyhow::anyhow!("Failed to send over debug channel"))?;
295 self.state = DebuggerState::Running;
296 }
297 DebuggerState::Complete => {
298 panic!("Cannot `run()` an already-complete Debugger");
299 }
300 }
301
302 log::trace!("waiting for response");
308 let response = self
309 .out_rx
310 .recv()
311 .await
312 .ok_or_else(|| anyhow::anyhow!("Premature close of debugger channel"))?;
313
314 match response {
315 Response::Finished => {
316 log::trace!("got Finished");
317 self.state = DebuggerState::Complete;
318 Ok(DebugRunResult::Finished)
319 }
320 Response::Paused(result) => {
321 log::trace!("got Paused");
322 self.state = DebuggerState::Paused;
323 Ok(result)
324 }
325 Response::QueryResponse(_) => {
326 panic!("Invalid debug response");
327 }
328 }
329 }
330
331 pub async fn finish(&mut self) -> Result<()> {
333 if self.is_complete() {
334 return Ok(());
335 }
336 loop {
337 match self.run().await? {
338 DebugRunResult::Finished => break,
339 e => {
340 log::trace!("finish: event {e:?}");
341 }
342 }
343 }
344 assert!(self.is_complete());
345 Ok(())
346 }
347
348 pub async fn with_store<
360 F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
361 R: Send + 'static,
362 >(
363 &mut self,
364 f: F,
365 ) -> Result<R> {
366 assert!(!self.is_complete());
367
368 match self.state {
369 DebuggerState::Queried => {
370 let response = self
372 .out_rx
373 .recv()
374 .await
375 .ok_or_else(|| anyhow::anyhow!("Premature close of debugger channel"))?;
376 assert!(matches!(response, Response::QueryResponse(_)));
377 self.state = DebuggerState::Paused;
378 }
379 DebuggerState::Running => {
380 panic!("Cannot query in Running state");
383 }
384 DebuggerState::Complete => {
385 panic!("Cannot query when complete");
386 }
387 DebuggerState::Paused => {
388 }
390 }
391
392 self.in_tx
393 .send(Command::Query(Box::new(|store| Box::new(f(store)))))
394 .await
395 .map_err(|_| anyhow::anyhow!("Premature close of debugger channel"))?;
396 self.state = DebuggerState::Queried;
397
398 let response = self
399 .out_rx
400 .recv()
401 .await
402 .ok_or_else(|| anyhow::anyhow!("Premature close of debugger channel"))?;
403 let Response::QueryResponse(resp) = response else {
404 anyhow::bail!("Incorrect response from debugger task");
405 };
406 self.state = DebuggerState::Paused;
407
408 Ok(*resp.downcast::<R>().expect("type mismatch"))
409 }
410
411 pub async fn take_store(&mut self) -> Result<Option<Store<T>>> {
420 match self.state {
421 DebuggerState::Complete => {
422 let inner = match self.inner.take() {
423 Some(inner) => inner,
424 None => return Ok(None),
425 };
426 let mut store = inner.await??;
427 store.clear_debug_handler();
428 Ok(Some(store))
429 }
430 _ => panic!("Invalid state: debugger not yet complete"),
431 }
432 }
433}
434
435#[derive(Debug)]
441pub enum DebugRunResult {
442 Finished,
444 HostcallError,
446 EpochYield,
448 CaughtExceptionThrown(OwnedRooted<ExnRef>),
451 UncaughtExceptionThrown(OwnedRooted<ExnRef>),
453 Trap(Trap),
455 Breakpoint,
457}
458
459#[cfg(test)]
460mod test {
461 use super::*;
462 use wasmtime::*;
463
464 #[tokio::test]
465 #[cfg_attr(miri, ignore)]
466 async fn basic_debugger() -> anyhow::Result<()> {
467 let _ = env_logger::try_init();
468
469 let mut config = Config::new();
470 config.guest_debug(true);
471 config.async_support(true);
472 let engine = Engine::new(&config)?;
473 let module = Module::new(
474 &engine,
475 r#"
476 (module
477 (func (export "main") (param i32 i32) (result i32)
478 local.get 0
479 local.get 1
480 i32.add))
481 "#,
482 )?;
483
484 let mut store = Store::new(&engine, ());
485 let instance = Instance::new_async(&mut store, &module, &[]).await?;
486 let main = instance.get_func(&mut store, "main").unwrap();
487
488 let mut debugger = Debugger::new(store, move |mut store| async move {
489 let mut results = [Val::I32(0)];
490 store.edit_breakpoints().unwrap().single_step(true).unwrap();
491 main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
492 .await?;
493 assert_eq!(results[0].unwrap_i32(), 3);
494 main.call_async(&mut store, &[Val::I32(3), Val::I32(4)], &mut results[..])
495 .await?;
496 assert_eq!(results[0].unwrap_i32(), 7);
497 Ok(store)
498 });
499
500 let event = debugger.run().await?;
501 assert!(matches!(event, DebugRunResult::Breakpoint));
502 debugger
504 .with_store(|store| {
505 let mut frame = store.debug_frames().unwrap();
506 assert!(!frame.done());
507 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
508 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 36);
509 assert_eq!(frame.num_locals(), 2);
510 assert_eq!(frame.num_stacks(), 0);
511 assert_eq!(frame.local(0).unwrap_i32(), 1);
512 assert_eq!(frame.local(1).unwrap_i32(), 2);
513 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
514 assert!(frame.done());
515 })
516 .await?;
517
518 let event = debugger.run().await?;
519 assert!(matches!(event, DebugRunResult::Breakpoint));
521 debugger
522 .with_store(|store| {
523 let mut frame = store.debug_frames().unwrap();
524 assert!(!frame.done());
525 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
526 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 38);
527 assert_eq!(frame.num_locals(), 2);
528 assert_eq!(frame.num_stacks(), 1);
529 assert_eq!(frame.local(0).unwrap_i32(), 1);
530 assert_eq!(frame.local(1).unwrap_i32(), 2);
531 assert_eq!(frame.stack(0).unwrap_i32(), 1);
532 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
533 assert!(frame.done());
534 })
535 .await?;
536
537 let event = debugger.run().await?;
538 assert!(matches!(event, DebugRunResult::Breakpoint));
540 debugger
541 .with_store(|store| {
542 let mut frame = store.debug_frames().unwrap();
543 assert!(!frame.done());
544 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
545 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 40);
546 assert_eq!(frame.num_locals(), 2);
547 assert_eq!(frame.num_stacks(), 2);
548 assert_eq!(frame.local(0).unwrap_i32(), 1);
549 assert_eq!(frame.local(1).unwrap_i32(), 2);
550 assert_eq!(frame.stack(0).unwrap_i32(), 1);
551 assert_eq!(frame.stack(1).unwrap_i32(), 2);
552 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
553 assert!(frame.done());
554 })
555 .await?;
556
557 let event = debugger.run().await?;
558 assert!(matches!(event, DebugRunResult::Breakpoint));
560 debugger
561 .with_store(|store| {
562 let mut frame = store.debug_frames().unwrap();
563 assert!(!frame.done());
564 assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
565 assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 41);
566 assert_eq!(frame.num_locals(), 2);
567 assert_eq!(frame.num_stacks(), 1);
568 assert_eq!(frame.local(0).unwrap_i32(), 1);
569 assert_eq!(frame.local(1).unwrap_i32(), 2);
570 assert_eq!(frame.stack(0).unwrap_i32(), 3);
571 assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
572 assert!(frame.done());
573 })
574 .await?;
575
576 debugger
578 .with_store(|store| {
579 store
580 .edit_breakpoints()
581 .unwrap()
582 .single_step(false)
583 .unwrap();
584 })
585 .await?;
586
587 let event = debugger.run().await?;
588 assert!(matches!(event, DebugRunResult::Finished));
589
590 assert!(debugger.is_complete());
591
592 let mut store = debugger.take_store().await?.unwrap();
595 let mut results = [Val::I32(0)];
596 main.call_async(&mut store, &[Val::I32(10), Val::I32(20)], &mut results[..])
597 .await?;
598 assert_eq!(results[0].unwrap_i32(), 30);
599
600 Ok(())
601 }
602
603 #[tokio::test]
604 #[cfg_attr(miri, ignore)]
605 async fn early_finish() -> Result<()> {
606 let _ = env_logger::try_init();
607
608 let mut config = Config::new();
609 config.guest_debug(true);
610 config.async_support(true);
611 let engine = Engine::new(&config)?;
612 let module = Module::new(
613 &engine,
614 r#"
615 (module
616 (func (export "main") (param i32 i32) (result i32)
617 local.get 0
618 local.get 1
619 i32.add))
620 "#,
621 )?;
622
623 let mut store = Store::new(&engine, ());
624 let instance = Instance::new_async(&mut store, &module, &[]).await?;
625 let main = instance.get_func(&mut store, "main").unwrap();
626
627 let mut debugger = Debugger::new(store, move |mut store| async move {
628 let mut results = [Val::I32(0)];
629 store.edit_breakpoints().unwrap().single_step(true).unwrap();
630 main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
631 .await?;
632 assert_eq!(results[0].unwrap_i32(), 3);
633 Ok(store)
634 });
635
636 debugger.finish().await?;
637 assert!(debugger.is_complete());
638
639 Ok(())
640 }
641
642 #[tokio::test]
643 #[cfg_attr(miri, ignore)]
644 async fn drop_debugger_and_store() -> Result<()> {
645 let _ = env_logger::try_init();
646
647 let mut config = Config::new();
648 config.guest_debug(true);
649 config.async_support(true);
650 let engine = Engine::new(&config)?;
651 let module = Module::new(
652 &engine,
653 r#"
654 (module
655 (func (export "main") (param i32 i32) (result i32)
656 local.get 0
657 local.get 1
658 i32.add))
659 "#,
660 )?;
661
662 let mut store = Store::new(&engine, ());
663 let instance = Instance::new_async(&mut store, &module, &[]).await?;
664 let main = instance.get_func(&mut store, "main").unwrap();
665
666 let mut debugger = Debugger::new(store, move |mut store| async move {
667 let mut results = [Val::I32(0)];
668 store.edit_breakpoints().unwrap().single_step(true).unwrap();
669 main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
670 .await?;
671 assert_eq!(results[0].unwrap_i32(), 3);
672 Ok(store)
673 });
674
675 let _ = debugger.run().await?;
680
681 Ok(())
682 }
683}