diff --git a/asm/elf.go b/asm/elf.go index 178259a..70345d6 100644 --- a/asm/elf.go +++ b/asm/elf.go @@ -269,6 +269,9 @@ func (img *Image) ELFObject() ([]byte, error) { putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0) putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0) putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0) + if dw.frameSize > 0 { + putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0) + } } // The ELF header. diff --git a/asm/elf_dwarf.go b/asm/elf_dwarf.go index c221d1d..eec8afa 100644 --- a/asm/elf_dwarf.go +++ b/asm/elf_dwarf.go @@ -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 +} diff --git a/asm/elf_dwarf_sections.go b/asm/elf_dwarf_sections.go index 8a017c7..d08f85a 100644 --- a/asm/elf_dwarf_sections.go +++ b/asm/elf_dwarf_sections.go @@ -12,6 +12,7 @@ type dwarfELFSections struct { infoOff, infoSize int lineOff, lineSize int lineStrOff, lineStrSize int + frameOff, frameSize int // Relocations for .debug_info address references. infoRelocs []elfDwarfReloc // Relocations for .debug_line address references. @@ -91,6 +92,14 @@ func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[str infoBase := len(*out) *out = append(*out, ds.debugInfo...) + // .debug_frame + if len(ds.debugFrame) > 0 { + align(1) + result.frameOff = len(*out) + result.frameSize = len(ds.debugFrame) + *out = append(*out, ds.debugFrame...) + } + // Patch .debug_info relocations. for _, dr := range ds.infoRelocs { if idx, ok := nameToIdx[dr.name]; ok { @@ -130,5 +139,5 @@ func dwarfRelaRecords(dw *dwarfELFSections, relType uint32) []byte { // dwarfSectionNames returns the DWARF section names for the string table. var dwarfSectionNames = []string{ ".debug_abbrev", ".debug_info", ".debug_line", ".debug_line_str", - ".rela.debug_info", ".rela.debug_line", + ".debug_frame", ".rela.debug_info", ".rela.debug_line", } diff --git a/asm/elfarm64.go b/asm/elfarm64.go index 1098d53..aeddb19 100644 --- a/asm/elfarm64.go +++ b/asm/elfarm64.go @@ -226,6 +226,9 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0) putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0) putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0) + if dw.frameSize > 0 { + putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0) + } } // ELF header. diff --git a/asm/elfloong64.go b/asm/elfloong64.go index 245fa19..501ed08 100644 --- a/asm/elfloong64.go +++ b/asm/elfloong64.go @@ -219,6 +219,9 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0) putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0) putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0) + if dw.frameSize > 0 { + putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0) + } } // ELF header. diff --git a/asm/elfriscv.go b/asm/elfriscv.go index dc48b67..5ff3a6c 100644 --- a/asm/elfriscv.go +++ b/asm/elfriscv.go @@ -232,6 +232,9 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0) putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0) putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0) + if dw.frameSize > 0 { + putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0) + } } // ELF header.