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, future::Future, pin::Pin, sync::Arc}; 13 use tokio::sync::{Mutex, mpsc}; 14 use 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`. 30 pub 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)] 76 enum 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. 127 enum Command<T: 'static> { 128 Continue, 129 Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>), 130 } 131 132 enum Response<T: 'static> { 133 Paused(DebugRunResult), 134 QueryResponse(Box<dyn Any + Send>), 135 Finished(Store<T>), 136 } 137 138 struct HandlerInner<T: Send + 'static> { 139 in_rx: Mutex<mpsc::Receiver<Command<T>>>, 140 out_tx: mpsc::Sender<Response<T>>, 141 } 142 143 struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>); 144 145 impl<T: Send + 'static> std::clone::Clone for Handler<T> { 146 fn clone(&self) -> Self { 147 Handler(self.0.clone()) 148 } 149 } 150 151 impl<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 190 impl<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)] 454 pub 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)] 473 mod 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 } 748