Skip to content

Commit 9500c41

Browse files
authored
Several fixes to debugging infrastructure: component vs. module PCs and gdbstub wasm module names. (#12901)
* Debugging: fix module-relative vs component-relative PCs and unique library names. Two bugfixes for guest debugging with components: 1. Convert component-relative source locations to module-relative PCs in the frame table. The guest-debug API presents a core-Wasm view where components are deconstructed into individual modules, so all PCs must be module-relative. This adds a `wasm_module_offset` field to `ModuleTranslation` and `FuncEnvironment`, set during component translation, and subtracts it in `debug_tags()`. 2. Give unique names to "library" entries in the gdbstub XML response. LLDB's DynamicLoader deduplicates by name, so using "wasm" for all modules caused only the first to be loaded. * Debugging: add ModulePC and ComponentPC newtypes for Wasm PC offsets. Introduce `ModulePC` (module-relative) and `ComponentPC` (component-relative) newtype wrappers around u32 Wasm bytecode offsets. These replace raw u32 values throughout the frame table, breakpoint, and debug systems to prevent confusion between the two offset spaces. * Debugging: add regression test for component module-relative PCs.
1 parent 33e8b3d commit 9500c41

18 files changed

Lines changed: 374 additions & 137 deletions

File tree

crates/cranelift/src/compiled_function.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use cranelift_codegen::{
66
isa::unwind::CfaUnwindInfo, isa::unwind::UnwindInfo,
77
};
88
use wasmtime_environ::{
9-
FilePos, FrameStateSlotBuilder, InstructionAddressMap, PrimaryMap, TrapInformation,
9+
FilePos, FrameStateSlotBuilder, InstructionAddressMap, ModulePC, PrimaryMap, TrapInformation,
1010
};
1111

1212
#[derive(Debug, Clone, PartialEq, Eq, Default)]
@@ -67,8 +67,8 @@ pub struct CompiledFunction {
6767
metadata: CompiledFunctionMetadata,
6868
/// Debug metadata for the top-level function's state slot.
6969
pub debug_slot_descriptor: Option<FrameStateSlotBuilder>,
70-
/// Debug breakpoint patches: Wasm PC, offset range in buffer.
71-
pub breakpoint_patch_points: Vec<(u32, Range<u32>)>,
70+
/// Debug breakpoint patches: module-relative Wasm PC, offset range in buffer.
71+
pub breakpoint_patch_points: Vec<(ModulePC, Range<u32>)>,
7272
}
7373

7474
impl CompiledFunction {
@@ -120,15 +120,15 @@ impl CompiledFunction {
120120
// second-to-last tag will get the innermost Wasm PC (if
121121
// there are multiple nested frames due to inlining).
122122
assert!(tag.tags.len() >= 3);
123-
let ir::DebugTag::User(wasm_pc) = tag.tags[tag.tags.len() - 2] else {
123+
let ir::DebugTag::User(wasm_pc_raw) = tag.tags[tag.tags.len() - 2] else {
124124
panic!("invalid tag")
125125
};
126126

127127
let patchable_start = patchable_callsite.ret_addr - patchable_callsite.len;
128128
let patchable_end = patchable_callsite.ret_addr;
129129

130130
self.breakpoint_patch_points
131-
.push((wasm_pc, patchable_start..patchable_end));
131+
.push((ModulePC::new(wasm_pc_raw), patchable_start..patchable_end));
132132

133133
tags.next();
134134
patchable_callsites.next();
@@ -218,7 +218,7 @@ impl CompiledFunction {
218218
/// Returns an iterator over breakpoint patches for this function.
219219
///
220220
/// Each tuple is (wasm PC, buffer offset range).
221-
pub fn breakpoint_patches(&self) -> impl Iterator<Item = (u32, Range<u32>)> + '_ {
221+
pub fn breakpoint_patches(&self) -> impl Iterator<Item = (ModulePC, Range<u32>)> + '_ {
222222
self.breakpoint_patch_points.iter().cloned()
223223
}
224224
}

crates/cranelift/src/compiler.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use wasmtime_environ::{
3737
Abi, AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, CompiledFunctionBody,
3838
DefinedFuncIndex, FlagValue, FrameInstPos, FrameStackShape, FrameStateSlotBuilder,
3939
FrameTableBuilder, FuncKey, FunctionBodyData, FunctionLoc, HostCall, InliningCompiler,
40-
ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection, StaticModuleIndex,
40+
ModulePC, ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection, StaticModuleIndex,
4141
TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, WasmFuncType, WasmValType, prelude::*,
4242
};
4343
use wasmtime_unwinder::ExceptionTableBuilder;
@@ -520,7 +520,7 @@ impl wasmtime_environ::Compiler for Compiler {
520520
}
521521
}
522522

523-
let mut breakpoint_table = Vec::new();
523+
let mut breakpoint_table: Vec<(ModulePC, Range<u32>)> = Vec::new();
524524
let mut nop_units = None;
525525

526526
let mut ret = Vec::with_capacity(funcs.len());
@@ -1623,7 +1623,7 @@ fn clif_to_env_frame_tables<'a>(
16231623
for frame_tags in tag_site.tags.chunks_exact(3) {
16241624
let &[
16251625
ir::DebugTag::StackSlot(slot),
1626-
ir::DebugTag::User(wasm_pc),
1626+
ir::DebugTag::User(wasm_pc_raw),
16271627
ir::DebugTag::User(stack_shape),
16281628
] = frame_tags
16291629
else {
@@ -1645,7 +1645,7 @@ fn clif_to_env_frame_tables<'a>(
16451645
});
16461646

16471647
frames.push((
1648-
wasm_pc,
1648+
ModulePC::new(wasm_pc_raw),
16491649
frame_descriptor,
16501650
FrameStackShape::from_raw(stack_shape),
16511651
));
@@ -1669,8 +1669,8 @@ fn clif_to_env_frame_tables<'a>(
16691669
/// Wasmtime's serialized metadata.
16701670
fn clif_to_env_breakpoints(
16711671
range: Range<u64>,
1672-
breakpoint_patches: impl Iterator<Item = (u32, Range<u32>)>,
1673-
patch_table: &mut Vec<(u32, Range<u32>)>,
1672+
breakpoint_patches: impl Iterator<Item = (ModulePC, Range<u32>)>,
1673+
patch_table: &mut Vec<(ModulePC, Range<u32>)>,
16741674
) -> Result<()> {
16751675
patch_table.extend(breakpoint_patches.map(|(wasm_pc, offset_range)| {
16761676
let start = offset_range.start + u32::try_from(range.start).unwrap();

crates/cranelift/src/func_environ.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ use std::mem;
2626
use wasmparser::{FuncValidator, Operator, WasmFeatures, WasmModuleResources};
2727
use wasmtime_core::math::f64_cvt_to_int_bounds;
2828
use wasmtime_environ::{
29-
BuiltinFunctionIndex, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex,
30-
FrameStateSlotBuilder, FrameValType, FuncIndex, FuncKey, GlobalConstValue, GlobalIndex,
31-
IndexType, Memory, MemoryIndex, Module, ModuleInternedTypeIndex, ModuleTranslation,
32-
ModuleTypesBuilder, PtrSize, Table, TableIndex, TagIndex, Tunables, TypeConvert, TypeIndex,
33-
VMOffsets, WasmCompositeInnerType, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmRefType,
34-
WasmResult, WasmValType,
29+
BuiltinFunctionIndex, ComponentPC, DataIndex, DefinedFuncIndex, ElemIndex,
30+
EngineOrModuleTypeIndex, FrameStateSlotBuilder, FrameValType, FuncIndex, FuncKey,
31+
GlobalConstValue, GlobalIndex, IndexType, Memory, MemoryIndex, Module, ModuleInternedTypeIndex,
32+
ModuleTranslation, ModuleTypesBuilder, PtrSize, Table, TableIndex, TagIndex, Tunables,
33+
TypeConvert, TypeIndex, VMOffsets, WasmCompositeInnerType, WasmFuncType, WasmHeapTopType,
34+
WasmHeapType, WasmRefType, WasmResult, WasmValType,
3535
};
3636
use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK};
3737

@@ -136,6 +136,11 @@ pub struct FuncEnvironment<'module_environment> {
136136
needs_gc_heap: bool,
137137
entities: WasmEntities,
138138

139+
/// The byte offset of the module's wasm binary within the outer
140+
/// binary (e.g. a component). Used to make source locations in
141+
/// guest-debug frame tables module-relative.
142+
pub(crate) wasm_module_offset: u64,
143+
139144
/// Translation state at the given point.
140145
pub(crate) stacks: FuncTranslationStacks,
141146

@@ -285,6 +290,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
285290

286291
state_slot: None,
287292
next_srcloc: ir::SourceLoc::default(),
293+
wasm_module_offset: translation.wasm_module_offset,
288294
}
289295
}
290296

@@ -1229,10 +1235,18 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
12291235
.last()
12301236
.map(|s| s.raw())
12311237
.unwrap_or(u32::MAX);
1232-
let pc = srcloc.bits();
1238+
// Convert component-relative srcloc to module-relative
1239+
// Wasm PC for the frame table. The srcloc on the builder
1240+
// remains component-relative for native DWARF and other
1241+
// purposes, but the frame table must be module-relative
1242+
// because the guest-debug API presents a purely core-Wasm
1243+
// view of the world where components are deconstructed
1244+
// into core Wasm modules.
1245+
let component_pc = ComponentPC::new(srcloc.bits());
1246+
let module_pc = component_pc.to_module_pc(self.wasm_module_offset);
12331247
vec![
12341248
ir::DebugTag::StackSlot(*slot),
1235-
ir::DebugTag::User(pc),
1249+
ir::DebugTag::User(module_pc.raw()),
12361250
ir::DebugTag::User(stack_shape),
12371251
]
12381252
} else {

crates/debugger/src/host/opaque.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ impl<T: Send + 'static> OpaqueDebugger for crate::Debuggee<T> {
503503
.wasm_function_index_and_pc(&mut store)
504504
.map_err(|_| wit::Error::InvalidFrame)?
505505
.ok_or(wit::Error::NonWasmFrame)?;
506-
Ok((func.as_u32(), pc))
506+
Ok((func.as_u32(), pc.raw()))
507507
})
508508
.await?
509509
}
@@ -556,7 +556,7 @@ impl<T: Send + 'static> OpaqueDebugger for crate::Debuggee<T> {
556556
store
557557
.edit_breakpoints()
558558
.expect("guest debugging is enabled")
559-
.add_breakpoint(&module, pc)
559+
.add_breakpoint(&module, wasmtime::ModulePC::new(pc))
560560
.map_err(|_| wit::Error::InvalidPc)?;
561561
Ok(())
562562
})
@@ -568,7 +568,7 @@ impl<T: Send + 'static> OpaqueDebugger for crate::Debuggee<T> {
568568
store
569569
.edit_breakpoints()
570570
.expect("guest debugging is enabled")
571-
.remove_breakpoint(&module, pc)
571+
.remove_breakpoint(&module, wasmtime::ModulePC::new(pc))
572572
.map_err(|_| wit::Error::InvalidPc)?;
573573
Ok(())
574574
})

