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, Func, Instance, Module, 7 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 ) -> anyhow::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 ) -> anyhow::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() -> anyhow::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 stack.move_to_parent(); 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 stack.move_to_parent(); 98 assert!(stack.done()); 99 }, 100 )?; 101 } 102 Ok(()) 103 } 104 105 #[test] 106 #[cfg_attr(miri, ignore)] 107 fn stack_values_exceptions() -> anyhow::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 stack.move_to_parent(); 128 assert!(stack.done()); 129 }, 130 ) 131 } 132 133 #[test] 134 #[cfg_attr(miri, ignore)] 135 fn stack_values_dead_gc_ref() -> anyhow::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 stack.move_to_parent(); 155 assert!(stack.done()); 156 }, 157 ) 158 } 159 160 #[test] 161 #[cfg_attr(miri, ignore)] 162 fn gc_access_during_call() -> anyhow::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 stack.move_to_parent(); 198 assert!(stack.done()); 199 }, 200 ) 201 } 202 203 #[test] 204 #[cfg_attr(miri, ignore)] 205 fn debug_frames_on_store_with_no_wasm_activation() -> anyhow::Result<()> { 206 let mut config = Config::default(); 207 config.guest_debug(true); 208 let engine = Engine::new(&config)?; 209 let mut store = Store::new(&engine, ()); 210 let frames = store 211 .debug_frames() 212 .expect("Debug frames should be available"); 213 assert!(frames.done()); 214 Ok(()) 215 } 216 217 macro_rules! debug_event_checker { 218 ($ty:tt, 219 $store:tt, 220 $( 221 { $i:expr ; $pat:pat => $body:tt } 222 ),*) 223 => 224 { 225 #[derive(Clone)] 226 struct $ty(Arc<AtomicUsize>); 227 impl $ty { 228 fn new_and_counter() -> (Self, Arc<AtomicUsize>) { 229 let counter = Arc::new(AtomicUsize::new(0)); 230 let counter_clone = counter.clone(); 231 ($ty(counter), counter_clone) 232 } 233 } 234 impl DebugHandler for $ty { 235 type Data = (); 236 fn handle( 237 &self, 238 #[allow(unused_variables, reason = "macro rules")] 239 #[allow(unused_mut, reason = "macro rules")] 240 mut $store: StoreContextMut<'_, ()>, 241 event: DebugEvent<'_>, 242 ) -> impl Future<Output = ()> + Send { 243 let step = self.0.fetch_add(1, Ordering::Relaxed); 244 async move { 245 if false {} 246 $( 247 else if step == $i { 248 match event { 249 $pat => { 250 $body; 251 } 252 _ => panic!("Incorrect event"), 253 } 254 } 255 )* 256 else { 257 panic!("Too many steps"); 258 } 259 } 260 } 261 } 262 } 263 } 264 265 #[tokio::test] 266 #[cfg_attr(miri, ignore)] 267 async fn uncaught_exception_events() -> anyhow::Result<()> { 268 let _ = env_logger::try_init(); 269 270 let (module, mut store) = get_module_and_store( 271 |config| { 272 config.async_support(true); 273 config.wasm_exceptions(true); 274 }, 275 r#" 276 (module 277 (tag $t (param i32)) 278 (func (export "main") 279 call 1) 280 (func 281 (local $i i32) 282 (local.set $i (i32.const 100)) 283 (throw $t (i32.const 42)))) 284 "#, 285 )?; 286 287 debug_event_checker!( 288 D, store, 289 { 0 ; 290 wasmtime::DebugEvent::UncaughtExceptionThrown(e) => { 291 assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42); 292 let mut stack = store.debug_frames().expect("frame cursor must be available"); 293 assert!(!stack.done()); 294 assert_eq!(stack.num_locals(), 1); 295 assert_eq!(stack.local(0).unwrap_i32(), 100); 296 stack.move_to_parent(); 297 assert!(!stack.done()); 298 stack.move_to_parent(); 299 assert!(stack.done()); 300 } 301 } 302 ); 303 304 let (handler, counter) = D::new_and_counter(); 305 store.set_debug_handler(handler); 306 307 let instance = Instance::new_async(&mut store, &module, &[]).await?; 308 let func = instance.get_func(&mut store, "main").unwrap(); 309 let mut results = []; 310 let result = func.call_async(&mut store, &[], &mut results).await; 311 assert!(result.is_err()); // Uncaught exception. 312 assert_eq!(counter.load(Ordering::Relaxed), 1); 313 314 Ok(()) 315 } 316 317 #[tokio::test] 318 #[cfg_attr(miri, ignore)] 319 async fn caught_exception_events() -> anyhow::Result<()> { 320 let _ = env_logger::try_init(); 321 322 let (module, mut store) = get_module_and_store( 323 |config| { 324 config.async_support(true); 325 config.wasm_exceptions(true); 326 }, 327 r#" 328 (module 329 (tag $t (param i32)) 330 (func (export "main") 331 (block $b (result i32) 332 (try_table (catch $t $b) 333 call 1) 334 i32.const 0) 335 drop) 336 (func 337 (local $i i32) 338 (local.set $i (i32.const 100)) 339 (throw $t (i32.const 42)))) 340 "#, 341 )?; 342 343 debug_event_checker!( 344 D, store, 345 { 0 ; 346 wasmtime::DebugEvent::CaughtExceptionThrown(e) => { 347 assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42); 348 let mut stack = store.debug_frames().expect("frame cursor must be available"); 349 assert!(!stack.done()); 350 assert_eq!(stack.num_locals(), 1); 351 assert_eq!(stack.local(0).unwrap_i32(), 100); 352 stack.move_to_parent(); 353 assert!(!stack.done()); 354 stack.move_to_parent(); 355 assert!(stack.done()); 356 } 357 } 358 ); 359 360 let (handler, counter) = D::new_and_counter(); 361 store.set_debug_handler(handler); 362 363 let instance = Instance::new_async(&mut store, &module, &[]).await?; 364 let func = instance.get_func(&mut store, "main").unwrap(); 365 let mut results = []; 366 func.call_async(&mut store, &[], &mut results).await?; 367 assert_eq!(counter.load(Ordering::Relaxed), 1); 368 369 Ok(()) 370 } 371 372 #[tokio::test] 373 #[cfg_attr(miri, ignore)] 374 async fn hostcall_trap_events() -> anyhow::Result<()> { 375 let _ = env_logger::try_init(); 376 377 let (module, mut store) = get_module_and_store( 378 |config| { 379 config.async_support(true); 380 config.wasm_exceptions(true); 381 }, 382 r#" 383 (module 384 (func (export "main") 385 i32.const 0 386 i32.const 0 387 i32.div_u 388 drop)) 389 "#, 390 )?; 391 392 debug_event_checker!( 393 D, store, 394 { 0 ; 395 wasmtime::DebugEvent::Trap(wasmtime_environ::Trap::IntegerDivisionByZero) => {} 396 } 397 ); 398 399 let (handler, counter) = D::new_and_counter(); 400 store.set_debug_handler(handler); 401 402 let instance = Instance::new_async(&mut store, &module, &[]).await?; 403 let func = instance.get_func(&mut store, "main").unwrap(); 404 let mut results = []; 405 let result = func.call_async(&mut store, &[], &mut results).await; 406 assert!(result.is_err()); // Uncaught trap. 407 assert_eq!(counter.load(Ordering::Relaxed), 1); 408 409 Ok(()) 410 } 411 412 #[tokio::test] 413 #[cfg_attr(miri, ignore)] 414 async fn hostcall_error_events() -> anyhow::Result<()> { 415 let _ = env_logger::try_init(); 416 417 let (module, mut store) = get_module_and_store( 418 |config| { 419 config.async_support(true); 420 config.wasm_exceptions(true); 421 }, 422 r#" 423 (module 424 (import "" "do_a_trap" (func)) 425 (func (export "main") 426 call 0)) 427 "#, 428 )?; 429 430 debug_event_checker!( 431 D, store, 432 { 0 ; 433 wasmtime::DebugEvent::HostcallError(e) => { 434 assert!(format!("{e:?}").contains("secret error message")); 435 } 436 } 437 ); 438 439 let (handler, counter) = D::new_and_counter(); 440 store.set_debug_handler(handler); 441 442 let do_a_trap = Func::wrap( 443 &mut store, 444 |_caller: Caller<'_, ()>| -> anyhow::Result<()> { 445 Err(anyhow::anyhow!("secret error message")) 446 }, 447 ); 448 let instance = Instance::new_async(&mut store, &module, &[Extern::Func(do_a_trap)]).await?; 449 let func = instance.get_func(&mut store, "main").unwrap(); 450 let mut results = []; 451 let result = func.call_async(&mut store, &[], &mut results).await; 452 assert!(result.is_err()); // Uncaught trap. 453 assert_eq!(counter.load(Ordering::Relaxed), 1); 454 Ok(()) 455 } 456 457 #[tokio::test] 458 #[cfg_attr(miri, ignore)] 459 async fn breakpoint_events() -> anyhow::Result<()> { 460 let _ = env_logger::try_init(); 461 462 let (module, mut store) = get_module_and_store( 463 |config| { 464 config.async_support(true); 465 config.wasm_exceptions(true); 466 }, 467 r#" 468 (module 469 (func (export "main") (param i32 i32) (result i32) 470 local.get 0 471 local.get 1 472 i32.add)) 473 "#, 474 )?; 475 476 debug_event_checker!( 477 D, store, 478 { 0 ; 479 wasmtime::DebugEvent::Breakpoint => { 480 let mut stack = store.debug_frames().expect("frame cursor must be available"); 481 assert!(!stack.done()); 482 assert_eq!(stack.num_locals(), 2); 483 assert_eq!(stack.local(0).unwrap_i32(), 1); 484 assert_eq!(stack.local(1).unwrap_i32(), 2); 485 let (func, pc) = stack.wasm_function_index_and_pc().unwrap(); 486 assert_eq!(func.as_u32(), 0); 487 assert_eq!(pc, 0x28); 488 stack.move_to_parent(); 489 assert!(stack.done()); 490 } 491 } 492 ); 493 494 let (handler, counter) = D::new_and_counter(); 495 store.set_debug_handler(handler); 496 store 497 .edit_breakpoints() 498 .unwrap() 499 .add_breakpoint(&module, 0x28)?; 500 501 let instance = Instance::new_async(&mut store, &module, &[]).await?; 502 let func = instance.get_func(&mut store, "main").unwrap(); 503 let mut results = [Val::I32(0)]; 504 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 505 .await?; 506 assert_eq!(counter.load(Ordering::Relaxed), 1); 507 assert_eq!(results[0].unwrap_i32(), 3); 508 509 let breakpoints = store.breakpoints().unwrap().collect::<Vec<_>>(); 510 assert_eq!(breakpoints.len(), 1); 511 assert!(Module::same(&breakpoints[0].module, &module)); 512 assert_eq!(breakpoints[0].pc, 0x28); 513 514 store 515 .edit_breakpoints() 516 .unwrap() 517 .remove_breakpoint(&module, 0x28)?; 518 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 519 .await?; 520 assert_eq!(counter.load(Ordering::Relaxed), 1); // Should not have incremented from above. 521 assert_eq!(results[0].unwrap_i32(), 3); 522 523 // Enable single-step mode (on top of the breakpoint already enabled). 524 assert!(!store.is_single_step()); 525 store.edit_breakpoints().unwrap().single_step(true).unwrap(); 526 assert!(store.is_single_step()); 527 528 debug_event_checker!( 529 D2, store, 530 { 0 ; 531 wasmtime::DebugEvent::Breakpoint => { 532 let stack = store.debug_frames().unwrap(); 533 assert!(!stack.done()); 534 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 535 assert_eq!(pc, 0x24); 536 } 537 }, 538 { 539 1 ; 540 wasmtime::DebugEvent::Breakpoint => { 541 let stack = store.debug_frames().unwrap(); 542 assert!(!stack.done()); 543 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 544 assert_eq!(pc, 0x26); 545 } 546 }, 547 { 548 2 ; 549 wasmtime::DebugEvent::Breakpoint => { 550 let stack = store.debug_frames().unwrap(); 551 assert!(!stack.done()); 552 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 553 assert_eq!(pc, 0x28); 554 } 555 }, 556 { 557 3 ; 558 wasmtime::DebugEvent::Breakpoint => { 559 let stack = store.debug_frames().unwrap(); 560 assert!(!stack.done()); 561 let (_, pc) = stack.wasm_function_index_and_pc().unwrap(); 562 assert_eq!(pc, 0x29); 563 } 564 } 565 ); 566 567 let (handler, counter) = D2::new_and_counter(); 568 store.set_debug_handler(handler); 569 570 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 571 .await?; 572 assert_eq!(counter.load(Ordering::Relaxed), 4); 573 574 // Re-enable individual breakpoint. 575 store 576 .edit_breakpoints() 577 .unwrap() 578 .add_breakpoint(&module, 0x28) 579 .unwrap(); 580 581 // Now disable single-stepping. The single breakpoint set above 582 // should still remain. 583 store 584 .edit_breakpoints() 585 .unwrap() 586 .single_step(false) 587 .unwrap(); 588 589 let (handler, counter) = D::new_and_counter(); 590 store.set_debug_handler(handler); 591 592 func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 593 .await?; 594 assert_eq!(counter.load(Ordering::Relaxed), 1); 595 596 Ok(()) 597 } 598 599 #[tokio::test] 600 #[cfg_attr(miri, ignore)] 601 async fn breakpoints_in_inlined_code() -> anyhow::Result<()> { 602 let _ = env_logger::try_init(); 603 604 let (module, mut store) = get_module_and_store( 605 |config| { 606 config.async_support(true); 607 config.wasm_exceptions(true); 608 config.compiler_inlining(true); 609 unsafe { 610 config.cranelift_flag_set("wasmtime_inlining_intra_module", "true"); 611 } 612 }, 613 r#" 614 (module 615 (func $f (export "f") (param i32 i32) (result i32) 616 local.get 0 617 local.get 1 618 i32.add) 619 620 (func (export "main") (param i32 i32) (result i32) 621 local.get 0 622 local.get 1 623 call $f)) 624 "#, 625 )?; 626 627 debug_event_checker!( 628 D, store, 629 { 0 ; 630 wasmtime::DebugEvent::Breakpoint => {} 631 }, 632 { 1 ; 633 wasmtime::DebugEvent::Breakpoint => {} 634 } 635 ); 636 637 let (handler, counter) = D::new_and_counter(); 638 store.set_debug_handler(handler); 639 store 640 .edit_breakpoints() 641 .unwrap() 642 .add_breakpoint(&module, 0x2d)?; // `i32.add` in `$f`. 643 644 let instance = Instance::new_async(&mut store, &module, &[]).await?; 645 let func_main = instance.get_func(&mut store, "main").unwrap(); 646 let func_f = instance.get_func(&mut store, "f").unwrap(); 647 let mut results = [Val::I32(0)]; 648 // Breakpoint in `$f` should have been hit in `main` even if it 649 // was inlined. 650 func_main 651 .call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 652 .await?; 653 assert_eq!(counter.load(Ordering::Relaxed), 1); 654 assert_eq!(results[0].unwrap_i32(), 3); 655 656 // Breakpoint in `$f` should be hit when called directly, too. 657 func_f 658 .call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) 659 .await?; 660 assert_eq!(counter.load(Ordering::Relaxed), 2); 661 assert_eq!(results[0].unwrap_i32(), 3); 662 663 Ok(()) 664 } 665