1 //! Tests for instrumentation-based debugging. 2 3 use std::sync::Arc; 4 use std::sync::atomic::{AtomicUsize, Ordering}; 5 use wasmtime::{ 6 AsContextMut, Caller, Config, DebugEvent, DebugHandler, Engine, Extern, FrameParentResult, 7 Func, Instance, Module, Store, StoreContextMut, Val, 8 }; 9 10 #[test] 11 fn debugging_does_not_work_with_signal_based_traps() { 12 let mut config = Config::default(); 13 config.guest_debug(true).signals_based_traps(true); 14 let err = Engine::new(&config).expect_err("invalid config should produce an error"); 15 assert!(format!("{err:?}").contains("cannot use signals-based traps")); 16 } 17 18 fn get_module_and_store<C: Fn(&mut Config)>( 19 c: C, 20 wat: &str, 21 ) -> wasmtime::Result<(Module, Store<()>)> { 22 let mut config = Config::default(); 23 config.guest_debug(true); 24 config.wasm_exceptions(true); 25 c(&mut config); 26 let engine = Engine::new(&config)?; 27 let module = Module::new(&engine, wat)?; 28 Ok((module, Store::new(&engine, ()))) 29 } 30 31 fn test_stack_values<C: Fn(&mut Config), F: Fn(Caller<'_, ()>) + Send + Sync + 'static>( 32 wat: &str, 33 c: C, 34 f: F, 35 ) -> wasmtime::Result<()> { 36 let (module, mut store) = get_module_and_store(c, wat)?; 37 let func = Func::wrap(&mut store, move |caller: Caller<'_, ()>| { 38 f(caller); 39 }); 40 let instance = Instance::new(&mut store, &module, &[Extern::Func(func)])?; 41 let mut results = []; 42 instance 43 .get_func(&mut store, "main") 44 .unwrap() 45 .call(&mut store, &[], &mut results)?; 46 47 Ok(()) 48 } 49 50 #[test] 51 #[cfg_attr(miri, ignore)] 52 fn stack_values_two_frames() -> wasmtime::Result<()> { 53 let _ = env_logger::try_init(); 54 55 for inlining in [false, true] { 56 test_stack_values( 57 r#" 58 (module 59 (import "" "host" (func)) 60 (func (export "main") 61 i32.const 1 62 i32.const 2 63 call 2 64 drop) 65 (func (param i32 i32) (result i32) 66 local.get 0 67 local.get 1 68 call 0 69 i32.add)) 70 "#, 71 |config| { 72 config.compiler_inlining(inlining); 73 if inlining { 74 unsafe { 75 config.cranelift_flag_set("wasmtime_inlining_intra_module", "true"); 76 } 77 } 78 }, 79 |mut caller: Caller<'_, ()>| { 80 let mut stack = caller.debug_frames().unwrap(); 81 assert!(!stack.done()); 82 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 1); 83 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 65); 84 85 assert_eq!(stack.num_locals(), 2); 86 assert_eq!(stack.num_stacks(), 2); 87 assert_eq!(stack.local(0).unwrap_i32(), 1); 88 assert_eq!(stack.local(1).unwrap_i32(), 2); 89 assert_eq!(stack.stack(0).unwrap_i32(), 1); 90 assert_eq!(stack.stack(1).unwrap_i32(), 2); 91 92 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 93 assert!(!stack.done()); 94 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 0); 95 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 55); 96 97 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 98 assert!(stack.done()); 99 }, 100 )?; 101 } 102 Ok(()) 103 } 104 105 #[test] 106 #[cfg_attr(miri, ignore)] 107 fn stack_values_exceptions() -> wasmtime::Result<()> { 108 test_stack_values( 109 r#" 110 (module 111 (tag $t (param i32)) 112 (import "" "host" (func)) 113 (func (export "main") 114 (block $b (result i32) 115 (try_table (catch $t $b) 116 (throw $t (i32.const 42))) 117 i32.const 0) 118 (call 0) 119 (drop))) 120 "#, 121 |_config| {}, 122 |mut caller: Caller<'_, ()>| { 123 let mut stack = caller.debug_frames().unwrap(); 124 assert!(!stack.done()); 125 assert_eq!(stack.num_stacks(), 1); 126 assert_eq!(stack.stack(0).unwrap_i32(), 42); 127 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 128 assert!(stack.done()); 129 }, 130 ) 131 } 132 133 #[test] 134 #[cfg_attr(miri, ignore)] 135 fn stack_values_dead_gc_ref() -> wasmtime::Result<()> { 136 test_stack_values( 137 r#" 138 (module 139 (type $s (struct)) 140 (import "" "host" (func)) 141 (func (export "main") 142 (struct.new $s) 143 (call 0) 144 (drop))) 145 "#, 146 |config| { 147 config.wasm_gc(true); 148 }, 149 |mut caller: Caller<'_, ()>| { 150 let mut stack = caller.debug_frames().unwrap(); 151 assert!(!stack.done()); 152 assert_eq!(stack.num_stacks(), 1); 153 assert!(stack.stack(0).unwrap_anyref().is_some()); 154 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 155 assert!(stack.done()); 156 }, 157 ) 158 } 159 160 #[test] 161 #[cfg_attr(miri, ignore)] 162 fn gc_access_during_call() -> wasmtime::Result<()> { 163 test_stack_values( 164 r#" 165 (module 166 (type $s (struct (field i32))) 167 (import "" "host" (func)) 168 (func (export "main") 169 (local $l (ref null $s)) 170 (local.set $l (struct.new $s (i32.const 42))) 171 (call 0))) 172 "#, 173 |config| { 174 config.wasm_gc(true); 175 }, 176 |mut caller: Caller<'_, ()>| { 177 let mut stack = caller.debug_frames().unwrap(); 178 179 // Do a GC while we hold the stack cursor. 180 stack.as_context_mut().gc(None); 181 182 assert!(!stack.done()); 183 assert_eq!(stack.num_stacks(), 0); 184 assert_eq!(stack.num_locals(), 1); 185 // Note that this struct is dead during the call, and the 186 // ref could otherwise be optimized away (no longer in the 187 // stackmap at this point); but we verify it is still 188 // alive here because it is rooted in the 189 // debug-instrumentation slot. 190 let s = stack 191 .local(0) 192 .unwrap_any_ref() 193 .unwrap() 194 .unwrap_struct(&stack) 195 .unwrap(); 196 assert_eq!(s.field(&mut stack, 0).unwrap().unwrap_i32(), 42); 197 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 198 assert!(stack.done()); 199 }, 200 ) 201 } 202 203 #[test] 204 #[cfg_attr(miri, ignore)] 205 fn stack_values_two_activations() -> wasmtime::Result<()> { 206 let _ = env_logger::try_init(); 207 208 let mut config = Config::default(); 209 config.guest_debug(true); 210 config.wasm_exceptions(true); 211 let engine = Engine::new(&config)?; 212 let module1 = Module::new( 213 &engine, 214 r#" 215 (module 216 (import "" "host1" (func (param i32 i32) (result i32))) 217 (func (export "main") (result i32) 218 i32.const 1 219 i32.const 2 220 call 0)) 221 "#, 222 )?; 223 let module2 = Module::new( 224 &engine, 225 r#" 226 (module 227 (import "" "host2" (func)) 228 (func (export "inner") (param i32 i32) (result i32) 229 local.get 0 230 local.get 1 231 call 0 232 i32.add)) 233 "#, 234 )?; 235 let mut store = Store::new(&engine, ()); 236 237 let module1_clone = module1.clone(); 238 let module2_clone = module2.clone(); 239 let host2 = Func::wrap(&mut store, move |mut caller: Caller<'_, ()>| { 240 let mut stack = caller.debug_frames().unwrap(); 241 assert!(!stack.done()); 242 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 0); 243 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 56); 244 assert!(Module::same(stack.module().unwrap(), &module2_clone)); 245 assert_eq!(stack.num_locals(), 2); 246 assert_eq!(stack.num_stacks(), 2); 247 assert_eq!(stack.local(0).unwrap_i32(), 1); 248 assert_eq!(stack.local(1).unwrap_i32(), 2); 249 assert_eq!(stack.stack(0).unwrap_i32(), 1); 250 assert_eq!(stack.stack(1).unwrap_i32(), 2); 251 let inner_instance = stack.instance(); 252 253 assert_eq!(stack.move_to_parent(), FrameParentResult::NewActivation); 254 assert!(!stack.done()); 255 256 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 0); 257 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 56); 258 assert!(Module::same(stack.module().unwrap(), &module1_clone)); 259 assert_eq!(stack.num_locals(), 0); 260 assert_eq!(stack.num_stacks(), 2); 261 assert_eq!(stack.stack(0).unwrap_i32(), 1); 262 assert_eq!(stack.stack(1).unwrap_i32(), 2); 263 let outer_instance = stack.instance(); 264 265 assert_ne!(inner_instance, outer_instance); 266 267 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 268 assert!(stack.done()); 269 }); 270 271 let instance2 = Instance::new(&mut store, &module2, &[Extern::Func(host2)])?; 272 let inner = instance2.get_func(&mut store, "inner").unwrap(); 273 274 let host1 = Func::wrap( 275 &mut store, 276 move |mut caller: Caller<'_, ()>, a: i32, b: i32| -> i32 { 277 let mut results = [Val::I32(0)]; 278 inner 279 .call(&mut caller, &[Val::I32(a), Val::I32(b)], &mut results[..]) 280 .unwrap(); 281 results[0].unwrap_i32() 282 }, 283 ); 284 285 let instance1 = Instance::new(&mut store, &module1, &[Extern::Func(host1)])?; 286 let main = instance1.get_func(&mut store, "main").unwrap(); 287 288 let mut results = [Val::I32(0)]; 289 main.call(&mut store, &[], &mut results)?; 290 assert_eq!(results[0].unwrap_i32(), 3); 291 Ok(()) 292 } 293 294 #[test] 295 #[cfg_attr(miri, ignore)] 296 fn debug_frames_on_store_with_no_wasm_activation() -> wasmtime::Result<()> { 297 let mut config = Config::default(); 298 config.guest_debug(true); 299 let engine = Engine::new(&config)?; 300 let mut store = Store::new(&engine, ()); 301 let frames = store 302 .debug_frames() 303 .expect("Debug frames should be available"); 304 assert!(frames.done()); 305 Ok(()) 306 } 307 308 macro_rules! debug_event_checker { 309 ($ty:tt, 310 $store:tt, 311 $( 312 { $i:expr ; $pat:pat => $body:tt } 313 ),*) 314 => 315 { 316 #[derive(Clone)] 317 struct $ty(Arc<AtomicUsize>); 318 impl $ty { 319 fn new_and_counter() -> (Self, Arc<AtomicUsize>) { 320 let counter = Arc::new(AtomicUsize::new(0)); 321 let counter_clone = counter.clone(); 322 ($ty(counter), counter_clone) 323 } 324 } 325 impl DebugHandler for $ty { 326 type Data = (); 327 fn handle( 328 &self, 329 #[allow(unused_variables, reason = "macro rules")] 330 #[allow(unused_mut, reason = "macro rules")] 331 mut $store: StoreContextMut<'_, ()>, 332 event: DebugEvent<'_>, 333 ) -> impl Future<Output = ()> + Send { 334 let step = self.0.fetch_add(1, Ordering::Relaxed); 335 async move { 336 if false {} 337 $( 338 else if step == $i { 339 match event { 340 $pat => { 341 $body; 342 } 343 _ => panic!("Incorrect event"), 344 } 345 } 346 )* 347 else { 348 panic!("Too many steps"); 349 } 350 } 351 } 352 } 353 } 354 } 355 356 #[tokio::test] 357 #[cfg_attr(miri, ignore)] 358 async fn uncaught_exception_events() -> wasmtime::Result<()> { 359 let _ = env_logger::try_init(); 360 361 let (module, mut store) = get_module_and_store( 362 |config| { 363 config.async_support(true); 364 config.wasm_exceptions(true); 365 }, 366 r#" 367 (module 368 (tag $t (param i32)) 369 (func (export "main") 370 call 1) 371 (func 372 (local $i i32) 373 (local.set $i (i32.const 100)) 374 (throw $t (i32.const 42)))) 375 "#, 376 )?; 377 378 debug_event_checker!( 379 D, store, 380 { 0 ; 381 wasmtime::DebugEvent::UncaughtExceptionThrown(e) => { 382 assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42); 383 let mut stack = store.debug_frames().expect("frame cursor must be available"); 384 assert!(!stack.done()); 385 assert_eq!(stack.num_locals(), 1); 386 assert_eq!(stack.local(0).unwrap_i32(), 100); 387 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 388 assert!(!stack.done()); 389 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 390 assert!(stack.done()); 391 } 392 } 393 ); 394 395 let (handler, counter) = D::new_and_counter(); 396 store.set_debug_handler(handler); 397 398 let instance = Instance::new_async(&mut store, &module, &[]).await?; 399 let func = instance.get_func(&mut store, "main").unwrap(); 400 let mut results = []; 401 let result = func.call_async(&mut store, &[], &mut results).await; 402 assert!(result.is_err()); // Uncaught exception. 403 assert_eq!(counter.load(Ordering::Relaxed), 1); 404 405 Ok(()) 406 } 407 408 #[tokio::test] 409 #[cfg_attr(miri, ignore)] 410 async fn caught_exception_events() -> wasmtime::Result<()> { 411 let _ = env_logger::try_init(); 412 413 let (module, mut store) = get_module_and_store( 414 |config| { 415 config.async_support(true); 416 config.wasm_exceptions(true); 417 }, 418 r#" 419 (module 420 (tag $t (param i32)) 421 (func (export "main") 422 (block $b (result i32) 423 (try_table (catch $t $b) 424 call 1) 425 i32.const 0) 426 drop) 427 (func 428 (local $i i32) 429 (local.set $i (i32.const 100)) 430 (throw $t (i32.const 42)))) 431 "#, 432 )?; 433 434 debug_event_checker!( 435 D, store, 436 { 0 ; 437 wasmtime::DebugEvent::CaughtExceptionThrown(e) => { 438 assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42); 439 let mut stack = store.debug_frames().expect("frame cursor must be available"); 440 assert!(!stack.done()); 441 assert_eq!(stack.num_locals(), 1); 442 assert_eq!(stack.local(0).unwrap_i32(), 100); 443 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 444 assert!(!stack.done()); 445 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 446 assert!(stack.done()); 447 } 448 } 449 ); 450 451 let (handler, counter) = D::new_and_counter(); 452 store.set_debug_handler(handler); 453 454 let instance = Instance::new_async(&mut store, &module, &[]).await?; 455 let func = instance.get_func(&mut store, "main").unwrap(); 456 let mut results = []; 457 func.call_async(&mut store, &[], &mut results).await?; 458 assert_eq!(counter.load(Ordering::Relaxed), 1); 459 460 Ok(()) 461 } 462 463 #[tokio::test] 464 #[cfg_attr(miri, ignore)] 465 async fn hostcall_trap_events() -> wasmtime::Result<()> { 466 let _ = env_logger::try_init(); 467 468 let (module, mut store) = get_module_and_store( 469 |config| { 470 config.async_support(true); 471 config.wasm_exceptions(true); 472 }, 473 r#" 474 (module 475 (func (export "main") 476 i32.const 0 477 i32.const 0 478 i32.div_u 479 drop)) 480 "#, 481 )?; 482 483 debug_event_checker!( 484 D, store, 485 { 0 ; 486 wasmtime::DebugEvent::Trap(wasmtime_environ::Trap::IntegerDivisionByZero) => {} 487 } 488 ); 489 490 let (handler, counter) = D::new_and_counter(); 491 store.set_debug_handler(handler); 492 493 let instance = Instance::new_async(&mut store, &module, &[]).await?; 494 let func = instance.get_func(&mut store, "main").unwrap(); 495 let mut results = []; 496 let result = func.call_async(&mut store, &[], &mut results).await; 497 assert!(result.is_err()); // Uncaught trap. 498 assert_eq!(counter.load(Ordering::Relaxed), 1); 499 500 Ok(()) 501 } 502 503 #[tokio::test] 504 #[cfg_attr(miri, ignore)] 505 async fn hostcall_error_events() -> wasmtime::Result<()> { 506 let _ = env_logger::try_init(); 507 508 let (module, mut store) = get_module_and_store( 509 |config| { 510 config.async_support(true); 511 config.wasm_exceptions(true); 512 }, 513 r#" 514 (module 515 (import "" "do_a_trap" (func)) 516 (func (export "main") 517 call 0)) 518 "#, 519 )?; 520 521 debug_event_checker!( 522 D, store, 523 { 0 ; 524 wasmtime::DebugEvent::HostcallError(e) => { 525 assert!(format!("{e:?}").contains("secret error message")); 526 } 527 } 528 ); 529 530 let (handler, counter) = D::new_and_counter(); 531 store.set_debug_handler(handler); 532 533 let do_a_trap = Func::wrap( 534 &mut store, 535 |_caller: Caller<'_, ()>| -> wasmtime::Result<()> { 536 Err(wasmtime::format_err!("secret error message")) 537 }, 538 ); 539 let instance = Instance::new_async(&mut store, &module, &[Extern::Func(do_a_trap)]).await?; 540 let func = instance.get_func(&mut store, "main").unwrap(); 541 let mut results = []; 542 let result = func.call_async(&mut store, &[], &mut results).await; 543 assert!(result.is_err()); // Uncaught trap. 544 assert_eq!(counter.load(Ordering::Relaxed), 1); 545 Ok(()) 546 } 547 548 #[tokio::test] 549 #[cfg_attr(miri, ignore)] 550 async fn breakpoint_events() -> wasmtime::Result<()> { 551 let _ = env_logger::try_init(); 552 553 let (module, mut store) = get_module_and_store( 554 |config| { 555 config.async_support(true); 556 config.wasm_exceptions(true); 557 }, 558 r#" 559 (module 560 (func (export "main") (param i32 i32) (result i32) 561 local.get 0 562 local.get 1 563 i32.add)) 564 "#, 565 )?; 566 567 debug_event_checker!( 568 D, store, 569 { 0 ; 570 wasmtime::DebugEvent::Breakpoint => { 571 let mut stack = store.debug_frames().expect("frame cursor must be available"); 572 assert!(!stack.done()); 573 assert_eq!(stack.num_locals(), 2); 574 assert_eq!(stack.local(0).unwrap_i32(), 1); 575 assert_eq!(stack.local(1).unwrap_i32(), 2); 576 let (func, pc) = stack.wasm_function_index_and_pc().unwrap(); 577 assert_eq!(func.as_u32(), 0); 578 assert_eq!(pc, 0x28); 579 assert_eq!(stack.move_to_parent(), FrameParentResult::SameActivation); 580 assert!(stack.done()); 581 } 582 } 583 ); 584 585 let (handler, counter) = D::new_and_counter(); 586 store.set_debug_handler(handler); 587 store 588 .edit_breakpoints() 589 .unwrap() 590 .add_breakpoint(&module, 0x28)?; 591 592 let instance = Instance::new_async(&mut store, &module, &[]).await?; 593 let func = instance.get_func(&mut store, "main").unwrap(); 594 let mut results = [Val::I32(0)]; 595 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 596 .await?; 597 assert_eq!(counter.load(Ordering::Relaxed), 1); 598 assert_eq!(results[0].unwrap_i32(), 3); 599 600 let breakpoints = store.breakpoints().unwrap().collect::<Vec<_>>(); 601 assert_eq!(breakpoints.len(), 1); 602 assert!(Module::same(&breakpoints[0].module, &module)); 603 assert_eq!(breakpoints[0].pc, 0x28); 604 605 store 606 .edit_breakpoints() 607 .unwrap() 608 .remove_breakpoint(&module, 0x28)?; 609 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 610 .await?; 611 assert_eq!(counter.load(Ordering::Relaxed), 1); // Should not have incremented from above. 612 assert_eq!(results[0].unwrap_i32(), 3); 613 614 // Enable single-step mode (on top of the breakpoint already enabled). 615 assert!(!store.is_single_step()); 616 store.edit_breakpoints().unwrap().single_step(true).unwrap(); 617 assert!(store.is_single_step()); 618 619 debug_event_checker!( 620 D2, store, 621 { 0 ; 622 wasmtime::DebugEvent::Breakpoint => { 623 let stack = store.debug_frames().unwrap(); 624 assert!(!stack.done()); 625 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 626 assert_eq!(pc, 0x24); 627 } 628 }, 629 { 630 1 ; 631 wasmtime::DebugEvent::Breakpoint => { 632 let stack = store.debug_frames().unwrap(); 633 assert!(!stack.done()); 634 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 635 assert_eq!(pc, 0x26); 636 } 637 }, 638 { 639 2 ; 640 wasmtime::DebugEvent::Breakpoint => { 641 let stack = store.debug_frames().unwrap(); 642 assert!(!stack.done()); 643 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 644 assert_eq!(pc, 0x28); 645 } 646 }, 647 { 648 3 ; 649 wasmtime::DebugEvent::Breakpoint => { 650 let stack = store.debug_frames().unwrap(); 651 assert!(!stack.done()); 652 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 653 assert_eq!(pc, 0x29); 654 } 655 } 656 ); 657 658 let (handler, counter) = D2::new_and_counter(); 659 store.set_debug_handler(handler); 660 661 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 662 .await?; 663 assert_eq!(counter.load(Ordering::Relaxed), 4); 664 665 // Re-enable individual breakpoint. 666 store 667 .edit_breakpoints() 668 .unwrap() 669 .add_breakpoint(&module, 0x28) 670 .unwrap(); 671 672 // Now disable single-stepping. The single breakpoint set above 673 // should still remain. 674 store 675 .edit_breakpoints() 676 .unwrap() 677 .single_step(false) 678 .unwrap(); 679 680 let (handler, counter) = D::new_and_counter(); 681 store.set_debug_handler(handler); 682 683 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 684 .await?; 685 assert_eq!(counter.load(Ordering::Relaxed), 1); 686 687 Ok(()) 688 } 689 690 #[tokio::test] 691 #[cfg_attr(miri, ignore)] 692 async fn breakpoints_in_inlined_code() -> wasmtime::Result<()> { 693 let _ = env_logger::try_init(); 694 695 let (module, mut store) = get_module_and_store( 696 |config| { 697 config.async_support(true); 698 config.wasm_exceptions(true); 699 config.compiler_inlining(true); 700 unsafe { 701 config.cranelift_flag_set("wasmtime_inlining_intra_module", "true"); 702 } 703 }, 704 r#" 705 (module 706 (func $f (export "f") (param i32 i32) (result i32) 707 local.get 0 708 local.get 1 709 i32.add) 710 711 (func (export "main") (param i32 i32) (result i32) 712 local.get 0 713 local.get 1 714 call $f)) 715 "#, 716 )?; 717 718 debug_event_checker!( 719 D, store, 720 { 0 ; 721 wasmtime::DebugEvent::Breakpoint => {} 722 }, 723 { 1 ; 724 wasmtime::DebugEvent::Breakpoint => {} 725 } 726 ); 727 728 let (handler, counter) = D::new_and_counter(); 729 store.set_debug_handler(handler); 730 store 731 .edit_breakpoints() 732 .unwrap() 733 .add_breakpoint(&module, 0x2d)?; // `i32.add` in `$f`. 734 735 let instance = Instance::new_async(&mut store, &module, &[]).await?; 736 let func_main = instance.get_func(&mut store, "main").unwrap(); 737 let func_f = instance.get_func(&mut store, "f").unwrap(); 738 let mut results = [Val::I32(0)]; 739 // Breakpoint in `$f` should have been hit in `main` even if it 740 // was inlined. 741 func_main 742 .call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 743 .await?; 744 assert_eq!(counter.load(Ordering::Relaxed), 1); 745 assert_eq!(results[0].unwrap_i32(), 3); 746 747 // Breakpoint in `$f` should be hit when called directly, too. 748 func_f 749 .call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 750 .await?; 751 assert_eq!(counter.load(Ordering::Relaxed), 2); 752 assert_eq!(results[0].unwrap_i32(), 3); 753 754 Ok(()) 755 } 756 757 #[tokio::test] 758 #[cfg_attr(miri, ignore)] 759 async fn epoch_events() -> wasmtime::Result<()> { 760 let _ = env_logger::try_init(); 761 762 let (module, mut store) = get_module_and_store( 763 |config| { 764 config.async_support(true); 765 config.epoch_interruption(true); 766 }, 767 r#" 768 (module 769 (func $f (export "f") (param i32 i32) (result i32) 770 local.get 0 771 local.get 1 772 i32.add)) 773 "#, 774 )?; 775 776 debug_event_checker!( 777 D, store, 778 { 0 ; 779 wasmtime::DebugEvent::EpochYield => {} 780 } 781 ); 782 783 let (handler, counter) = D::new_and_counter(); 784 store.set_debug_handler(handler); 785 786 store.set_epoch_deadline(1); 787 store.epoch_deadline_async_yield_and_update(1); 788 store.engine().increment_epoch(); 789 790 let instance = Instance::new_async(&mut store, &module, &[]).await?; 791 let func_f = instance.get_func(&mut store, "f").unwrap(); 792 let mut results = [Val::I32(0)]; 793 func_f 794 .call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 795 .await?; 796 assert_eq!(counter.load(Ordering::Relaxed), 1); 797 assert_eq!(results[0].unwrap_i32(), 3); 798 799 Ok(()) 800 } 801