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, 8 }; 9 10 fn get_module_and_store<C: Fn(&mut Config)>( 11 c: C, 12 wat: &str, 13 ) -> anyhow::Result<(Module, Store<()>)> { 14 let mut config = Config::default(); 15 config.guest_debug(true); 16 config.wasm_exceptions(true); 17 c(&mut config); 18 let engine = Engine::new(&config)?; 19 let module = Module::new(&engine, wat)?; 20 Ok((module, Store::new(&engine, ()))) 21 } 22 23 fn test_stack_values<C: Fn(&mut Config), F: Fn(Caller<'_, ()>) + Send + Sync + 'static>( 24 wat: &str, 25 c: C, 26 f: F, 27 ) -> anyhow::Result<()> { 28 let (module, mut store) = get_module_and_store(c, wat)?; 29 let func = Func::wrap(&mut store, move |caller: Caller<'_, ()>| { 30 f(caller); 31 }); 32 let instance = Instance::new(&mut store, &module, &[Extern::Func(func)])?; 33 let mut results = []; 34 instance 35 .get_func(&mut store, "main") 36 .unwrap() 37 .call(&mut store, &[], &mut results)?; 38 39 Ok(()) 40 } 41 42 #[test] 43 #[cfg_attr(miri, ignore)] 44 fn stack_values_two_frames() -> anyhow::Result<()> { 45 let _ = env_logger::try_init(); 46 47 for inlining in [false, true] { 48 test_stack_values( 49 r#" 50 (module 51 (import "" "host" (func)) 52 (func (export "main") 53 i32.const 1 54 i32.const 2 55 call 2 56 drop) 57 (func (param i32 i32) (result i32) 58 local.get 0 59 local.get 1 60 call 0 61 i32.add)) 62 "#, 63 |config| { 64 config.compiler_inlining(inlining); 65 if inlining { 66 unsafe { 67 config.cranelift_flag_set("wasmtime_inlining_intra_module", "true"); 68 } 69 } 70 }, 71 |mut caller: Caller<'_, ()>| { 72 let mut stack = caller.debug_frames().unwrap(); 73 assert!(!stack.done()); 74 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 1); 75 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 65); 76 77 assert_eq!(stack.num_locals(), 2); 78 assert_eq!(stack.num_stacks(), 2); 79 assert_eq!(stack.local(0).unwrap_i32(), 1); 80 assert_eq!(stack.local(1).unwrap_i32(), 2); 81 assert_eq!(stack.stack(0).unwrap_i32(), 1); 82 assert_eq!(stack.stack(1).unwrap_i32(), 2); 83 84 stack.move_to_parent(); 85 assert!(!stack.done()); 86 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 0); 87 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 55); 88 89 stack.move_to_parent(); 90 assert!(stack.done()); 91 }, 92 )?; 93 } 94 Ok(()) 95 } 96 97 #[test] 98 #[cfg_attr(miri, ignore)] 99 fn stack_values_exceptions() -> anyhow::Result<()> { 100 test_stack_values( 101 r#" 102 (module 103 (tag $t (param i32)) 104 (import "" "host" (func)) 105 (func (export "main") 106 (block $b (result i32) 107 (try_table (catch $t $b) 108 (throw $t (i32.const 42))) 109 i32.const 0) 110 (call 0) 111 (drop))) 112 "#, 113 |_config| {}, 114 |mut caller: Caller<'_, ()>| { 115 let mut stack = caller.debug_frames().unwrap(); 116 assert!(!stack.done()); 117 assert_eq!(stack.num_stacks(), 1); 118 assert_eq!(stack.stack(0).unwrap_i32(), 42); 119 stack.move_to_parent(); 120 assert!(stack.done()); 121 }, 122 ) 123 } 124 125 #[test] 126 #[cfg_attr(miri, ignore)] 127 fn stack_values_dead_gc_ref() -> anyhow::Result<()> { 128 test_stack_values( 129 r#" 130 (module 131 (type $s (struct)) 132 (import "" "host" (func)) 133 (func (export "main") 134 (struct.new $s) 135 (call 0) 136 (drop))) 137 "#, 138 |config| { 139 config.wasm_gc(true); 140 }, 141 |mut caller: Caller<'_, ()>| { 142 let mut stack = caller.debug_frames().unwrap(); 143 assert!(!stack.done()); 144 assert_eq!(stack.num_stacks(), 1); 145 assert!(stack.stack(0).unwrap_anyref().is_some()); 146 stack.move_to_parent(); 147 assert!(stack.done()); 148 }, 149 ) 150 } 151 152 #[test] 153 #[cfg_attr(miri, ignore)] 154 fn gc_access_during_call() -> anyhow::Result<()> { 155 test_stack_values( 156 r#" 157 (module 158 (type $s (struct (field i32))) 159 (import "" "host" (func)) 160 (func (export "main") 161 (local $l (ref null $s)) 162 (local.set $l (struct.new $s (i32.const 42))) 163 (call 0))) 164 "#, 165 |config| { 166 config.wasm_gc(true); 167 }, 168 |mut caller: Caller<'_, ()>| { 169 let mut stack = caller.debug_frames().unwrap(); 170 171 // Do a GC while we hold the stack cursor. 172 stack.as_context_mut().gc(None); 173 174 assert!(!stack.done()); 175 assert_eq!(stack.num_stacks(), 0); 176 assert_eq!(stack.num_locals(), 1); 177 // Note that this struct is dead during the call, and the 178 // ref could otherwise be optimized away (no longer in the 179 // stackmap at this point); but we verify it is still 180 // alive here because it is rooted in the 181 // debug-instrumentation slot. 182 let s = stack 183 .local(0) 184 .unwrap_any_ref() 185 .unwrap() 186 .unwrap_struct(&stack) 187 .unwrap(); 188 assert_eq!(s.field(&mut stack, 0).unwrap().unwrap_i32(), 42); 189 stack.move_to_parent(); 190 assert!(stack.done()); 191 }, 192 ) 193 } 194 195 #[test] 196 #[cfg_attr(miri, ignore)] 197 fn debug_frames_on_store_with_no_wasm_activation() -> anyhow::Result<()> { 198 let mut config = Config::default(); 199 config.guest_debug(true); 200 let engine = Engine::new(&config)?; 201 let mut store = Store::new(&engine, ()); 202 let frames = store 203 .debug_frames() 204 .expect("Debug frames should be available"); 205 assert!(frames.done()); 206 Ok(()) 207 } 208 209 macro_rules! debug_event_checker { 210 ($ty:tt, 211 $store:tt, 212 $( 213 { $i:expr ; $pat:pat => $body:tt } 214 ),*) 215 => 216 { 217 #[derive(Clone)] 218 struct $ty(Arc<AtomicUsize>); 219 impl $ty { 220 fn new_and_counter() -> (Self, Arc<AtomicUsize>) { 221 let counter = Arc::new(AtomicUsize::new(0)); 222 let counter_clone = counter.clone(); 223 ($ty(counter), counter_clone) 224 } 225 } 226 impl DebugHandler for $ty { 227 type Data = (); 228 fn handle( 229 &self, 230 #[allow(unused_variables, reason = "macro rules")] 231 #[allow(unused_mut, reason = "macro rules")] 232 mut $store: StoreContextMut<'_, ()>, 233 event: DebugEvent<'_>, 234 ) -> impl Future<Output = ()> + Send { 235 let step = self.0.fetch_add(1, Ordering::Relaxed); 236 async move { 237 if false {} 238 $( 239 else if step == $i { 240 match event { 241 $pat => { 242 $body; 243 } 244 _ => panic!("Incorrect event"), 245 } 246 } 247 )* 248 else { 249 panic!("Too many steps"); 250 } 251 } 252 } 253 } 254 } 255 } 256 257 #[tokio::test] 258 #[cfg_attr(miri, ignore)] 259 async fn uncaught_exception_events() -> anyhow::Result<()> { 260 let _ = env_logger::try_init(); 261 262 let (module, mut store) = get_module_and_store( 263 |config| { 264 config.async_support(true); 265 config.wasm_exceptions(true); 266 }, 267 r#" 268 (module 269 (tag $t (param i32)) 270 (func (export "main") 271 call 1) 272 (func 273 (local $i i32) 274 (local.set $i (i32.const 100)) 275 (throw $t (i32.const 42)))) 276 "#, 277 )?; 278 279 debug_event_checker!( 280 D, store, 281 { 0 ; 282 wasmtime::DebugEvent::UncaughtExceptionThrown(e) => { 283 assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42); 284 let mut stack = store.debug_frames().expect("frame cursor must be available"); 285 assert!(!stack.done()); 286 assert_eq!(stack.num_locals(), 1); 287 assert_eq!(stack.local(0).unwrap_i32(), 100); 288 stack.move_to_parent(); 289 assert!(!stack.done()); 290 stack.move_to_parent(); 291 assert!(stack.done()); 292 } 293 } 294 ); 295 296 let (handler, counter) = D::new_and_counter(); 297 store.set_debug_handler(handler); 298 299 let instance = Instance::new_async(&mut store, &module, &[]).await?; 300 let func = instance.get_func(&mut store, "main").unwrap(); 301 let mut results = []; 302 let result = func.call_async(&mut store, &[], &mut results).await; 303 assert!(result.is_err()); // Uncaught exception. 304 assert_eq!(counter.load(Ordering::Relaxed), 1); 305 306 Ok(()) 307 } 308 309 #[tokio::test] 310 #[cfg_attr(miri, ignore)] 311 async fn caught_exception_events() -> anyhow::Result<()> { 312 let _ = env_logger::try_init(); 313 314 let (module, mut store) = get_module_and_store( 315 |config| { 316 config.async_support(true); 317 config.wasm_exceptions(true); 318 }, 319 r#" 320 (module 321 (tag $t (param i32)) 322 (func (export "main") 323 (block $b (result i32) 324 (try_table (catch $t $b) 325 call 1) 326 i32.const 0) 327 drop) 328 (func 329 (local $i i32) 330 (local.set $i (i32.const 100)) 331 (throw $t (i32.const 42)))) 332 "#, 333 )?; 334 335 debug_event_checker!( 336 D, store, 337 { 0 ; 338 wasmtime::DebugEvent::CaughtExceptionThrown(e) => { 339 assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42); 340 let mut stack = store.debug_frames().expect("frame cursor must be available"); 341 assert!(!stack.done()); 342 assert_eq!(stack.num_locals(), 1); 343 assert_eq!(stack.local(0).unwrap_i32(), 100); 344 stack.move_to_parent(); 345 assert!(!stack.done()); 346 stack.move_to_parent(); 347 assert!(stack.done()); 348 } 349 } 350 ); 351 352 let (handler, counter) = D::new_and_counter(); 353 store.set_debug_handler(handler); 354 355 let instance = Instance::new_async(&mut store, &module, &[]).await?; 356 let func = instance.get_func(&mut store, "main").unwrap(); 357 let mut results = []; 358 func.call_async(&mut store, &[], &mut results).await?; 359 assert_eq!(counter.load(Ordering::Relaxed), 1); 360 361 Ok(()) 362 } 363 364 #[tokio::test] 365 #[cfg_attr(miri, ignore)] 366 #[cfg(any( 367 target_arch = "x86_64", 368 target_arch = "aarch64", 369 target_arch = "s390x", 370 target_arch = "riscv64" 371 ))] 372 async fn hostcall_trap_events() -> anyhow::Result<()> { 373 let _ = env_logger::try_init(); 374 375 let (module, mut store) = get_module_and_store( 376 |config| { 377 config.async_support(true); 378 config.wasm_exceptions(true); 379 // Force hostcall-based traps. 380 config.signals_based_traps(false); 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