crates/debugger/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,8 @@ mod test {
586586
.wasm_function_index_and_pc(&mut store)
587587
.unwrap()
588588
.unwrap()
589-
.1,
589+
.1
590+
.raw(),
590591
36
591592
);
592593
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
@@ -618,7 +619,8 @@ mod test {
618619
.wasm_function_index_and_pc(&mut store)
619620
.unwrap()
620621
.unwrap()
621-
.1,
622+
.1
623+
.raw(),
622624
38
623625
);
624626
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
@@ -651,7 +653,8 @@ mod test {
651653
.wasm_function_index_and_pc(&mut store)
652654
.unwrap()
653655
.unwrap()
654-
.1,
656+
.1
657+
.raw(),
655658
40
656659
);
657660
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
@@ -685,7 +688,8 @@ mod test {
685688
.wasm_function_index_and_pc(&mut store)
686689
.unwrap()
687690
.unwrap()
688-
.1,
691+
.1
692+
.raw(),
689693
41
690694
);
691695
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);

crates/environ/src/address_map.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Data structures to provide transformation of the source
22
3+
use core::fmt;
34
use object::{Bytes, LittleEndian, U32};
45
use serde_derive::{Deserialize, Serialize};
56

@@ -61,6 +62,85 @@ impl Default for FilePos {
6162
}
6263
}
6364

