407 lines
14 KiB
Go
407 lines
14 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package asm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
)
|
|
|
|
// DWARF5 section generation for ELF output. Unlike the GOOBJ path (where
|
|
// the linker assembles the final DWARF), the ELF path must emit complete,
|
|
// self-contained sections because the system linker only performs fixup
|
|
// relocations, not assembly.
|
|
|
|
// DWARF5 attribute, form and line-table constants (the values the
|
|
// toolchain uses, cmd/internal/dwarf/dwarf_defs.go; the DIE streams below
|
|
// are written against these forms).
|
|
const (
|
|
dwAtName = 0x03 // DW_AT_name
|
|
dwAtStmtList = 0x10 // DW_AT_stmt_list
|
|
dwAtLowPC = 0x11 // DW_AT_low_pc
|
|
dwAtHighPC = 0x12 // DW_AT_high_pc
|
|
dwAtDeclFile = 0x3a // DW_AT_decl_file
|
|
dwAtDeclLine = 0x3b // DW_AT_decl_line
|
|
dwAtExternal = 0x3f // DW_AT_external
|
|
dwAtFrameBase = 0x40 // DW_AT_frame_base
|
|
dwTagSubprog = 0x2e // DW_TAG_subprogram
|
|
dwTagCompUnit = 0x11 // DW_TAG_compile_unit
|
|
dwFormAddr = 0x01 // DW_FORM_addr
|
|
dwFormData8 = 0x07 // DW_FORM_data8
|
|
dwFormString = 0x08 // DW_FORM_string
|
|
dwFormData1 = 0x0b // DW_FORM_data1
|
|
dwFormUdata = 0x0f // DW_FORM_udata
|
|
dwFormSecOff = 0x17 // DW_FORM_sec_offset
|
|
dwFormExprloc = 0x18 // DW_FORM_exprloc
|
|
dwFormLineStrp = 0x1f // DW_FORM_line_strp
|
|
dwLnctPath = 0x01 // DW_LNCT_path
|
|
dwLnctDirIndex = 0x02 // DW_LNCT_directory_index
|
|
)
|
|
|
|
// dwarfAbbrevTable returns the .debug_abbrev content: a single compilation
|
|
// unit with DW_TAG_compile_unit and DW_TAG_subprogram entries. The
|
|
// attribute/form pairs must match the DIE streams dwarfBuildInfoSection
|
|
// writes byte for byte, in the same order, or every consumer's parse of
|
|
// .debug_info desynchronises.
|
|
func dwarfAbbrevTable() []byte {
|
|
var b []byte
|
|
// Abbrev 1: DW_TAG_compile_unit.
|
|
b = append(b, 1) // abbreviation code
|
|
b = appendUleb(b, dwTagCompUnit) // DW_TAG_compile_unit
|
|
b = append(b, 1) // DW_CHILDREN_yes
|
|
b = appendUleb(b, dwAtLowPC) // DW_AT_low_pc
|
|
b = appendUleb(b, dwFormAddr) // DW_FORM_addr
|
|
b = appendUleb(b, dwAtHighPC) // DW_AT_high_pc
|
|
b = appendUleb(b, dwFormData8) // DW_FORM_data8
|
|
b = appendUleb(b, dwAtStmtList) // DW_AT_stmt_list
|
|
b = appendUleb(b, dwFormSecOff) // DW_FORM_sec_offset (4 bytes here)
|
|
b = appendUleb(b, dwAtName) // DW_AT_name
|
|
b = appendUleb(b, dwFormString) // DW_FORM_string
|
|
b = appendUleb(b, 0) // end of attributes: attr 0
|
|
b = appendUleb(b, 0) // ... paired with form 0
|
|
|
|
// Abbrev 2: DW_TAG_subprogram.
|
|
b = append(b, 2) // abbreviation code
|
|
b = appendUleb(b, dwTagSubprog) // DW_TAG_subprogram
|
|
b = append(b, 0) // DW_CHILDREN_no
|
|
b = appendUleb(b, dwAtName) // DW_AT_name
|
|
b = appendUleb(b, dwFormString) // DW_FORM_string
|
|
b = appendUleb(b, dwAtLowPC) // DW_AT_low_pc
|
|
b = appendUleb(b, dwFormAddr) // DW_FORM_addr
|
|
b = appendUleb(b, dwAtHighPC) // DW_AT_high_pc
|
|
b = appendUleb(b, dwFormData8) // DW_FORM_data8
|
|
b = appendUleb(b, dwAtFrameBase) // DW_AT_frame_base
|
|
b = appendUleb(b, dwFormExprloc) // DW_FORM_exprloc
|
|
b = appendUleb(b, dwAtDeclFile) // DW_AT_decl_file
|
|
b = appendUleb(b, dwFormData1) // DW_FORM_data1
|
|
b = appendUleb(b, dwAtDeclLine) // DW_AT_decl_line
|
|
b = appendUleb(b, dwFormData1) // DW_FORM_data1
|
|
b = appendUleb(b, dwAtExternal) // DW_AT_external
|
|
b = appendUleb(b, 0x0c) // DW_FORM_flag (one byte, 0 or 1)
|
|
b = appendUleb(b, 0) // end of attributes: attr 0
|
|
b = appendUleb(b, 0) // ... paired with form 0
|
|
|
|
// End of table.
|
|
b = append(b, 0)
|
|
return b
|
|
}
|
|
|
|
// dwarfSections holds the generated DWARF section payloads and their
|
|
// relocations (byte offsets within .debug_info and .debug_line that need
|
|
// fixup against .text symbols).
|
|
type dwarfSections struct {
|
|
debugAbbrev []byte
|
|
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).
|
|
lineRelocs []dwarfReloc
|
|
// Relocations for .debug_frame: (offset, symbol name, addend), one per
|
|
// FDE initial_location.
|
|
frameRelocs []dwarfReloc
|
|
}
|
|
|
|
type dwarfReloc struct {
|
|
off uint64
|
|
name string
|
|
addend int64
|
|
}
|
|
|
|
// emitDWARF generates complete DWARF5 sections for the image. cfi carries
|
|
// the architecture's .debug_frame register conventions.
|
|
func emitDWARF(img *Image, srcFile string, cfi cfiArch) *dwarfSections {
|
|
ds := &dwarfSections{}
|
|
ds.debugAbbrev = dwarfAbbrevTable()
|
|
|
|
// Build the string table for .debug_line_str.
|
|
lineStr := newElfStrtab()
|
|
lineStr.add(srcFile)
|
|
ds.debugLineStr = lineStr.bytes()
|
|
|
|
// Build .debug_line; the file table references the source name through
|
|
// its offset in .debug_line_str.
|
|
ds.debugLine = dwarfBuildLineSection(img, uint32(lineStr.at(srcFile)), ds)
|
|
|
|
// Build .debug_info.
|
|
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
|
|
|
|
// Build .debug_frame.
|
|
ds.debugFrame = dwarfBuildFrameSection(img, cfi, ds)
|
|
return ds
|
|
}
|
|
|
|
// dwarfBuildLineSection builds a complete .debug_line section. srcStrOff is
|
|
// the source file name's offset in .debug_line_str.
|
|
func dwarfBuildLineSection(img *Image, srcStrOff uint32, ds *dwarfSections) []byte {
|
|
var b []byte
|
|
le := binary.LittleEndian
|
|
|
|
// We'll build the header first, then the programs, then patch the length.
|
|
headerStart := len(b)
|
|
b = append(b, 0, 0, 0, 0) // unit_length (placeholder)
|
|
b = le.AppendUint16(b, 5) // version (DWARF5)
|
|
b = append(b, 8) // address_size
|
|
b = append(b, 0) // segment_selector_size
|
|
b = append(b, 0, 0, 0, 0) // header_length (placeholder)
|
|
|
|
// Line program parameters.
|
|
b = append(b, 1) // minimum_instruction_length
|
|
b = append(b, 1) // maximum_ops_per_instruction
|
|
b = append(b, 1) // default_is_stmt
|
|
b = append(b, byte(dwLineBase&0xFF)) // line_base (-4 as unsigned)
|
|
b = append(b, uint8(dwLineRange)) // line_range
|
|
b = append(b, uint8(dwOpcodeBase)) // opcode_base
|
|
// Standard opcode lengths (opcode 1..opcode_base-1).
|
|
b = append(b, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0)
|
|
|
|
// Directory table (DWARF5 §6.2.4): entry format descriptors followed by
|
|
// the entries. One directory, the compilation directory, whose path is
|
|
// the empty string at .debug_line_str offset 0.
|
|
b = append(b, 1) // directory_entry_format_count
|
|
b = appendUleb(b, dwLnctPath) // DW_LNCT_path
|
|
b = appendUleb(b, dwFormLineStrp) // DW_FORM_line_strp
|
|
b = appendUleb(b, 1) // directories_count
|
|
b = le.AppendUint32(b, 0) // .debug_line_str offset of ""
|
|
|
|
// File table (DWARF5 §6.2.5). v5 indexes files from 0, so the source
|
|
// file is entry 0, matching the DW_AT_decl_file value 0 the DIEs carry.
|
|
b = append(b, 2) // file_name_entry_format_count
|
|
b = appendUleb(b, dwLnctPath) // DW_LNCT_path
|
|
b = appendUleb(b, dwFormLineStrp) // DW_FORM_line_strp
|
|
b = appendUleb(b, dwLnctDirIndex) // DW_LNCT_directory_index
|
|
b = appendUleb(b, dwFormUdata) // DW_FORM_udata
|
|
b = appendUleb(b, 1) // file_names_count
|
|
b = le.AppendUint32(b, srcStrOff) // .debug_line_str offset of the source name
|
|
b = appendUleb(b, 0) // directory index 0 (the compilation directory)
|
|
|
|
headerEnd := len(b)
|
|
|
|
// Per-function line programs.
|
|
for _, fn := range img.Funcs {
|
|
// LNE_set_address with the function's offset in .text.
|
|
b = append(b, 0, 9, 2) // extended opcode, length 9, DW_LNE_set_address
|
|
addrOff := len(b)
|
|
b = le.AppendUint64(b, 0) // placeholder for address
|
|
ds.lineRelocs = append(ds.lineRelocs, dwarfReloc{
|
|
off: uint64(addrOff),
|
|
name: fn.Name,
|
|
addend: 0,
|
|
})
|
|
|
|
// Build the line entries.
|
|
pts := make([]LineEntry, 0, len(fn.Lines)+1)
|
|
if len(fn.Lines) == 0 || fn.Lines[0].Offset > 0 {
|
|
pts = append(pts, LineEntry{Offset: 0, Line: fn.Line})
|
|
}
|
|
pts = append(pts, fn.Lines...)
|
|
|
|
line := int64(1)
|
|
pc := uint64(0)
|
|
for _, p := range pts {
|
|
if p.Line == 0 || uint64(p.Offset) < pc {
|
|
continue
|
|
}
|
|
if int64(p.Line) == line {
|
|
continue
|
|
}
|
|
deltaPC := uint64(p.Offset) - pc
|
|
deltaLC := int64(p.Line) - line
|
|
b = dwPutPCLCDelta(b, deltaPC, deltaLC)
|
|
line, pc = int64(p.Line), uint64(p.Offset)
|
|
}
|
|
|
|
// Advance to end of function.
|
|
if end := uint64(fn.Size) - pc; end > 0 {
|
|
b = append(b, 2) // DW_LNS_advance_pc
|
|
b = appendUleb(b, end)
|
|
}
|
|
b = append(b, 0, 1, 1) // LNE_end_sequence
|
|
}
|
|
|
|
// Patch unit_length.
|
|
le.PutUint32(b[headerStart:], uint32(len(b)-headerStart-4))
|
|
// Patch header_length. In the v5 header it follows the one-byte
|
|
// address_size and segment_selector_size (offset 8, not the DWARF2-4
|
|
// offset 6), and counts from just past itself to the first program
|
|
// byte.
|
|
le.PutUint32(b[headerStart+8:], uint32(headerEnd-headerStart-12))
|
|
return b
|
|
}
|
|
|
|
// dwarfBuildInfoSection builds a complete .debug_info section.
|
|
func dwarfBuildInfoSection(img *Image, srcFile string, ds *dwarfSections) []byte {
|
|
var b []byte
|
|
le := binary.LittleEndian
|
|
|
|
cuStart := len(b)
|
|
b = append(b, 0, 0, 0, 0) // unit_length (placeholder)
|
|
b = le.AppendUint16(b, 5) // version (DWARF5)
|
|
b = append(b, 0x01) // unit_type (DW_UT_compile)
|
|
b = append(b, 8) // address_size
|
|
b = le.AppendUint32(b, 0) // debug_abbrev_offset (0 since single CU)
|
|
|
|
// DW_TAG_compile_unit (abbrev 1).
|
|
b = append(b, 1) // abbreviation code
|
|
// DW_AT_low_pc: address of .text start. A data-only image has no
|
|
// functions to relocate against; its CU covers no code, so the base
|
|
// stays zero (the DWARF "no base address" value) with no relocation.
|
|
b = le.AppendUint64(b, 0) // placeholder
|
|
if len(img.Funcs) > 0 {
|
|
ds.infoRelocs = append(ds.infoRelocs, dwarfReloc{
|
|
off: uint64(len(b) - 8),
|
|
name: img.Funcs[0].Name,
|
|
})
|
|
}
|
|
// DW_AT_high_pc: size of .text.
|
|
b = le.AppendUint64(b, uint64(len(img.Code)))
|
|
// DW_AT_stmt_list: offset into .debug_line (0).
|
|
b = le.AppendUint32(b, 0)
|
|
// DW_AT_name: source file name.
|
|
b = append(b, srcFile...)
|
|
b = append(b, 0)
|
|
|
|
// DW_TAG_subprogram entries (abbrev 2).
|
|
for _, fn := range img.Funcs {
|
|
b = append(b, 2) // abbreviation code
|
|
// DW_AT_name.
|
|
b = append(b, fn.Name...)
|
|
b = append(b, 0)
|
|
// DW_AT_low_pc.
|
|
addrOff := len(b)
|
|
b = le.AppendUint64(b, 0) // placeholder
|
|
ds.infoRelocs = append(ds.infoRelocs, dwarfReloc{
|
|
off: uint64(addrOff),
|
|
name: fn.Name,
|
|
addend: 0,
|
|
})
|
|
// DW_AT_high_pc: function size.
|
|
b = le.AppendUint64(b, uint64(fn.Size))
|
|
// DW_AT_frame_base: DW_OP_call_frame_cfa.
|
|
b = append(b, 1, 0x9c)
|
|
// DW_AT_decl_file: the single file-table entry, index 0 (v5 indexes
|
|
// files from 0).
|
|
b = append(b, 0)
|
|
// DW_AT_decl_line.
|
|
b = append(b, uint8(fn.Line))
|
|
// DW_AT_external.
|
|
if fn.Static {
|
|
b = append(b, 0)
|
|
} else {
|
|
b = append(b, 1)
|
|
}
|
|
}
|
|
|
|
// End of compile unit children.
|
|
b = append(b, 0)
|
|
|
|
// Patch unit_length.
|
|
le.PutUint32(b[cuStart:], uint32(len(b)-cuStart-4))
|
|
return b
|
|
}
|
|
|
|
func appendUleb(b []byte, v uint64) []byte {
|
|
return binary.AppendUvarint(b, v)
|
|
}
|
|
|
|
// appendSleb appends v in signed LEB128, the encoding DWARF specifies:
|
|
// two's-complement sign extension, which is NOT Go's zigzag varint
|
|
// (binary.AppendVarint(-8) encodes 15, where DWARF wants 0x78).
|
|
func appendSleb(b []byte, v int64) []byte {
|
|
for {
|
|
c := byte(v & 0x7f)
|
|
v >>= 7
|
|
if (v == 0 && c&0x40 == 0) || (v == -1 && c&0x40 != 0) {
|
|
return append(b, c)
|
|
}
|
|
b = append(b, c|0x80)
|
|
}
|
|
}
|
|
|
|
// cfiArch carries the .debug_frame CIE parameters that differ per
|
|
// architecture: the DWARF register numbers of the stack pointer the initial
|
|
// CFA rule names and of the return address. The values are the ones the Go
|
|
// linker writes into its own CIE (cmd/link/internal/ld/dwarf.go uses
|
|
// Dwarfregsp and Dwarfreglr; the per-architecture constants live in
|
|
// cmd/link/internal/<arch>/l.go).
|
|
type cfiArch struct {
|
|
name string
|
|
cfaReg byte // the stack-pointer register the initial CFA rule names
|
|
raReg byte // the return-address register
|
|
}
|
|
|
|
var (
|
|
cfiAMD64 = cfiArch{"amd64", 7, 16} // RSP, RIP
|
|
cfiARM64 = cfiArch{"arm64", 31, 30} // SP (X31), LR (X30)
|
|
cfiRISCV64 = cfiArch{"riscv64", 2, 1} // X2 (sp), X1 (ra)
|
|
cfiLOONG64 = cfiArch{"loong64", 3, 1} // $r3 (sp), $r1 (ra)
|
|
)
|
|
|
|
// 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, cfi cfiArch, ds *dwarfSections) []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, uint64(cfi.raReg)) // return address register
|
|
// Initial CFA rule: DW_CFA_def_cfa (SP, 0)
|
|
b = append(b, 0x0c) // DW_CFA_def_cfa
|
|
b = appendUleb(b, uint64(cfi.cfaReg)) // the architecture's stack pointer
|
|
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, referenced through
|
|
// the function's symbol so the linker relocates it.
|
|
ds.frameRelocs = append(ds.frameRelocs, dwarfReloc{
|
|
off: uint64(fdeStart + 8),
|
|
name: fn.Name,
|
|
})
|
|
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
|
|
}
|