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