Skip to main content

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::CaughtExceptionThrown(exn) => DebugRunResult::CaughtExceptionThrown(exn),
182            DebugEvent::UncaughtExceptionThrown(exn) => {
183                DebugRunResult::UncaughtExceptionThrown(exn)
184            }
185            DebugEvent::Trap(trap) => DebugRunResult::Trap(trap),
186            DebugEvent::Breakpoint => DebugRunResult::Breakpoint,
187            DebugEvent::EpochYield => {
188                // Only pause on epoch yields that were requested via
189                // interrupt(). Other epoch ticks simply yield to the
190                // event loop (functionality already implemented in
191                // core Wasmtime; no need to do that yield here in the
192                // debug handler).
193                if !self.0.interrupt_pending.swap(false, Ordering::SeqCst) {
194                    return;
195                }
196                DebugRunResult::EpochYield
197            }
198        };
199        if self.0.out_tx.send(Response::Paused(result)).await.is_err() {
200            // Outer Debuggee has been dropped: just continue
201            // executing.
202            return;
203        }
204
205        while let Some(cmd) = in_rx.recv().await {
206            match cmd {
207                Command::Query(closure) => {
208                    let result = closure(store.as_context_mut());
209                    if self
210                        .0
211                        .out_tx
212                        .send(Response::QueryResponse(result))
213                        .await
214                        .is_err()
215                    {
216                        // Outer Debuggee has been dropped: just
217                        // continue executing.
218                        return;
219                    }
220                }
221                Command::Continue => {
222                    break;
223                }
224            }
225        }
226    }
227}
228
229impl<T: Send + 'static> Debuggee<T> {
230    /// Create a new Debugger that attaches to the given Store and
231    /// runs the given inner body.
232    ///
233    /// The debugger is always in one of two states: running or
234    /// paused.
235    ///
236    /// When paused, the holder of this object can invoke
237    /// `Debuggee::run` to enter the running state. The inner body
238    /// will run until paused by a debug event. While running, the
239    /// future returned by either of these methods owns the `Debuggee`
240    /// and hence no other methods can be invoked.
241    ///
242    /// When paused, the holder of this object can access the `Store`
243    /// indirectly by providing a closure
244    pub fn new<F>(mut store: Store<T>, inner: F) -> Debuggee<T>
245    where
246        F: for<'a> FnOnce(
247                &'a mut Store<T>,
248            ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
249            + Send
250            + 'static,
251    {
252        let engine = store.engine().clone();
253        let (in_tx, in_rx) = mpsc::channel(1);
254        let (out_tx, out_rx) = mpsc::channel(1);
255        let interrupt_pending = Arc::new(AtomicBool::new(false));
256
257        let handle = tokio::spawn({
258            let interrupt_pending = interrupt_pending.clone();
259            async move {
260                // Create the handler that's invoked from within the async
261                // debug-event callback.
262                let out_tx_clone = out_tx.clone();
263                let handler = Handler(Arc::new(HandlerInner {
264                    in_rx: Mutex::new(in_rx),
265                    out_tx,
266                    interrupt_pending,
267                }));
268
269                // Emulate a breakpoint at startup.
270                log::trace!("inner debuggee task: first breakpoint");
271                handler
272                    .handle(store.as_context_mut(), DebugEvent::Breakpoint)
273                    .await;
274                log::trace!("inner debuggee task: first breakpoint resumed");
275
276                // Now invoke the actual inner body.
277                store.set_debug_handler(handler);
278                log::trace!("inner debuggee task: running `inner`");
279                let result = inner(&mut store).await;
280                log::trace!("inner debuggee task: done with `inner`");
281                let _ = out_tx_clone.send(Response::Finished(store)).await;
282                result
283            }
284        });
285
286        Debuggee {
287            engine,
288            state: DebuggeeState::Initial,
289            store: None,
290            in_tx,
291            out_rx,
292            interrupt_pending,
293            handle: Some(handle),
294        }
295    }
296
297    /// Is the inner body done running?
298    pub fn is_complete(&self) -> bool {
299        match self.state {
300            DebuggeeState::Complete => true,
301            _ => false,
302        }
303    }
304
305    /// Get the Engine associated with the debuggee.
306    pub fn engine(&self) -> &Engine {
307        &self.engine
308    }
309
310    /// Get the interrupt-pending flag. Setting this to `true` causes
311    /// the next epoch yield to surface as an `Interrupted` event.
312    pub fn interrupt_pending(&self) -> &Arc<AtomicBool> {
313        &self.interrupt_pending
314    }
315
316    async fn wait_for_initial(&mut self) -> Result<()> {
317        if let DebuggeeState::Initial = &self.state {
318            // Need to receive and discard first `Paused`.
319            let response = self
320                .out_rx
321                .recv()
322                .await
323                .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
324            assert!(matches!(response, Response::Paused(_)));
325            self.state = DebuggeeState::Paused;
326        }
327        Ok(())
328    }
329
330    /// Run the inner body until the next debug event.
331    ///
332    /// This method is cancel-safe, and no events will be lost.
333    pub async fn run(&mut self) -> Result<DebugRunResult> {
334        log::trace!("running: state is {:?}", self.state);
335
336        self.wait_for_initial().await?;
337
338        match self.state {
339            DebuggeeState::Initial => unreachable!(),
340            DebuggeeState::Paused => {
341                log::trace!("sending Continue");
342                self.in_tx
343                    .send(Command::Continue)
344                    .await
345                    .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
346                log::trace!("sent Continue");
347
348                // If that `send` was canceled, the command was not
349                // sent, so it's fine to remain in `Paused`. If it
350                // succeeded and we reached here, transition to
351                // `Running` so we don't re-send.
352                self.state = DebuggeeState::Running;
353            }
354            DebuggeeState::Running => {
355                // Previous `run()` must have been canceled; no action
356                // to take here.
357            }
358            DebuggeeState::Queried => {
359                // We expect to receive a `QueryResponse`; drop it if
360                // the query was canceled, then transition back to
361                // `Paused`.
362                log::trace!("in Queried; receiving");
363                let response =
364                    self.out_rx.recv().await.ok_or_else(|| {
365                        wasmtime::format_err!("Premature close of debugger channel")
366                    })?;
367                log::trace!("in Queried; received, dropping");
368                assert!(matches!(response, Response::QueryResponse(_)));
369                self.state = DebuggeeState::Paused;
370
371                // Now send a `Continue`, as above.
372                log::trace!("in Paused; sending Continue");
373                self.in_tx
374                    .send(Command::Continue)
375                    .await
376                    .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
377                self.state = DebuggeeState::Running;
378            }
379            DebuggeeState::Complete => {
380                panic!("Cannot `run()` an already-complete Debuggee");
381            }
382        }
383
384        // At this point, the inner task is in Running state. We
385        // expect to receive a message when it next pauses or
386        // completes. If this `recv()` is canceled, no message is
387        // lost, and the state above accurately reflects what must be
388        // done on the next `run()`.
389        log::trace!("waiting for response");
390        let response = self
391            .out_rx
392            .recv()
393            .await
394            .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
395
396        match response {
397            Response::Finished(store) => {
398                log::trace!("got Finished");
399                self.state = DebuggeeState::Complete;
400                self.store = Some(store);
401                Ok(DebugRunResult::Finished)
402            }
403            Response::Paused(result) => {
404                log::trace!("got Paused");
405                self.state = DebuggeeState::Paused;
406                Ok(result)
407            }
408            Response::QueryResponse(_) => {
409                panic!("Invalid debug response");
410            }
411        }
412    }
413
414    /// Run the debugger body until completion, with no further events.
415    pub async fn finish(&mut self) -> Result<()> {
416        if self.is_complete() {
417            return Ok(());
418        }
419        loop {
420            match self.run().await? {
421                DebugRunResult::Finished => break,
422                e => {
423                    log::trace!("finish: event {e:?}");
424                }
425            }
426        }
427        if let Some(handle) = self.handle.take() {
428            handle.await??;
429        }
430        assert!(self.is_complete());
431        Ok(())
432    }
433
434    /// Perform some action on the contained `Store` while not running.
435    ///
436    /// This may only be invoked before the inner body finishes and
437    /// when it is paused; that is, when the `Debuggee` is initially
438    /// created and after any call to `run()` returns a result other
439    /// than `DebugRunResult::Finished`. If an earlier `run()`
440    /// invocation was canceled, it must be re-invoked and return
441    /// successfully before a query is made.
442    ///
443    /// This is cancel-safe; if canceled, the result of the query will
444    /// be dropped.
445    pub async fn with_store<
446        F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
447        R: Send + 'static,
448    >(
449        &mut self,
450        f: F,
451    ) -> Result<R> {
452        if let Some(store) = self.store.as_mut() {
453            return Ok(f(store.as_context_mut()));
454        }
455
456        self.wait_for_initial().await?;
457
458        match self.state {
459            DebuggeeState::Initial => unreachable!(),
460            DebuggeeState::Queried => {
461                // Earlier query canceled; drop its response first.
462                let response =
463                    self.out_rx.recv().await.ok_or_else(|| {
464                        wasmtime::format_err!("Premature close of debugger channel")
465                    })?;
466                assert!(matches!(response, Response::QueryResponse(_)));
467                self.state = DebuggeeState::Paused;
468            }
469            DebuggeeState::Running => {
470                // Results from a canceled `run()`; `run()` must
471                // complete before this can be invoked.
472                panic!("Cannot query in Running state");
473            }
474            DebuggeeState::Complete => {
475                panic!("Cannot query when complete");
476            }
477            DebuggeeState::Paused => {
478                // OK -- this is the state we want.
479            }
480        }
481
482        log::trace!("sending query in with_store");
483        self.in_tx
484            .send(Command::Query(Box::new(|store| Box::new(f(store)))))
485            .await
486            .map_err(|_| wasmtime::format_err!("Premature close of debugger channel"))?;
487        self.state = DebuggeeState::Queried;
488
489        let response = self
490            .out_rx
491            .recv()
492            .await
493            .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
494        let Response::QueryResponse(resp) = response else {
495            wasmtime::bail!("Incorrect response from debugger task");
496        };
497        self.state = DebuggeeState::Paused;
498
499        Ok(*resp.downcast::<R>().expect("type mismatch"))
500    }
501}
502
503/// The result of one call to `Debuggee::run()`.
504///
505/// This is similar to `DebugEvent` but without the lifetime, so it
506/// can be sent across async tasks, and incorporates the possibility
507/// of completion (`Finished`) as well.
508#[derive(Debug)]
509pub enum DebugRunResult {
510    /// Execution of the inner body finished.
511    Finished,
512    /// An error was raised by a hostcall.
513    HostcallError,
514    /// Wasm execution was interrupted by an epoch change.
515    EpochYield,
516    /// An exception is thrown and caught by Wasm. The current state
517    /// is at the throw-point.
518    CaughtExceptionThrown(OwnedRooted<ExnRef>),
519    /// An exception was not caught and is escaping to the host.
520    UncaughtExceptionThrown(OwnedRooted<ExnRef>),
521    /// A Wasm trap occurred.
522    Trap(Trap),
523    /// A breakpoint was reached.
524    Breakpoint,
525}
526
527#[cfg(test)]
528mod test {
529    use super::*;
530    use wasmtime::*;
531
532    #[tokio::test]
533    #[cfg_attr(miri, ignore)]
534    async fn basic_debugger() -> wasmtime::Result<()> {
535        let _ = env_logger::try_init();
536
537        let mut config = Config::new();
538        config.guest_debug(true);
539        let engine = Engine::new(&config)?;
540        let module = Module::new(
541            &engine,
542            r#"
543                (module
544                  (func (export "main") (param i32 i32) (result i32)
545                    local.get 0
546                    local.get 1
547                    i32.add))
548            "#,
549        )?;
550
551        let mut store = Store::new(&engine, ());
552        let instance = Instance::new_async(&mut store, &module, &[]).await?;
553        let main = instance.get_func(&mut store, "main").unwrap();
554
555        let mut debuggee = Debuggee::new(store, move |store| {
556            Box::pin(async move {
557                let mut results = [Val::I32(0)];
558                store.edit_breakpoints().unwrap().single_step(true).unwrap();
559                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
560                    .await?;
561                assert_eq!(results[0].unwrap_i32(), 3);
562                main.call_async(&mut *store, &[Val::I32(3), Val::I32(4)], &mut results[..])
563                    .await?;
564                assert_eq!(results[0].unwrap_i32(), 7);
565                Ok(())
566            })
567        });
568
569        let event = debuggee.run().await?;
570        assert!(matches!(event, DebugRunResult::Breakpoint));
571        // At (before executing) first `local.get`.
572        debuggee
573            .with_store(|mut store| {
574                let frame = store.debug_exit_frames().next().unwrap();
575                assert_eq!(
576                    frame
577                        .wasm_function_index_and_pc(&mut store)
578                        .unwrap()
579                        .unwrap()
580                        .0
581                        .as_u32(),
582                    0
583                );
584                assert_eq!(
585                    frame
586                        .wasm_function_index_and_pc(&mut store)
587                        .unwrap()
588                        .unwrap()
589                        .1
590                        .raw(),
591                    36
592                );
593                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
594                assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
595                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
596                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
597                let frame = frame.parent(&mut store).unwrap();
598                assert!(frame.is_none());
599            })
600            .await?;
601
602        let event = debuggee.run().await?;
603        // At second `local.get`.
604        assert!(matches!(event, DebugRunResult::Breakpoint));
605        debuggee
606            .with_store(|mut store| {
607                let frame = store.debug_exit_frames().next().unwrap();
608                assert_eq!(
609                    frame
610                        .wasm_function_index_and_pc(&mut store)
611                        .unwrap()
612                        .unwrap()
613                        .0
614                        .as_u32(),
615                    0
616                );
617                assert_eq!(
618                    frame
619                        .wasm_function_index_and_pc(&mut store)
620                        .unwrap()
621                        .unwrap()
622                        .1
623                        .raw(),
624                    38
625                );
626                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
627                assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
628                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
629                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
630                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
631                let frame = frame.parent(&mut store).unwrap();
632                assert!(frame.is_none());
633            })
634            .await?;
635
636        let event = debuggee.run().await?;
637        // At `i32.add`.
638        assert!(matches!(event, DebugRunResult::Breakpoint));
639        debuggee
640            .with_store(|mut store| {
641                let frame = store.debug_exit_frames().next().unwrap();
642                assert_eq!(
643                    frame
644                        .wasm_function_index_and_pc(&mut store)
645                        .unwrap()
646                        .unwrap()
647                        .0
648                        .as_u32(),
649                    0
650                );
651                assert_eq!(
652                    frame
653                        .wasm_function_index_and_pc(&mut store)
654                        .unwrap()
655                        .unwrap()
656                        .1
657                        .raw(),
658                    40
659                );
660                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
661                assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
662                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
663                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
664                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
665                assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
666                let frame = frame.parent(&mut store).unwrap();
667                assert!(frame.is_none());
668            })
669            .await?;
670
671        let event = debuggee.run().await?;
672        // At return point.
673        assert!(matches!(event, DebugRunResult::Breakpoint));
674        debuggee
675            .with_store(|mut store| {
676                let frame = store.debug_exit_frames().next().unwrap();
677                assert_eq!(
678                    frame
679                        .wasm_function_index_and_pc(&mut store)
680                        .unwrap()
681                        .unwrap()
682                        .0
683                        .as_u32(),
684                    0
685                );
686                assert_eq!(
687                    frame
688                        .wasm_function_index_and_pc(&mut store)
689                        .unwrap()
690                        .unwrap()
691                        .1
692                        .raw(),
693                    41
694                );
695                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
696                assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
697                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
698                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
699                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
700                let frame = frame.parent(&mut store).unwrap();
701                assert!(frame.is_none());
702            })
703            .await?;
704
705        // Now disable breakpoints before continuing. Second call should proceed with no more events.
706        debuggee
707            .with_store(|store| {
708                store
709                    .edit_breakpoints()
710                    .unwrap()
711                    .single_step(false)
712                    .unwrap();
713            })
714            .await?;
715
716        let event = debuggee.run().await?;
717        assert!(matches!(event, DebugRunResult::Finished));
718
719        assert!(debuggee.is_complete());
720
721        Ok(())
722    }
723
724    #[tokio::test]
725    #[cfg_attr(miri, ignore)]
726    async fn early_finish() -> Result<()> {
727        let _ = env_logger::try_init();
728
729        let mut config = Config::new();
730        config.guest_debug(true);
731        let engine = Engine::new(&config)?;
732        let module = Module::new(
733            &engine,
734            r#"
735                (module
736                  (func (export "main") (param i32 i32) (result i32)
737                    local.get 0
738                    local.get 1
739                    i32.add))
740            "#,
741        )?;
742
743        let mut store = Store::new(&engine, ());
744        let instance = Instance::new_async(&mut store, &module, &[]).await?;
745        let main = instance.get_func(&mut store, "main").unwrap();
746
747        let mut debuggee = Debuggee::new(store, move |store| {
748            Box::pin(async move {
749                let mut results = [Val::I32(0)];
750                store.edit_breakpoints().unwrap().single_step(true).unwrap();
751                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
752                    .await?;
753                assert_eq!(results[0].unwrap_i32(), 3);
754                Ok(())
755            })
756        });
757
758        debuggee.finish().await?;
759        assert!(debuggee.is_complete());
760
761        Ok(())
762    }
763
764    #[tokio::test]
765    #[cfg_attr(miri, ignore)]
766    async fn drop_debuggee_and_store() -> Result<()> {
767        let _ = env_logger::try_init();
768
769        let mut config = Config::new();
770        config.guest_debug(true);
771        let engine = Engine::new(&config)?;
772        let module = Module::new(
773            &engine,
774            r#"
775                (module
776                  (func (export "main") (param i32 i32) (result i32)
777                    local.get 0
778                    local.get 1
779                    i32.add))
780            "#,
781        )?;
782
783        let mut store = Store::new(&engine, ());
784        let instance = Instance::new_async(&mut store, &module, &[]).await?;
785        let main = instance.get_func(&mut store, "main").unwrap();
786
787        let mut debuggee = Debuggee::new(store, move |store| {
788            Box::pin(async move {
789                let mut results = [Val::I32(0)];
790                store.edit_breakpoints().unwrap().single_step(true).unwrap();
791                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
792                    .await?;
793                assert_eq!(results[0].unwrap_i32(), 3);
794                Ok(())
795            })
796        });
797
798        // Step once, then drop everything at the end of this
799        // function. Wasmtime's fiber cleanup should safely happen
800        // without attempting to raise debug async handler calls with
801        // missing async context.
802        let _ = debuggee.run().await?;
803
804        Ok(())
805    }
806}