// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import "encoding/binary" // Absolute 64-bit relocation types for the DWARF address fixups, one per // supported architecture (the numbers debug/elf carries). const ( rX8664Abs64 = 1 // R_X86_64_64 rAARCH64Abs64 = 257 // R_AARCH64_ABS64 rRISCVAbs64 = 2 // R_RISCV_64 rLarchAbs64 = 2 // R_LARCH_64 ) // 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 frameOff, frameSize int // .rela.debug_info and .rela.debug_line contents: file offsets and // entry counts (zero count: the section is absent). infoRelaOff, infoRelaCount int lineRelaOff, lineRelaCount int frameRelaOff, frameRelaCount int // Relocations for .debug_info address references, offsets relative to // the section start (what an r_offset in .rela.debug_info means). infoRelocs []elfDwarfReloc // Relocations for .debug_line address references, section-relative. lineRelocs []elfDwarfReloc // Relocations for .debug_frame FDE initial locations, section-relative. frameRelocs []elfDwarfReloc } type elfDwarfReloc struct { off uint64 // offset within the target section 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. cfi // carries the architecture's .debug_frame register conventions. func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[string]int, align func(int), cfi cfiArch) *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, cfi) 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) *out = append(*out, ds.debugLine...) for _, dr := range ds.lineRelocs { if idx, ok := nameToIdx[dr.name]; ok { result.lineRelocs = append(result.lineRelocs, elfDwarfReloc{ off: dr.off, sym: idx, addend: dr.addend, }) } } // .debug_info align(1) result.infoOff = len(*out) result.infoSize = len(ds.debugInfo) *out = append(*out, ds.debugInfo...) for _, dr := range ds.infoRelocs { if idx, ok := nameToIdx[dr.name]; ok { result.infoRelocs = append(result.infoRelocs, elfDwarfReloc{ off: dr.off, sym: idx, addend: dr.addend, }) } } // .debug_frame: the section header declares alignment 8, so the data is // padded to 8, matching it. if len(ds.debugFrame) > 0 { align(8) result.frameOff = len(*out) result.frameSize = len(ds.debugFrame) *out = append(*out, ds.debugFrame...) for _, dr := range ds.frameRelocs { if idx, ok := nameToIdx[dr.name]; ok { result.frameRelocs = append(result.frameRelocs, elfDwarfReloc{ off: dr.off, sym: idx, addend: dr.addend, }) } } } return result } // appendDWARFRelas writes the .rela.debug_info and .rela.debug_line section // bodies from the relocations appendDWARFSections recorded, with the // architecture's absolute 64-bit relocation type, and records their file // offsets and entry counts on dw. Called after the DWARF sections // themselves so the r_offsets (section-relative) need no adjustment. func appendDWARFRelas(out *[]byte, dw *dwarfELFSections, abs64 uint32, align func(int)) { le := binary.LittleEndian write := func(relas []elfDwarfReloc) (off, count int) { if len(relas) == 0 { return 0, 0 } align(8) off = len(*out) for _, r := range relas { var b [24]byte le.PutUint64(b[0:], r.off) le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(abs64)) le.PutUint64(b[16:], uint64(r.addend)) *out = append(*out, b[:]...) } return off, len(relas) } dw.infoRelaOff, dw.infoRelaCount = write(dw.infoRelocs) dw.lineRelaOff, dw.lineRelaCount = write(dw.lineRelocs) dw.frameRelaOff, dw.frameRelaCount = write(dw.frameRelocs) } // dwarfSourceName returns the source name the DWARF sections record: the // image's source path when the assembler captured one, "gasm.s" otherwise. func dwarfSourceName(img *Image) string { if img.SourcePath != "" { return img.SourcePath } return "gasm.s" } // dwarfSectionNames returns the DWARF section names for the string table. var dwarfSectionNames = []string{ ".debug_abbrev", ".debug_info", ".debug_line", ".debug_line_str", ".debug_frame", ".rela.debug_info", ".rela.debug_line", ".rela.debug_frame", }