From 3ee3c2f0aa3a7874964e0682d0462d62864bf971 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Wed, 18 Mar 2026 18:11:41 -0700 Subject: [PATCH] Debugging: add debug-tags to instrumented trap sites so we actually get PCs on traps. This was not exposed earlier by (i) lack of handling of trap events in the initial version of the gdbstub component in #12771, and (ii) lack of asserting some value for the PC on the top frame in the debug-event test for traps. We got the PC for the last opcode in the function body previously because, with no debug tags on the trapping path that calls raise() (sunk to the bottom of the machine code body as cold code), we scanned backward for the last tag metadata and found that instead. Adding metadata according to the current source location when emitting traps fixes this for all trapping events. --- crates/cranelift/src/func_environ.rs | 4 ++++ crates/cranelift/src/trap.rs | 7 ++++++- tests/all/debug.rs | 13 +++++++++---- tests/disas/debug-exceptions.wat | 2 ++ tests/disas/debug.wat | 1 + 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 5eccf9732d58..854cd4eae429 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -1391,6 +1391,10 @@ impl TranslateTrap for FuncEnvironment<'_> { ) -> ir::FuncRef { self.builtin_functions.load_builtin(builder.func, index) } + + fn debug_tags(&self, srcloc: ir::SourceLoc) -> Vec { + FuncEnvironment::debug_tags(self, srcloc) + } } #[derive(Default)] diff --git a/crates/cranelift/src/trap.rs b/crates/cranelift/src/trap.rs index 811835c654c6..3b72f0282ab1 100644 --- a/crates/cranelift/src/trap.rs +++ b/crates/cranelift/src/trap.rs @@ -20,6 +20,9 @@ pub trait TranslateTrap { builder: &mut FunctionBuilder<'_>, index: BuiltinFunctionIndex, ) -> ir::FuncRef; + fn debug_tags(&self, _srcloc: ir::SourceLoc) -> Vec { + vec![] + } fn trap(&mut self, builder: &mut FunctionBuilder, trap: ir::TrapCode) { match ( @@ -36,12 +39,14 @@ pub trait TranslateTrap { // pass in our trap code. Leave a debug `unreachable` in place // afterwards as a defense-in-depth measure. (false, Some(trap)) => { + let debug_tags = self.debug_tags(builder.srcloc()); let trap_libcall = self.builtin_funcref(builder, BuiltinFunctionIndex::trap()); let vmctx = self.vmctx_val(&mut builder.cursor()); let trap_code = builder.ins().iconst(I8, i64::from(trap as u8)); builder.ins().call(trap_libcall, &[vmctx, trap_code]); let raise_libcall = self.builtin_funcref(builder, BuiltinFunctionIndex::raise()); - builder.ins().call(raise_libcall, &[vmctx]); + let inst = builder.ins().call(raise_libcall, &[vmctx]); + builder.func.debug_tags.set(inst, debug_tags); builder.ins().trap(TRAP_INTERNAL_ASSERT); } } diff --git a/tests/all/debug.rs b/tests/all/debug.rs index 4614313ec130..fd1086a9452c 100644 --- a/tests/all/debug.rs +++ b/tests/all/debug.rs @@ -694,18 +694,23 @@ async fn hostcall_trap_events() -> wasmtime::Result<()> { }, r#" (module - (func (export "main") + (func (export "main") (result i32) i32.const 0 i32.const 0 i32.div_u - drop)) + drop + i32.const 42)) "#, )?; debug_event_checker!( D, store, { 0 ; - wasmtime::DebugEvent::Trap(wasmtime_environ::Trap::IntegerDivisionByZero) => {} + wasmtime::DebugEvent::Trap(wasmtime_environ::Trap::IntegerDivisionByZero) => { + let frame = store.debug_exit_frames().next().unwrap(); + let (_func, pc) = frame.wasm_function_index_and_pc(&mut store).unwrap().unwrap(); + assert_eq!(pc, 0x26); + } } ); @@ -714,7 +719,7 @@ async fn hostcall_trap_events() -> wasmtime::Result<()> { let instance = Instance::new_async(&mut store, &module, &[]).await?; let func = instance.get_func(&mut store, "main").unwrap(); - let mut results = []; + let mut results = [Val::I32(0)]; let result = func.call_async(&mut store, &[], &mut results).await; assert!(result.is_err()); // Uncaught trap. assert_eq!(counter.load(Ordering::Relaxed), 1); diff --git a/tests/disas/debug-exceptions.wat b/tests/disas/debug-exceptions.wat index ad911f7302ae..440b63fdae59 100644 --- a/tests/disas/debug-exceptions.wat +++ b/tests/disas/debug-exceptions.wat @@ -89,6 +89,7 @@ ;; bl #0x3dc ;; ec: ldur x2, [sp, #0x10] ;; bl #0x414 +;; ╰─╼ debug frame state (after previous inst): func key DefinedWasmFunction(StaticModuleIndex(0), DefinedFuncIndex(0)), wasm PC 66, slot at FP-0xc0, locals , stack I32 @ slot+0x8 ;; f4: .byte 0x1f, 0xc1, 0x00, 0x00 ;; mov x2, x0 ;; mov w3, w2 @@ -141,6 +142,7 @@ ;; 19c: bl #0x3dc ;; 1a0: ldur x2, [sp, #0x10] ;; 1a4: bl #0x414 +;; ╰─╼ debug frame state (after previous inst): func key DefinedWasmFunction(StaticModuleIndex(0), DefinedFuncIndex(0)), wasm PC 52, slot at FP-0xc0, locals , stack ;; 1a8: .byte 0x1f, 0xc1, 0x00, 0x00 ;; 1ac: .byte 0x1f, 0xc1, 0x00, 0x00 ;; 1b0: .byte 0x1f, 0xc1, 0x00, 0x00 diff --git a/tests/disas/debug.wat b/tests/disas/debug.wat index 285b5d40e502..e40e07208cd9 100644 --- a/tests/disas/debug.wat +++ b/tests/disas/debug.wat @@ -48,6 +48,7 @@ ;; 67: callq 0x18c ;; 6c: movq %r12, %rdi ;; 6f: callq 0x1bd +;; ╰─╼ debug frame state (after previous inst): func key DefinedWasmFunction(StaticModuleIndex(0), DefinedFuncIndex(0)), wasm PC 35, slot at FP-0x30, locals I32 @ slot+0x8, I32 @ slot+0xc, stack ;; 74: ud2 ;; ;; wasm[0]::array_to_wasm_trampoline[0]: