wasmtime_internal_debugger/lib.rs
1//! Wasmtime debugger functionality.
2//!
3//! This crate builds on top of the core Wasmtime crate's
4//! guest-debugger APIs to present an environment where a debugger
5//! runs as a "co-running process" and sees the debuggee as a a
6//! provider of a stream of events, on which actions can be taken
7//! between each event.
8//!
9//! In the future, this crate will also provide a WIT-level API and
10//! world in which to run debugger components.
11
12use std::{
13 any::Any,
14 future::Future,
15 pin::Pin,
16 sync::{
17 Arc,
18 atomic::{AtomicBool, Ordering},
19 },
20};
21use tokio::{
22 sync::{Mutex, mpsc},
23 task::JoinHandle,
24};
25use wasmtime::{
26 AsContextMut, DebugEvent, DebugHandler, Engine, ExnRef, OwnedRooted, Result, Store,
27 StoreContextMut, Trap,
28};
29
30mod host;
31pub use host::{DebuggerComponent, add_debuggee, add_to_linker, wit};
32
33/// A `Debuggee` wraps up state associated with debugging the code
34/// running in a single `Store`.
35///
36/// It acts as a Future combinator, wrapping an inner async body that
37/// performs some actions on a store. Those actions are subject to the
38/// debugger, and debugger events will be raised as appropriate. From
39/// the "outside" of this combinator, it is always in one of two
40/// states: running or paused. When paused, it acts as a
41/// `StoreContextMut` and can allow examining the paused execution's
42/// state. One runs until the next event suspends execution by
43/// invoking `Debuggee::run`.
44pub struct Debuggee<T: Send + 'static> {
45 /// A handle to the Engine that the debuggee store lives within.
46 engine: Engine,
47 /// State: either a task handle or the store when passed out of
48 /// the complete task.
49 state: DebuggeeState,
50 /// The store, once complete.
51 store: Option<Store<T>>,
52 in_tx: mpsc::Sender<Command<T>>,
53 out_rx: mpsc::Receiver<Response<T>>,
54 handle: Option<JoinHandle<Result<()>>>,
55 /// Flag shared with the inner handler: set to `true` by
56 /// `interrupt()` so the next epoch yield is surfaced as an
57 /// `Interrupted` event rather than eaten by the handler. Epoch
58 /// yields serve two purposes, namely ensuring regular yields to
59 /// the event loop and enacting an explicit interrupt, and this
60 /// flag distinguishes those cases.
61 interrupt_pending: Arc<AtomicBool>,
62}
63
64/// State machine from the perspective of the outer logic.
65///
66/// The intermediate states here, and the separation of these states
67/// from the `JoinHandle` above, are what allow us to implement a
68/// cancel-safe version of `Debuggee::run` below.
69///
70/// The state diagram for the outer logic is:
71///
72/// ```plain
73/// (start)
74/// v
75/// |
76/// .--->---------. v
77/// | .----< Paused <-----------------------------------------------.
78/// | | v |
79/// | | | (async fn run() starts, sends Command::Continue) |
80/// | | | |
81/// | | v ^
82/// | | Running |
83/// | | v v (async fn run() receives Response::Paused, returns) |
84/// | | | |_____________________________________________________|
85/// | | |
86/// | | | (async fn run() receives Response::Finished, returns)
87/// | | v
88/// | | Complete
89/// | |
90/// ^ | (async fn with_store() starts, sends Command::Query)
91/// | v
92/// | Queried
93/// | |
94/// | | (async fn with_store() receives Response::QueryResponse, returns)
95/// `---<-'
96/// ```
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98enum DebuggeeState {
99 /// Inner body has just been started.
100 Initial,
101 /// Inner body is running in an async task and not in a debugger
102 /// callback. Outer logic is waiting for a `Response::Paused` or
103 /// `Response::Complete`.
104 Running,
105 /// Inner body is running in an async task and at a debugger
106 /// callback (or in the initial trampoline waiting for the first
107 /// `Continue`). `Response::Paused` has been received. Outer
108 /// logic has not sent any commands.
109 Paused,
110 /// We have sent a command to the inner body and are waiting for a
111 /// response.
112 Queried,
113 /// Inner body is complete (has sent `Response::Finished` and we
114 /// have received it). We may or may not have joined yet; if so,
115 /// the `Option<JoinHandle<...>>` will be `None`.
116 Complete,
117}
118
119/// Message from "outside" to the debug hook.
120///
121/// The `Query` catch-all with a boxed closure is a little janky, but
122/// is the way that we provide access
123/// from outside to the Store (which is owned by `inner` above)
124/// only during pauses. Note that the future cannot take full
125/// ownership or a mutable borrow of the Store, because it cannot
126/// hold this across async yield points.
127///
128/// Instead, the debugger body sends boxed closures which take the
129/// Store as a parameter (lifetime-limited not to escape that
130/// closure) out to this crate's implementation that runs inside of
131/// debugger-instrumentation callbacks (which have access to the
132/// Store during their duration). We send return values
133/// back. Return values are boxed Any values.
134///
135/// If we wanted to make this a little more principled, we could
136/// come up with a Command/Response pair of enums for all possible
137/// closures and make everything more statically typed and less
138/// Box'd, but that would severely restrict the flexibility of the
139/// abstraction here and essentially require writing a full proxy
140/// of the debugger API.
141///
142/// Furthermore, we expect to rip this out eventually when we move
143/// the debugger over to an async implementation based on
144/// `run_concurrent` and `Accessor`s (see #11896). Building things
145/// this way now will actually allow a less painful transition at
146/// that time, because we will have a bunch of closures accessing
147/// the store already and we can run those "with an accessor"
148/// instead.
149enum Command<T: 'static> {
150 Continue,
151 Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
152}
153
154enum Response<T: 'static> {
155 Paused(DebugRunResult),
156 QueryResponse(Box<dyn Any + Send>),
157 Finished(Store<T>),
158}
159
160struct HandlerInner<T: Send + 'static> {
161 in_rx: Mutex<mpsc::Receiver<Command<T>>>,
162 out_tx: mpsc::Sender<Response<T>>,
163 interrupt_pending: Arc<AtomicBool>,
164}
165
166struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);
167
168impl<T: Send + 'static> std::clone::Clone for Handler<T> {
169 fn clone(&self) -> Self {
170 Handler(self.0.clone())
171 }
172}
173
174impl<T: Send + 'static> DebugHandler for Handler<T> {
175 type Data = T;
176 async fn handle(&self, mut store: StoreContextMut<'_, T>, event: DebugEvent<'_>) {
177 let mut in_rx = self.0.in_rx.lock().await;
178
179 let result = match event {
180 DebugEvent::HostcallError(_) => DebugRunResult::HostcallError,
181 DebugEvent::Exception(exn) => DebugRunResult::Exception(exn),
182 DebugEvent::Trap(trap) => DebugRunResult::Trap(trap),
183 DebugEvent::Breakpoint => DebugRunResult::Breakpoint,
184 DebugEvent::EpochYield => {
185 // Only pause on epoch yields that were requested via
186 // interrupt(). Other epoch ticks simply yield to the
187 // event loop (functionality already implemented in
188 // core Wasmtime; no need to do that yield here in the
189 // debug handler).
190 if !self.0.interrupt_pending.swap(false, Ordering::SeqCst) {
191 return;
192 }
193 DebugRunResult::EpochYield
194 }
195 };
196 if self.0.out_tx.send(Response::Paused(result)).await.is_err() {
197 // Outer Debuggee has been dropped: just continue
198 // executing.
199 return;
200 }
201
202 while let Some(cmd) = in_rx.recv().await {
203 match cmd {
204 Command::Query(closure) => {
205 let result = closure(store.as_context_mut());
206 if self
207 .0
208 .out_tx
209 .send(Response::QueryResponse(result))
210 .await
211 .is_err()
212 {
213 // Outer Debuggee has been dropped: just
214 // continue executing.
215 return;
216 }
217 }
218 Command::Continue => {
219 break;
220 }
221 }
222 }
223 }
224}
225
226impl<T: Send + 'static> Debuggee<T> {
227 /// Create a new Debugger that attaches to the given Store and
228 /// runs the given inner body.
229 ///
230 /// The debugger is always in one of two states: running or
231 /// paused.
232 ///
233 /// When paused, the holder of this object can invoke
234 /// `Debuggee::run` to enter the running state. The inner body
235 /// will run until paused by a debug event. While running, the
236 /// future returned by either of these methods owns the `Debuggee`
237 /// and hence no other methods can be invoked.
238 ///
239 /// When paused, the holder of this object can access the `Store`
240 /// indirectly by providing a closure
241 pub fn new<F>(mut store: Store<T>, inner: F) -> Debuggee<T>
242 where
243 F: for<'a> FnOnce(
244 &'a mut Store<T>,
245 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
246 + Send
247 + 'static,
248 {
249 let engine = store.engine().clone();
250 let (in_tx, in_rx) = mpsc::channel(1);
251 let (out_tx, out_rx) = mpsc::channel(1);
252 let interrupt_pending = Arc::new(AtomicBool::new(false));
253
254 let handle = tokio::spawn({
255 let interrupt_pending = interrupt_pending.clone();
256 async move {
257 // Create the handler that's invoked from within the async
258 // debug-event callback.
259 let out_tx_clone = out_tx.clone();
260 let handler = Handler(Arc::new(HandlerInner {
261 in_rx: Mutex::new(in_rx),
262 out_tx,
263 interrupt_pending,
264 }));
265
266 // Emulate a breakpoint at startup.
267 log::trace!("inner debuggee task: first breakpoint");
268 handler
269 .handle(store.as_context_mut(), DebugEvent::Breakpoint)
270 .await;
271 log::trace!("inner debuggee task: first breakpoint resumed");
272
273 // Now invoke the actual inner body.
274 store.set_debug_handler(handler);
275 log::trace!("inner debuggee task: running `inner`");
276 let result = inner(&mut store).await;
277 log::trace!("inner debuggee task: done with `inner`");
278 let _ = out_tx_clone.send(Response::Finished(store)).await;
279 result
280 }
281 });
282
283 Debuggee {
284 engine,
285 state: DebuggeeState::Initial,
286 store: None,
287 in_tx,
288 out_rx,
289 interrupt_pending,
290 handle: Some(handle),
291 }
292 }
293
294 /// Is the inner body done running?
295 pub fn is_complete(&self) -> bool {
296 match self.state {
297 DebuggeeState::Complete => true,
298 _ => false,
299 }
300 }
301
302 /// Get the Engine associated with the debuggee.
303 pub fn engine(&self) -> &Engine {
304 &self.engine
305 }
306
307 /// Get the interrupt-pending flag. Setting this to `true` causes
308 /// the next epoch yield to surface as an `Interrupted` event.
309 pub fn interrupt_pending(&self) -> &Arc<AtomicBool> {
310 &self.interrupt_pending
311 }
312
313 async fn wait_for_initial(&mut self) -> Result<()> {
314 if let DebuggeeState::Initial = &self.state {
315 // Need to receive and discard first `Paused`.
316 let response = self
317 .out_rx
318 .recv()
319 .await
320 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
321 assert!(matches!(response, Response::Paused(_)));
322 self.state = DebuggeeState::Paused;
323 }
324 Ok(())
325 }
326
327 /// Run the inner body until the next debug event.
328 ///
329 /// This method is cancel-safe, and no events will be lost.
330 pub async fn run(&mut self) -> Result<DebugRunResult> {
331 log::trace!("running: state is {:?}", self.state);
332
333 self.wait_for_initial().await?;
334
335 match self.state {
336 DebuggeeState::Initial => unreachable!(),
337 DebuggeeState::Paused => {
338 log::trace!("sending Continue");
339 self.in_tx
340 .send(Command::Continue)
341 .await
342 .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
343 log::trace!("sent Continue");
344
345 // If that `send` was canceled, the command was not
346 // sent, so it's fine to remain in `Paused`. If it
347 // succeeded and we reached here, transition to
348 // `Running` so we don't re-send.
349 self.state = DebuggeeState::Running;
350 }
351 DebuggeeState::Running => {
352 // Previous `run()` must have been canceled; no action
353 // to take here.
354 }
355 DebuggeeState::Queried => {
356 // We expect to receive a `QueryResponse`; drop it if
357 // the query was canceled, then transition back to
358 // `Paused`.
359 log::trace!("in Queried; receiving");
360 let response =
361 self.out_rx.recv().await.ok_or_else(|| {
362 wasmtime::format_err!("Premature close of debugger channel")
363 })?;
364 log::trace!("in Queried; received, dropping");
365 assert!(matches!(response, Response::QueryResponse(_)));
366 self.state = DebuggeeState::Paused;
367
368 // Now send a `Continue`, as above.
369 log::trace!("in Paused; sending Continue");
370 self.in_tx
371 .send(Command::Continue)
372 .await
373 .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
374 self.state = DebuggeeState::Running;
375 }
376 DebuggeeState::Complete => {
377 panic!("Cannot `run()` an already-complete Debuggee");
378 }
379 }
380
381 // At this point, the inner task is in Running state. We
382 // expect to receive a message when it next pauses or
383 // completes. If this `recv()` is canceled, no message is
384 // lost, and the state above accurately reflects what must be
385 // done on the next `run()`.
386 log::trace!("waiting for response");
387 let response = self
388 .out_rx
389 .recv()
390 .await
391 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
392
393 match response {
394 Response::Finished(store) => {
395 log::trace!("got Finished");
396 self.state = DebuggeeState::Complete;
397 self.store = Some(store);
398 Ok(DebugRunResult::Finished)
399 }
400 Response::Paused(result) => {
401 log::trace!("got Paused");
402 self.state = DebuggeeState::Paused;
403 Ok(result)
404 }
405 Response::QueryResponse(_) => {
406 panic!("Invalid debug response");
407 }
408 }
409 }
410
411 /// Run the debugger body until completion, with no further events.
412 pub async fn finish(&mut self) -> Result<()> {
413 if self.is_complete() {
414 return Ok(());
415 }
416 loop {
417 match self.run().await? {
418 DebugRunResult::Finished => break,
419 e => {
420 log::trace!("finish: event {e:?}");
421 }
422 }
423 }
424 if let Some(handle) = self.handle.take() {
425 handle.await??;
426 }
427 assert!(self.is_complete());
428 Ok(())
429 }
430
431 /// Perform some action on the contained `Store` while not running.
432 ///
433 /// This may only be invoked before the inner body finishes and
434 /// when it is paused; that is, when the `Debuggee` is initially
435 /// created and after any call to `run()` returns a result other
436 /// than `DebugRunResult::Finished`. If an earlier `run()`
437 /// invocation was canceled, it must be re-invoked and return
438 /// successfully before a query is made.
439 ///
440 /// This is cancel-safe; if canceled, the result of the query will
441 /// be dropped.
442 pub async fn with_store<
443 F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
444 R: Send + 'static,
445 >(
446 &mut self,
447 f: F,
448 ) -> Result<R> {
449 if let Some(store) = self.store.as_mut() {
450 return Ok(f(store.as_context_mut()));
451 }
452
453 self.wait_for_initial().await?;
454
455 match self.state {
456 DebuggeeState::Initial => unreachable!(),
457 DebuggeeState::Queried => {
458 // Earlier query canceled; drop its response first.
459 let response =
460 self.out_rx.recv().await.ok_or_else(|| {
461 wasmtime::format_err!("Premature close of debugger channel")
462 })?;
463 assert!(matches!(response, Response::QueryResponse(_)));
464 self.state = DebuggeeState::Paused;
465 }
466 DebuggeeState::Running => {
467 // Results from a canceled `run()`; `run()` must
468 // complete before this can be invoked.
469 panic!("Cannot query in Running state");
470 }
471 DebuggeeState::Complete => {
472 panic!("Cannot query when complete");
473 }
474 DebuggeeState::Paused => {
475 // OK -- this is the state we want.
476 }
477 }
478
479 log::trace!("sending query in with_store");
480 self.in_tx
481 .send(Command::Query(Box::new(|store| Box::new(f(store)))))
482 .await
483 .map_err(|_| wasmtime::format_err!("Premature close of debugger channel"))?;
484 self.state = DebuggeeState::Queried;
485
486 let response = self
487 .out_rx
488 .recv()
489 .await
490 .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
491 let Response::QueryResponse(resp) = response else {
492 wasmtime::bail!("Incorrect response from debugger task");
493 };
494 self.state = DebuggeeState::Paused;
495
496 Ok(*resp.downcast::<R>().expect("type mismatch"))
497 }
498}
499
500/// The result of one call to `Debuggee::run()`.
501///
502/// This is similar to `DebugEvent` but without the lifetime, so it
503/// can be sent across async tasks, and incorporates the possibility
504/// of completion (`Finished`) as well.
505#[derive(Debug)]
506pub enum DebugRunResult {
507 /// Execution of the inner body finished.
508 Finished,
509 /// An error was raised by a hostcall.
510 HostcallError,
511 /// Wasm execution was interrupted by an epoch change.
512 EpochYield,
513 /// An exception is thrown by Wasm. The current state is at the
514 /// throw-point.
515 Exception(OwnedRooted<ExnRef>),
516 /// A Wasm trap occurred.
517 Trap(Trap),
518 /// A breakpoint was reached.
519 Breakpoint,
520}
521
522#[cfg(test)]
523mod test {
524 use super::*;
525 use wasmtime::*;
526
527 #[tokio::test]
528 #[cfg_attr(miri, ignore)]
529 async fn basic_debugger() -> wasmtime::Result<()> {
530 let _ = env_logger::try_init();
531
532 let mut config = Config::new();
533 config.guest_debug(true);
534 let engine = Engine::new(&config)?;
535 let module = Module::new(
536 &engine,
537 r#"
538 (module
539 (func (export "main") (param i32 i32) (result i32)
540 local.get 0
541 local.get 1
542 i32.add))
543 "#,
544 )?;
545
546 let mut store = Store::new(&engine, ());
547 let instance = Instance::new_async(&mut store, &module, &[]).await?;
548 let main = instance.get_func(&mut store, "main").unwrap();
549
550 let mut debuggee = Debuggee::new(store, move |store| {
551 Box::pin(async move {
552 let mut results = [Val::I32(0)];
553 store.edit_breakpoints().unwrap().single_step(true).unwrap();
554 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
555 .await?;
556 assert_eq!(results[0].unwrap_i32(), 3);
557 main.call_async(&mut *store, &[Val::I32(3), Val::I32(4)], &mut results[..])
558 .await?;
559 assert_eq!(results[0].unwrap_i32(), 7);
560 Ok(())
561 })
562 });
563
564 let event = debuggee.run().await?;
565 assert!(matches!(event, DebugRunResult::Breakpoint));
566 // At (before executing) first `local.get`.
567 debuggee
568 .with_store(|mut store| {
569 let frame = store.debug_exit_frames().next().unwrap();
570 assert_eq!(
571 frame
572 .wasm_function_index_and_pc(&mut store)
573 .unwrap()
574 .unwrap()
575 .0
576 .as_u32(),
577 0
578 );
579 assert_eq!(
580 frame
581 .wasm_function_index_and_pc(&mut store)
582 .unwrap()
583 .unwrap()
584 .1
585 .raw(),
586 36
587 );
588 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
589 assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
590 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
591 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
592 let frame = frame.parent(&mut store).unwrap();
593 assert!(frame.is_none());
594 })
595 .await?;
596
597 let event = debuggee.run().await?;
598 // At second `local.get`.
599 assert!(matches!(event, DebugRunResult::Breakpoint));
600 debuggee
601 .with_store(|mut store| {
602 let frame = store.debug_exit_frames().next().unwrap();
603 assert_eq!(
604 frame
605 .wasm_function_index_and_pc(&mut store)
606 .unwrap()
607 .unwrap()
608 .0
609 .as_u32(),
610 0
611 );
612 assert_eq!(
613 frame
614 .wasm_function_index_and_pc(&mut store)
615 .unwrap()
616 .unwrap()
617 .1
618 .raw(),
619 38
620 );
621 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
622 assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
623 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
624 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
625 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
626 let frame = frame.parent(&mut store).unwrap();
627 assert!(frame.is_none());
628 })
629 .await?;
630
631 let event = debuggee.run().await?;
632 // At `i32.add`.
633 assert!(matches!(event, DebugRunResult::Breakpoint));
634 debuggee
635 .with_store(|mut store| {
636 let frame = store.debug_exit_frames().next().unwrap();
637 assert_eq!(
638 frame
639 .wasm_function_index_and_pc(&mut store)
640 .unwrap()
641 .unwrap()
642 .0
643 .as_u32(),
644 0
645 );
646 assert_eq!(
647 frame
648 .wasm_function_index_and_pc(&mut store)
649 .unwrap()
650 .unwrap()
651 .1
652 .raw(),
653 40
654 );
655 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
656 assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
657 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
658 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
659 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
660 assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
661 let frame = frame.parent(&mut store).unwrap();
662 assert!(frame.is_none());
663 })
664 .await?;
665
666 let event = debuggee.run().await?;
667 // At return point.
668 assert!(matches!(event, DebugRunResult::Breakpoint));
669 debuggee
670 .with_store(|mut store| {
671 let frame = store.debug_exit_frames().next().unwrap();
672 assert_eq!(
673 frame
674 .wasm_function_index_and_pc(&mut store)
675 .unwrap()
676 .unwrap()
677 .0
678 .as_u32(),
679 0
680 );
681 assert_eq!(
682 frame
683 .wasm_function_index_and_pc(&mut store)
684 .unwrap()
685 .unwrap()
686 .1
687 .raw(),
688 41
689 );
690 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
691 assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
692 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
693 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
694 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
695 let frame = frame.parent(&mut store).unwrap();
696 assert!(frame.is_none());
697 })
698 .await?;
699
700 // Now disable breakpoints before continuing. Second call should proceed with no more events.
701 debuggee
702 .with_store(|store| {
703 store
704 .edit_breakpoints()
705 .unwrap()
706 .single_step(false)
707 .unwrap();
708 })
709 .await?;
710
711 let event = debuggee.run().await?;
712 assert!(matches!(event, DebugRunResult::Finished));
713
714 assert!(debuggee.is_complete());
715
716 Ok(())
717 }
718
719 #[tokio::test]
720 #[cfg_attr(miri, ignore)]
721 async fn early_finish() -> Result<()> {
722 let _ = env_logger::try_init();
723
724 let mut config = Config::new();
725 config.guest_debug(true);
726 let engine = Engine::new(&config)?;
727 let module = Module::new(
728 &engine,
729 r#"
730 (module
731 (func (export "main") (param i32 i32) (result i32)
732 local.get 0
733 local.get 1
734 i32.add))
735 "#,
736 )?;
737
738 let mut store = Store::new(&engine, ());
739 let instance = Instance::new_async(&mut store, &module, &[]).await?;
740 let main = instance.get_func(&mut store, "main").unwrap();
741
742 let mut debuggee = Debuggee::new(store, move |store| {
743 Box::pin(async move {
744 let mut results = [Val::I32(0)];
745 store.edit_breakpoints().unwrap().single_step(true).unwrap();
746 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
747 .await?;
748 assert_eq!(results[0].unwrap_i32(), 3);
749 Ok(())
750 })
751 });
752
753 debuggee.finish().await?;
754 assert!(debuggee.is_complete());
755
756 Ok(())
757 }
758
759 #[tokio::test]
760 #[cfg_attr(miri, ignore)]
761 async fn drop_debuggee_and_store() -> Result<()> {
762 let _ = env_logger::try_init();
763
764 let mut config = Config::new();
765 config.guest_debug(true);
766 let engine = Engine::new(&config)?;
767 let module = Module::new(
768 &engine,
769 r#"
770 (module
771 (func (export "main") (param i32 i32) (result i32)
772 local.get 0
773 local.get 1
774 i32.add))
775 "#,
776 )?;
777
778 let mut store = Store::new(&engine, ());
779 let instance = Instance::new_async(&mut store, &module, &[]).await?;
780 let main = instance.get_func(&mut store, "main").unwrap();
781
782 let mut debuggee = Debuggee::new(store, move |store| {
783 Box::pin(async move {
784 let mut results = [Val::I32(0)];
785 store.edit_breakpoints().unwrap().single_step(true).unwrap();
786 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
787 .await?;
788 assert_eq!(results[0].unwrap_i32(), 3);
789 Ok(())
790 })
791 });
792
793 // Step once, then drop everything at the end of this
794 // function. Wasmtime's fiber cleanup should safely happen
795 // without attempting to raise debug async handler calls with
796 // missing async context.
797 let _ = debuggee.run().await?;
798
799 Ok(())
800 }
801}