Skip to content

std.debug.Dwarf should be refactored to not operate on a native CPU context #25436

@alexrp

Description

@alexrp

Consider these functions:

zig/lib/std/debug/Dwarf.zig

Lines 1429 to 1458 in 14019a9

/// Returns `null` for CPU architectures without an instruction pointer register.
pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
return switch (arch) {
.x86 => 8,
.x86_64 => 16,
.arm, .armeb, .thumb, .thumbeb => 15,
.aarch64, .aarch64_be => 32,
else => null,
};
}
pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
return switch (arch) {
.x86 => 5,
.x86_64 => 6,
.arm, .armeb, .thumb, .thumbeb => 11,
.aarch64, .aarch64_be => 29,
else => unreachable,
};
}
pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
return switch (arch) {
.x86 => 4,
.x86_64 => 7,
.arm, .armeb, .thumb, .thumbeb => 13,
.aarch64, .aarch64_be => 31,
else => unreachable,
};
}

spRegNum is mostly fine since all relevant architectures define a stack pointer, but ipRegNum and fpRegNum don't really make a ton of sense in the general case:

  • Most architectures don't actually define a DWARF register number for their program counter. The only ones that do, as far as I can tell, are Arm and AArch64.
    • The std.debug code currently conflates the program counter with the return address DWARF register.
  • There are architectures that either do not define a fixed frame pointer register in their calling convention, or do not define a DWARF register number for it. PowerPC is one example here.

In reality, most uses of these functions don't really care what the DWARF register number is anyway (if one even exists).

Additionally, the unwind code expects to be able to ask the native CPU context for storage locations for DWARF registers - which can be (and often are) virtual registers with no relation to actual hardware registers. That's kind of what the "program counter" register described above ends up being in practice.

So instead of operating on a native CPU context in std.debug.Dwarf, we should define something like:

const State = struct {
    regs: [n]usize,
};

Where n is determined on a per-target basis depending on how many registers are actually relevant for unwinding. This will have a close relationship with the DWARF_FRAME_REGISTERS constant in GCC. However, that constant can cover a sparse range of DWARF register numbers (e.g. 110 on PowerPC), so indexing Context.regs with DWARF register numbers isn't quite it; instead, we'll remap the DWARF register numbers to contiguous internal indexes, allowing us to select a small n. We'll use those internal indexes when capturing the current CPU context, when translating from a signal context, and when processing register rules in the unwinder.

I'll be doing ABI research for this effort and posting the results as comments on this issue...

Metadata

Metadata

Assignees

Labels

enhancementSolving this issue will likely involve adding new logic or components to the codebase.standard libraryThis issue involves writing Zig code for the standard library.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions