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}