Files
gasm-sdk/asm/elf_dwarf.go
T
2026-08-21 00:35:21 +02:00

251 lines
7.7 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
// 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, srcFile, ds)
// Build .debug_info.
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
return ds
}
// dwarfBuildLineSection builds a complete .debug_line section.
func dwarfBuildLineSection(img *Image, srcFile string, 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)
}