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