320 lines
10 KiB
Go
320 lines
10 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.
|
|
|
|
// dwarfAbbrevTable returns the .debug_abbrev content: a single compilation
|
|
// unit with DW_TAG_compile_unit and DW_TAG_subprogram entries.
|
|
func dwarfAbbrevTable() []byte {
|
|
var b []byte
|
|
// Abbrev 1: DW_TAG_compile_unit
|
|
b = append(b, 1) // abbreviation code
|
|
b = append(b, 0x11) // DW_TAG_compile_unit
|
|
b = append(b, 1) // DW_CHILDREN_yes
|
|
b = appendUleb(b, 0x1b) // DW_AT_low_pc
|
|
b = appendUleb(b, 0x01) // DW_FORM_addr
|
|
b = appendUleb(b, 0x29) // DW_AT_high_pc
|
|
b = appendUleb(b, 0x07) // DW_FORM_data8
|
|
b = appendUleb(b, 0x10) // DW_AT_stmt_list
|
|
b = appendUleb(b, 0x25) // DW_FORM_sec_offset
|
|
b = appendUleb(b, 0x01) // DW_AT_name
|
|
b = appendUleb(b, 0x08) // DW_FORM_string
|
|
b = appendUleb(b, 0) // end of attributes
|
|
|
|
// Abbrev 2: DW_TAG_subprogram
|
|
b = append(b, 2) // abbreviation code
|
|
b = append(b, 0x2e) // DW_TAG_subprogram
|
|
b = append(b, 0) // DW_CHILDREN_no
|
|
b = appendUleb(b, 0x03) // DW_AT_name
|
|
b = appendUleb(b, 0x08) // DW_FORM_string
|
|
b = appendUleb(b, 0x11) // DW_AT_low_pc
|
|
b = appendUleb(b, 0x01) // DW_FORM_addr
|
|
b = appendUleb(b, 0x29) // DW_AT_high_pc
|
|
b = appendUleb(b, 0x07) // DW_FORM_data8
|
|
b = appendUleb(b, 0x3f) // DW_AT_frame_base
|
|
b = appendUleb(b, 0x18) // DW_FORM_exprloc
|
|
b = appendUleb(b, 0x3b) // DW_AT_decl_file
|
|
b = appendUleb(b, 0x0b) // DW_FORM_data1
|
|
b = appendUleb(b, 0x37) // DW_AT_decl_line
|
|
b = appendUleb(b, 0x0b) // DW_FORM_data1
|
|
b = appendUleb(b, 0x63) // DW_AT_external
|
|
b = appendUleb(b, 0x0b) // DW_FORM_flag
|
|
b = appendUleb(b, 0) // end of attributes
|
|
|
|
// 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
|
|
}
|
|
|
|
type dwarfReloc struct {
|
|
off uint64
|
|
name string
|
|
addend int64
|
|
}
|
|
|
|
// emitDWARF generates complete DWARF5 sections for the image.
|
|
func emitDWARF(img *Image, srcFile string) *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.
|
|
ds.debugLine = dwarfBuildLineSection(img, ds)
|
|
|
|
// Build .debug_info.
|
|
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
|
|
|
|
// Build .debug_frame.
|
|
ds.debugFrame = dwarfBuildFrameSection(img)
|
|
return ds
|
|
}
|
|
|
|
// dwarfBuildLineSection builds a complete .debug_line section.
|
|
func dwarfBuildLineSection(img *Image, 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 format).
|
|
b = append(b, 0) // one directory entry (index 0 = empty)
|
|
// File table.
|
|
b = appendUleb(b, 1) // file count
|
|
// File 1: name index into .debug_line_str, dir index, time, size.
|
|
b = appendUleb(b, 0) // name (index 0 in line_str)
|
|
b = appendUleb(b, 0) // directory index
|
|
b = appendUleb(b, 0) // last modification time
|
|
b = appendUleb(b, 0) // file size
|
|
|
|
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.
|
|
le.PutUint32(b[headerStart+6:], uint32(headerEnd-headerStart-10))
|
|
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.
|
|
infoRelocBase := len(b)
|
|
b = le.AppendUint64(b, 0) // placeholder
|
|
ds.infoRelocs = append(ds.infoRelocs, dwarfReloc{
|
|
off: uint64(infoRelocBase),
|
|
name: img.Funcs[0].Name,
|
|
addend: 0,
|
|
})
|
|
// 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: file index 1.
|
|
b = append(b, 1)
|
|
// 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)
|
|
}
|
|
|
|
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
|
|
}
|