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 debugee 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::{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
19/// A `Debugger` wraps up state associated with debugging the code
20/// running in a single `Store`.
21///
22/// It acts as a Future combinator, wrapping an inner async body that
23/// performs some actions on a store. Those actions are subject to the
24/// debugger, and debugger events will be raised as appropriate. From
25/// the "outside" of this combinator, it is always in one of two
26/// states: running or paused. When paused, it acts as a
27/// `StoreContextMut` and can allow examining the paused execution's
28/// state. One runs until the next event suspends execution by
29/// invoking `Debugger::run`.
30pub struct Debugger<T: Send + 'static> {
31    /// A handle to the Engine that the debuggee store lives within.
32    engine: Engine,
33    /// State: either a task handle or the store when passed out of
34    /// the complete task.
35    state: DebuggerState,
36    /// The store, once complete.
37    store: Option<Store<T>>,
38    in_tx: mpsc::Sender<Command<T>>,
39    out_rx: mpsc::Receiver<Response<T>>,
40}
41
42/// State machine from the perspective of the outer logic.
43///
44/// The intermediate states here, and the separation of these states
45/// from the `JoinHandle` above, are what allow us to implement a
46/// cancel-safe version of `Debugger::run` below.
47///
48/// The state diagram for the outer logic is:
49///
50/// ```plain
51///              (start)
52///                 v
53///                 |
54/// .--->---------. v
55/// |     .----<  Paused  <-----------------------------------------------.
56/// |     |         v                                                     |
57/// |     |         | (async fn run() starts, sends Command::Continue)    |
58/// |     |         |                                                     |
59/// |     |         v                                                     ^
60/// |     |      Running                                                  |
61/// |     |       v v (async fn run() receives Response::Paused, returns) |
62/// |     |       | |_____________________________________________________|
63/// |     |       |
64/// |     |       | (async fn run() receives Response::Finished, returns)
65/// |     |       v
66/// |     |     Complete
67/// |     |
68/// ^     | (async fn with_store() starts, sends Command::Query)
69/// |     v
70/// |   Queried
71/// |     |
72/// |     | (async fn with_store() receives Response::QueryResponse, returns)
73/// `---<-'
74/// ```
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76enum DebuggerState {
77    /// Inner body has just been started.
78    Initial,
79    /// Inner body is running in an async task and not in a debugger
80    /// callback. Outer logic is waiting for a `Response::Paused` or
81    /// `Response::Complete`.
82    Running,
83    /// Inner body is running in an async task and at a debugger
84    /// callback (or in the initial trampoline waiting for the first
85    /// `Continue`). `Response::Paused` has been received. Outer
86    /// logic has not sent any commands.
87    Paused,
88    /// We have sent a command to the inner body and are waiting for a
89    /// response.
90    Queried,
91    /// Inner body is complete (has sent `Response::Finished` and we
92    /// have received it). We may or may not have joined yet; if so,
93    /// the `Option<JoinHandle<...>>` will be `None`.
94    Complete,
95}
96
97/// Message from "outside" to the debug hook.
98///
99/// The `Query` catch-all with a boxed closure is a little janky, but
100/// is the way that we provide access
101/// from outside to the Store (which is owned by `inner` above)
102/// only during pauses. Note that the future cannot take full
103/// ownership or a mutable borrow of the Store, because it cannot
104/// hold this across async yield points.
105///
106/// Instead, the debugger body sends boxed closures which take the
107/// Store as a parameter (lifetime-limited not to escape that
108/// closure) out to this crate's implementation that runs inside of
109/// debugger-instrumentation callbacks (which have access to the
110/// Store during their duration). We send return values
111/// back. Return values are boxed Any values.
112///
113/// If we wanted to make this a little more principled, we could
114/// come up with a Command/Response pair of enums for all possible
115/// closures and make everything more statically typed and less
116/// Box'd, but that would severely restrict the flexibility of the
117/// abstraction here and essentially require writing a full proxy
118/// of the debugger API.
119///
120/// Furthermore, we expect to rip this out eventually when we move
121/// the debugger over to an async implementation based on
122/// `run_concurrent` and `Accessor`s (see #11896). Building things
123/// this way now will actually allow a less painful transition at
124/// that time, because we will have a bunch of closures accessing
125/// the store already and we can run those "with an accessor"
126/// instead.
127enum 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    /// Create a new Debugger that attaches to the given Store and
192    /// runs the given inner body.
193    ///
194    /// The debugger is always in one of two states: running or
195    /// paused.
196    ///
197    /// When paused, the holder of this object can invoke
198    /// `Debugger::run` to enter the running state. The inner body
199    /// will run until paused by a debug event. While running, the
200    /// future returned by either of these methods owns the `Debugger`
201    /// and hence no other methods can be invoked.
202    ///
203    /// When paused, the holder of this object can access the `Store`
204    /// indirectly by providing a closure
205    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            // Create the handler that's invoked from within the async
219            // debug-event callback.
220            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            // Emulate a breakpoint at startup.
227            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            // Now invoke the actual inner body.
234            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    /// Is the inner body done running?
252    pub fn is_complete(&self) -> bool {
253        match self.state {
254            DebuggerState::Complete => true,
255            _ => false,
256        }
257    }
258
259    /// Get the Engine associated with the debuggee.
260    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            // Need to receive and discard first `Paused`.
267            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    /// Run the inner body until the next debug event.
279    ///
280    /// This method is cancel-safe, and no events will be lost.
281    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                // If that `send` was canceled, the command was not
297                // sent, so it's fine to remain in `Paused`. If it
298                // succeeded and we reached here, transition to
299                // `Running` so we don't re-send.
300                self.state = DebuggerState::Running;
301            }
302            DebuggerState::Running => {
303                // Previous `run()` must have been canceled; no action
304                // to take here.
305            }
306            DebuggerState::Queried => {
307                // We expect to receive a `QueryResponse`; drop it if
308                // the query was canceled, then transition back to
309                // `Paused`.
310                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                // Now send a `Continue`, as above.
320                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        // At this point, the inner task is in Running state. We
333        // expect to receive a message when it next pauses or
334        // completes. If this `recv()` is canceled, no message is
335        // lost, and the state above accurately reflects what must be
336        // done on the next `run()`.
337        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    /// Run the debugger body until completion, with no further events.
363    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    /// Perform some action on the contained `Store` while not running.
380    ///
381    /// This may only be invoked before the inner body finishes and
382    /// when it is paused; that is, when the `Debugger` is initially
383    /// created and after any call to `run()` returns a result other
384    /// than `DebugRunResult::Finished`. If an earlier `run()`
385    /// invocation was canceled, it must be re-invoked and return
386    /// successfully before a query is made.
387    ///
388    /// This is cancel-safe; if canceled, the result of the query will
389    /// be dropped.
390    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                // Earlier query canceled; drop its response first.
407                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                // Results from a canceled `run()`; `run()` must
416                // complete before this can be invoked.
417                panic!("Cannot query in Running state");
418            }
419            DebuggerState::Complete => {
420                panic!("Cannot query when complete");
421            }
422            DebuggerState::Paused => {
423                // OK -- this is the state we want.
424            }
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/// The result of one call to `Debugger::run()`.
449///
450/// This is similar to `DebugEvent` but without the lifetime, so it
451/// can be sent across async tasks, and incorporates the possibility
452/// of completion (`Finished`) as well.
453#[derive(Debug)]
454pub enum DebugRunResult {
455    /// Execution of the inner body finished.
456    Finished,
457    /// An error was raised by a hostcall.
458    HostcallError,
459    /// Wasm execution was interrupted by an epoch change.
460    EpochYield,
461    /// An exception is thrown and caught by Wasm. The current state
462    /// is at the throw-point.
463    CaughtExceptionThrown(OwnedRooted<ExnRef>),
464    /// An exception was not caught and is escaping to the host.
465    UncaughtExceptionThrown(OwnedRooted<ExnRef>),
466    /// A Wasm trap occurred.
467    Trap(Trap),
468    /// A breakpoint was reached.
469    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        // At (before executing) first `local.get`.
517        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        // At second `local.get`.
548        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        // At `i32.add`.
581        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        // At return point.
615        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        // Now disable breakpoints before continuing. Second call should proceed with no more events.
647        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        // Step once, then drop everything at the end of this
740        // function. Wasmtime's fiber cleanup should safely happen
741        // without attempting to raise debug async handler calls with
742        // missing async context.
743        let _ = debugger.run().await?;
744
745        Ok(())
746    }
747}