From ce5ec24fa86278dc1e7b55eddc6260e62e8bc415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Fri, 21 Aug 2026 00:14:02 +0200 Subject: [PATCH] feat(asm): integrate DWARF5 sections into all ELF emitters Assisted-by: MiMo V2.5 Pro --- asm/elf.go | 23 +++++++ asm/elf_dwarf_sections.go | 134 ++++++++++++++++++++++++++++++++++++++ asm/elfarm64.go | 20 ++++++ asm/elfloong64.go | 19 ++++++ asm/elfriscv.go | 19 ++++++ 5 files changed, 215 insertions(+) create mode 100644 asm/elf_dwarf_sections.go diff --git a/asm/elf.go b/asm/elf.go index f40dc0a..178259a 100644 --- a/asm/elf.go +++ b/asm/elf.go @@ -48,6 +48,7 @@ const ( shnUndef = 0 rX8664PC32 = 2 + rX8664_64 = 1 // R_X86_64_64 (absolute 64-bit) ) // elfSym is one symbol-table entry in construction. @@ -160,6 +161,9 @@ func (img *Image) ELFObject() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + for _, n := range dwarfSectionNames { + stSections.add(n) + } // Section presence: .rela.text only when there are relocations. hasRela := len(relas) > 0 @@ -220,6 +224,17 @@ func (img *Image) ELFObject() ([]byte, error) { shstrOff := len(out) out = append(out, stSections.bytes()...) + // DWARF debug sections (no relocations — the linker resolves DWARF fixups). + dwAlign := func(n int) { + for len(out)%n != 0 { + out = append(out, 0) + } + } + dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign) + if dw != nil { + nSections += 4 // .debug_abbrev, .debug_info, .debug_line, .debug_line_str + } + align(8) shoff := len(out) @@ -248,6 +263,14 @@ func (img *Image) ELFObject() ([]byte, error) { } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) + // DWARF section headers. + if dw != nil { + putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0) + 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) + } + // The ELF header. hdr := out[:64] copy(hdr[0:], []byte{0x7f, 'E', 'L', 'F', elfClass64, elfDataLSB, elfVersion, 0}) diff --git a/asm/elf_dwarf_sections.go b/asm/elf_dwarf_sections.go new file mode 100644 index 0000000..8a017c7 --- /dev/null +++ b/asm/elf_dwarf_sections.go @@ -0,0 +1,134 @@ +// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) +// SPDX-License-Identifier: BSD-3-Clause + +package asm + +import "encoding/binary" + +// dwarfELFSections holds the laid-out DWARF sections ready for inclusion +// in an ELF file. +type dwarfELFSections struct { + abbrevOff, abbrevSize int + infoOff, infoSize int + lineOff, lineSize int + lineStrOff, lineStrSize int + // Relocations for .debug_info address references. + infoRelocs []elfDwarfReloc + // Relocations for .debug_line address references. + lineRelocs []elfDwarfReloc +} + +type elfDwarfReloc struct { + off uint64 + sym int // symbol index in .symtab + addend int64 +} + +// appendDWARFSections generates and appends DWARF5 debug sections to the ELF +// output. It returns the section offsets/sizes and relocations for the caller +// to emit section headers and relocation records. +// +// symIdx maps function names to their .symtab indices (needed for relocations +// against .text symbols). The map uses objectName format (pkg.name); the +// DWARF code uses bare function names, so we build a reverse lookup. +func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[string]int, align func(int)) *dwarfELFSections { + // Build a lookup from bare function name to symbol index. + nameToIdx := make(map[string]int, len(symIdx)) + for name, idx := range symIdx { + // Strip package prefix: "pkg.name" → "name". + if i := len(name) - 1; i >= 0 { + for j := len(name) - 1; j >= 0; j-- { + if name[j] == '.' { + nameToIdx[name[j+1:]] = idx + break + } + } + } + nameToIdx[name] = idx + } + ds := emitDWARF(img, srcFile) + if ds == nil || len(ds.debugAbbrev) == 0 { + return nil + } + + result := &dwarfELFSections{} + + // .debug_abbrev + align(1) + result.abbrevOff = len(*out) + result.abbrevSize = len(ds.debugAbbrev) + *out = append(*out, ds.debugAbbrev...) + + // .debug_line_str + align(1) + result.lineStrOff = len(*out) + result.lineStrSize = len(ds.debugLineStr) + *out = append(*out, ds.debugLineStr...) + + // .debug_line + align(1) + result.lineOff = len(*out) + result.lineSize = len(ds.debugLine) + lineBase := len(*out) + *out = append(*out, ds.debugLine...) + + // Patch .debug_line relocations: replace placeholder addresses with + // actual .text offsets via symbol lookup. + for _, dr := range ds.lineRelocs { + if idx, ok := nameToIdx[dr.name]; ok { + result.lineRelocs = append(result.lineRelocs, elfDwarfReloc{ + off: uint64(lineBase) + dr.off, + sym: idx, + addend: dr.addend, + }) + } + } + + // .debug_info + align(1) + result.infoOff = len(*out) + result.infoSize = len(ds.debugInfo) + infoBase := len(*out) + *out = append(*out, ds.debugInfo...) + + // Patch .debug_info relocations. + for _, dr := range ds.infoRelocs { + if idx, ok := nameToIdx[dr.name]; ok { + result.infoRelocs = append(result.infoRelocs, elfDwarfReloc{ + off: uint64(infoBase) + dr.off, + sym: idx, + addend: dr.addend, + }) + } + } + + return result +} + +// dwarfRelaRecords returns the ELF RELA records for DWARF address fixups. +// Each record uses R_X86_64_64 (absolute 64-bit) relocation type. +func dwarfRelaRecords(dw *dwarfELFSections, relType uint32) []byte { + le := binary.LittleEndian + var out []byte + for _, r := range dw.infoRelocs { + var b [24]byte + le.PutUint64(b[0:], r.off) + le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(relType)) + le.PutUint64(b[16:], uint64(r.addend)) + out = append(out, b[:]...) + } + for _, r := range dw.lineRelocs { + var b [24]byte + le.PutUint64(b[0:], r.off) + le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(relType)) + le.PutUint64(b[16:], uint64(r.addend)) + out = append(out, b[:]...) + } + return out +} + +// 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", +} diff --git a/asm/elfarm64.go b/asm/elfarm64.go index 9d3bb4e..1098d53 100644 --- a/asm/elfarm64.go +++ b/asm/elfarm64.go @@ -122,6 +122,9 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + for _, n := range dwarfSectionNames { + stSections.add(n) + } hasRela := len(relas) > 0 nSections := 6 @@ -181,6 +184,17 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { shstrOff := len(out) out = append(out, stSections.bytes()...) + // DWARF debug sections. + dwAlign := func(n int) { + for len(out)%n != 0 { + out = append(out, 0) + } + } + dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign) + if dw != nil { + nSections += 4 + } + align(8) shoff := len(out) @@ -207,6 +221,12 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) + if dw != nil { + putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0) + 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) + } // ELF header. hdr := out[:64] diff --git a/asm/elfloong64.go b/asm/elfloong64.go index e321fe9..245fa19 100644 --- a/asm/elfloong64.go +++ b/asm/elfloong64.go @@ -116,6 +116,9 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + for _, n := range dwarfSectionNames { + stSections.add(n) + } hasRela := len(relas) > 0 nSections := 6 @@ -175,6 +178,16 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { shstrOff := len(out) out = append(out, stSections.bytes()...) + dwAlign := func(n int) { + for len(out)%n != 0 { + out = append(out, 0) + } + } + dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign) + if dw != nil { + nSections += 4 + } + align(8) shoff := len(out) @@ -201,6 +214,12 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) + if dw != nil { + putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0) + 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) + } // ELF header. hdr := out[:64] diff --git a/asm/elfriscv.go b/asm/elfriscv.go index 1f5cf3d..dc48b67 100644 --- a/asm/elfriscv.go +++ b/asm/elfriscv.go @@ -129,6 +129,9 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + for _, n := range dwarfSectionNames { + stSections.add(n) + } hasRela := len(relas) > 0 nSections := 6 @@ -188,6 +191,16 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { shstrOff := len(out) out = append(out, stSections.bytes()...) + dwAlign := func(n int) { + for len(out)%n != 0 { + out = append(out, 0) + } + } + dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign) + if dw != nil { + nSections += 4 + } + align(8) shoff := len(out) @@ -214,6 +227,12 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) + if dw != nil { + putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0) + 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) + } // ELF header. hdr := out[:64]