feat(asm): add .debug_frame CFI section for stack unwinding

Assisted-by: MiMo V2.5 Pro
This commit is contained in:
2026-08-21 01:20:21 +02:00
parent f153be8158
commit 746cef8100
6 changed files with 91 additions and 1 deletions
+69
View File
@@ -63,6 +63,7 @@ type dwarfSections struct {
debugInfo []byte
debugLine []byte
debugLineStr []byte
debugFrame []byte
// Relocations for .debug_info: (offset, symbol name, addend).
infoRelocs []dwarfReloc
// Relocations for .debug_line: (offset, symbol name, addend).
@@ -90,6 +91,9 @@ func emitDWARF(img *Image, srcFile string) *dwarfSections {
// Build .debug_info.
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
// Build .debug_frame.
ds.debugFrame = dwarfBuildFrameSection(img)
return ds
}
@@ -248,3 +252,68 @@ func dwarfBuildInfoSection(img *Image, srcFile string, ds *dwarfSections) []byte
func appendUleb(b []byte, v uint64) []byte {
return binary.AppendUvarint(b, v)
}
func appendSleb(b []byte, v int64) []byte {
return binary.AppendVarint(b, v)
}
// dwarfBuildFrameSection builds a .debug_frame section with CFI for stack
// unwinding. It emits one CIE and one FDE per function, encoding the
// CFA (Canonical Frame Address) rule changes at each stack-adjustment
// boundary recorded in FuncLayout.Spadj.
func dwarfBuildFrameSection(img *Image) []byte {
var b []byte
le := binary.LittleEndian
// CIE (Common Information Entry).
cieStart := len(b)
b = append(b, 0, 0, 0, 0) // length (placeholder)
b = le.AppendUint32(b, 0xFFFFFFFF) // CIE marker
b = append(b, 3) // version (DWARF3, widely supported)
b = append(b, 0) // augmentation (empty)
b = appendUleb(b, 1) // code alignment
b = appendSleb(b, -8) // data alignment (-8 for 64-bit)
b = appendUleb(b, 16) // return address register (LR on arm64, RIP on amd64)
// Initial CFA rule: DW_CFA_def_cfa (SP, 0)
b = append(b, 0x0c) // DW_CFA_def_cfa
b = appendUleb(b, 31) // register: SP (RSP=7 on amd64, SP=31 on arm64)
b = appendUleb(b, 0) // offset: 0
b = append(b, 0) // DW_CFA_nop (padding)
// Patch CIE length.
le.PutUint32(b[cieStart:], uint32(len(b)-cieStart-4))
// FDEs (Frame Description Entries) — one per function.
for _, fn := range img.Funcs {
fdeStart := len(b)
b = append(b, 0, 0, 0, 0) // length (placeholder)
b = le.AppendUint32(b, uint32(cieStart)) // CIE pointer (offset from start)
// Initial location: function offset in .text (relocated by linker).
b = le.AppendUint64(b, uint64(fn.Offset))
// Address range: function size.
b = le.AppendUint64(b, uint64(fn.Size))
// Emit CFA rule changes at each Spadj boundary.
for _, step := range fn.Spadj {
if step.Value == 0 {
continue
}
// DW_CFA_def_cfa_offset: set CFA = SP + |delta|.
// The delta is negative (stack grows down), so CFA offset = -delta.
offset := -step.Value
if offset > 0 {
b = append(b, 0x0e) // DW_CFA_def_cfa_offset
b = appendUleb(b, uint64(offset))
}
}
// Pad to alignment.
for len(b)%4 != 0 {
b = append(b, 0) // DW_CFA_nop
}
// Patch FDE length.
le.PutUint32(b[fdeStart:], uint32(len(b)-fdeStart-4))
}
return b
}