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