xref: /wasmtime-44.0.1/tests/all/debug.rs (revision bb0ebb73)
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