65+
/// A Wasm bytecode offset relative to the start of a component (or
66+
/// top-level module) binary.
67+
///
68+
/// When compiling a component, the Wasm parser returns source
69+
/// positions relative to the entire component binary. This type
70+
/// captures that convention. Use
71+
/// [`ComponentPC::to_module_pc`] to convert to a
72+
/// [`ModulePC`] given the byte offset of the module within the
73+
/// component.
74+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
75+
pub struct ComponentPC(u32);
76+
77+
impl ComponentPC {
78+
/// Create a new component-relative PC from a raw offset.
79+
pub fn new(offset: u32) -> Self {
80+
Self(offset)
81+
}
82+
83+
/// Get the raw u32 offset.
84+
pub fn raw(self) -> u32 {
85+
self.0
86+
}
87+
88+
/// Convert to a module-relative PC by subtracting the byte offset
89+
/// of the module within the component binary.
90+
pub fn to_module_pc(self, wasm_module_offset: u64) -> ModulePC {
91+
let offset = u32::try_from(wasm_module_offset).unwrap();
92+
ModulePC(self.0 - offset)
93+
}
94+
}
95+
96+
impl fmt::Debug for ComponentPC {
97+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98+
write!(f, "ComponentPC({:#x})", self.0)
99+
}
100+
}
101+
102+
impl fmt::Display for ComponentPC {
103+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104+
write!(f, "{:#x}", self.0)
105+
}
106+
}
107+
108+
/// A Wasm bytecode offset relative to the start of a core Wasm
109+
/// module binary.
110+
///
111+
/// In the guest-debug system, PCs are always module-relative because
112+
/// the debugger presents a core-Wasm view of the world where
113+
/// components are deconstructed into individual core Wasm modules.
114+
///
115+
/// For standalone (non-component) modules, `ModulePC` and
116+
/// [`ComponentPC`] values are numerically identical.
117+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
118+
pub struct ModulePC(u32);
119+
120+
impl ModulePC {
121+
/// Create a new module-relative PC from a raw offset.
122+
pub fn new(offset: u32) -> Self {
123+
Self(offset)
124+
}
125+
126+
/// Get the raw u32 offset.
127+
pub fn raw(self) -> u32 {
128+
self.0
129+
}
130+
}
131+
132+
impl fmt::Debug for ModulePC {
133+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134+
write!(f, "ModulePC({:#x})", self.0)
135+
}
136+
}
137+
138+
impl fmt::Display for ModulePC {
139+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140+
write!(f, "{:#x}", self.0)
141+
}
142+
}
143+
64144
/// Parse an `ELF_WASMTIME_ADDRMAP` section, returning the slice of code offsets
65145
/// and the slice of associated file positions for each offset.
66146
fn parse_address_map(section: &[u8]) -> Option<(&[U32<LittleEndian>], &[U32<LittleEndian>])> {

crates/environ/src/compile/frame_table.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
use crate::{
1212
FrameInstPos, FrameStackShape, FrameStateSlotOffset, FrameTableDescriptorIndex, FrameValType,
13-
FuncKey, WasmHeapTopType, WasmValType, prelude::*,
13+
FuncKey, ModulePC, WasmHeapTopType, WasmValType, prelude::*,
1414
};
1515
use object::{LittleEndian, U32};
1616
use std::collections::{HashMap, hash_map::Entry};
@@ -278,9 +278,9 @@ impl FrameTableBuilder {
278278
&mut self,
279279
native_pc: u32,
280280
pos: FrameInstPos,
281-
// For each frame: Wasm PC, frame descriptor, stack shape
282-
// within the frame descriptor.
283-
frames: &[(u32, FrameTableDescriptorIndex, FrameStackShape)],
281+
// For each frame: module-relative Wasm PC, frame descriptor,
282+
// stack shape within the frame descriptor.
283+
frames: &[(ModulePC, FrameTableDescriptorIndex, FrameStackShape)],
284284
) {
285285
let pc_and_pos = FrameInstPos::encode(native_pc, pos);
286286
// If we already have a program point record at this PC,
@@ -300,11 +300,12 @@ impl FrameTableBuilder {
300300
.push(U32::new(LittleEndian, start));
301301

302302
for (i, &(wasm_pc, frame_descriptor, stack_shape)) in frames.iter().enumerate() {
303-
debug_assert!(wasm_pc < 0x8000_0000);
303+
let wasm_pc_raw = wasm_pc.raw();
304+
debug_assert!(wasm_pc_raw < 0x8000_0000);
304305
let not_last = i < (frames.len() - 1);
305-
let wasm_pc = wasm_pc | if not_last { 0x8000_0000 } else { 0 };
306+
let wasm_pc_raw = wasm_pc_raw | if not_last { 0x8000_0000 } else { 0 };
306307
self.progpoint_descriptor_data
307-
.push(U32::new(LittleEndian, wasm_pc));
308+
.push(U32::new(LittleEndian, wasm_pc_raw));
308309
self.progpoint_descriptor_data
309310
.push(U32::new(LittleEndian, frame_descriptor.0));
310311
self.progpoint_descriptor_data
@@ -313,8 +314,14 @@ impl FrameTableBuilder {
313314
}
314315

315316
/// Add one breakpoint patch.
316-
pub fn add_breakpoint_patch(&mut self, wasm_pc: u32, patch_start_native_pc: u32, patch: &[u8]) {
317-
self.breakpoint_pcs.push(U32::new(LittleEndian, wasm_pc));
317+
pub fn add_breakpoint_patch(
318+
&mut self,
319+
wasm_pc: ModulePC,
320+
patch_start_native_pc: u32,
321+
patch: &[u8],
322+
) {
323+
self.breakpoint_pcs
324+
.push(U32::new(LittleEndian, wasm_pc.raw()));
318325
self.breakpoint_patch_offsets
319326
.push(U32::new(LittleEndian, patch_start_native_pc));
320327
self.breakpoint_patch_data.extend(patch.iter().cloned());

crates/environ/src/compile/module_environ.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ pub struct ModuleTranslation<'data> {
5353
/// themselves.
5454
pub wasm: &'data [u8],
5555

56+
/// The byte offset of this module's Wasm binary within the outer
57+
/// binary (e.g. a component). For standalone modules this is 0.
58+
/// This is used to convert component-relative source locations to
59+
/// module-relative source locations.
60+
pub wasm_module_offset: u64,
61+
5662
/// References to the function bodies.
5763
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'data>>,
5864

@@ -118,6 +124,7 @@ impl<'data> ModuleTranslation<'data> {
118124
Self {
119125
module: Module::new(module_index),
120126
wasm: &[],
127+
wasm_module_offset: 0,
121128
function_body_inputs: PrimaryMap::default(),
122129
known_imported_functions: SecondaryMap::default(),
123130
exported_signatures: Vec::default(),

0 commit comments

Comments
 (0)