xref: /wasmtime-44.0.1/crates/debugger/src/lib.rs (revision dbaaa92f)
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 
12 use std::{
13     any::Any,
14     future::Future,
15     pin::Pin,
16     sync::{
17         Arc,
18         atomic::{AtomicBool, Ordering},
19     },
20 };
21 use tokio::{
22     sync::{Mutex, mpsc},
23     task::JoinHandle,
24 };
25 use wasmtime::{
26     AsContextMut, DebugEvent, DebugHandler, Engine, ExnRef, OwnedRooted, Result, Store,
27     StoreContextMut, Trap,
28 };
29 
30 mod host;
31 pub 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`.
44 pub 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)]
98 enum 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.
149 enum Command<T: 'static> {
150     Continue,
151     Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
152 }
153 
154 enum Response<T: 'static> {
155     Paused(DebugRunResult),
156     QueryResponse(Box<dyn Any + Send>),
157     Finished(Store<T>),
158 }
159 
160 struct 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 
166 struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);
167 
168 impl<T: Send + 'static> std::clone::Clone for Handler<T> {
169     fn clone(&self) -> Self {
170         Handler(self.0.clone())
171     }
172 }
173 
174 impl<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 
229 impl<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)]
509 pub 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)]
528 mod 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                     36
591                 );
592                 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
593                 assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
594                 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
595                 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
596                 let frame = frame.parent(&mut store).unwrap();
597                 assert!(frame.is_none());
598             })
599             .await?;
600 
601         let event = debuggee.run().await?;
602         // At second `local.get`.
603         assert!(matches!(event, DebugRunResult::Breakpoint));
604         debuggee
605             .with_store(|mut store| {
606                 let frame = store.debug_exit_frames().next().unwrap();
607                 assert_eq!(
608                     frame
609                         .wasm_function_index_and_pc(&mut store)
610                         .unwrap()
611                         .unwrap()
612                         .0
613                         .as_u32(),
614                     0
615                 );
616                 assert_eq!(
617                     frame
618                         .wasm_function_index_and_pc(&mut store)
619                         .unwrap()
620                         .unwrap()
621                         .1,
622                     38
623                 );
624                 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
625                 assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
626                 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
627                 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
628                 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
629                 let frame = frame.parent(&mut store).unwrap();
630                 assert!(frame.is_none());
631             })
632             .await?;
633 
634         let event = debuggee.run().await?;
635         // At `i32.add`.
636         assert!(matches!(event, DebugRunResult::Breakpoint));
637         debuggee
638             .with_store(|mut store| {
639                 let frame = store.debug_exit_frames().next().unwrap();
640                 assert_eq!(
641                     frame
642                         .wasm_function_index_and_pc(&mut store)
643                         .unwrap()
644                         .unwrap()
645                         .0
646                         .as_u32(),
647                     0
648                 );
649                 assert_eq!(
650                     frame
651                         .wasm_function_index_and_pc(&mut store)
652                         .unwrap()
653                         .unwrap()
654                         .1,
655                     40
656                 );
657                 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
658                 assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
659                 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
660                 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
661                 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
662                 assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
663                 let frame = frame.parent(&mut store).unwrap();
664                 assert!(frame.is_none());
665             })
666             .await?;
667 
668         let event = debuggee.run().await?;
669         // At return point.
670         assert!(matches!(event, DebugRunResult::Breakpoint));
671         debuggee
672             .with_store(|mut store| {
673                 let frame = store.debug_exit_frames().next().unwrap();
674                 assert_eq!(
675                     frame
676                         .wasm_function_index_and_pc(&mut store)
677                         .unwrap()
678                         .unwrap()
679                         .0
680                         .as_u32(),
681                     0
682                 );
683                 assert_eq!(
684                     frame
685                         .wasm_function_index_and_pc(&mut store)
686                         .unwrap()
687                         .unwrap()
688                         .1,
689                     41
690                 );
691                 assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
692                 assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
693                 assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
694                 assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
695                 assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
696                 let frame = frame.parent(&mut store).unwrap();
697                 assert!(frame.is_none());
698             })
699             .await?;
700 
701         // Now disable breakpoints before continuing. Second call should proceed with no more events.
702         debuggee
703             .with_store(|store| {
704                 store
705                     .edit_breakpoints()
706                     .unwrap()
707                     .single_step(false)
708                     .unwrap();
709             })
710             .await?;
711 
712         let event = debuggee.run().await?;
713         assert!(matches!(event, DebugRunResult::Finished));
714 
715         assert!(debuggee.is_complete());
716 
717         Ok(())
718     }
719 
720     #[tokio::test]
721     #[cfg_attr(miri, ignore)]
722     async fn early_finish() -> Result<()> {
723         let _ = env_logger::try_init();
724 
725         let mut config = Config::new();
726         config.guest_debug(true);
727         let engine = Engine::new(&config)?;
728         let module = Module::new(
729             &engine,
730             r#"
731                 (module
732                   (func (export "main") (param i32 i32) (result i32)
733                     local.get 0
734                     local.get 1
735                     i32.add))
736             "#,
737         )?;
738 
739         let mut store = Store::new(&engine, ());
740         let instance = Instance::new_async(&mut store, &module, &[]).await?;
741         let main = instance.get_func(&mut store, "main").unwrap();
742 
743         let mut debuggee = Debuggee::new(store, move |store| {
744             Box::pin(async move {
745                 let mut results = [Val::I32(0)];
746                 store.edit_breakpoints().unwrap().single_step(true).unwrap();
747                 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
748                     .await?;
749                 assert_eq!(results[0].unwrap_i32(), 3);
750                 Ok(())
751             })
752         });
753 
754         debuggee.finish().await?;
755         assert!(debuggee.is_complete());
756 
757         Ok(())
758     }
759 
760     #[tokio::test]
761     #[cfg_attr(miri, ignore)]
762     async fn drop_debuggee_and_store() -> Result<()> {
763         let _ = env_logger::try_init();
764 
765         let mut config = Config::new();
766         config.guest_debug(true);
767         let engine = Engine::new(&config)?;
768         let module = Module::new(
769             &engine,
770             r#"
771                 (module
772                   (func (export "main") (param i32 i32) (result i32)
773                     local.get 0
774                     local.get 1
775                     i32.add))
776             "#,
777         )?;
778 
779         let mut store = Store::new(&engine, ());
780         let instance = Instance::new_async(&mut store, &module, &[]).await?;
781         let main = instance.get_func(&mut store, "main").unwrap();
782 
783         let mut debuggee = Debuggee::new(store, move |store| {
784             Box::pin(async move {
785                 let mut results = [Val::I32(0)];
786                 store.edit_breakpoints().unwrap().single_step(true).unwrap();
787                 main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
788                     .await?;
789                 assert_eq!(results[0].unwrap_i32(), 3);
790                 Ok(())
791             })
792         });
793 
794         // Step once, then drop everything at the end of this
795         // function. Wasmtime's fiber cleanup should safely happen
796         // without attempting to raise debug async handler calls with
797         // missing async context.
798         let _ = debuggee.run().await?;
799 
800         Ok(())
801     }
802 }
803