Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/debug.rs
2450 views
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
666