fix(elf): relocation records, DWARF tables and per-architecture frame data
Assisted-by: GLM 5.3
This commit is contained in:
+161
-74
@@ -12,43 +12,74 @@ import (
|
||||
// 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.
|
||||
// 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 = 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 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 = 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
|
||||
// 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)
|
||||
@@ -68,6 +99,9 @@ type dwarfSections struct {
|
||||
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 {
|
||||
@@ -76,8 +110,9 @@ type dwarfReloc struct {
|
||||
addend int64
|
||||
}
|
||||
|
||||
// emitDWARF generates complete DWARF5 sections for the image.
|
||||
func emitDWARF(img *Image, srcFile string) *dwarfSections {
|
||||
// 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()
|
||||
|
||||
@@ -86,19 +121,21 @@ func emitDWARF(img *Image, srcFile string) *dwarfSections {
|
||||
lineStr.add(srcFile)
|
||||
ds.debugLineStr = lineStr.bytes()
|
||||
|
||||
// Build .debug_line.
|
||||
ds.debugLine = dwarfBuildLineSection(img, ds)
|
||||
// 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)
|
||||
ds.debugFrame = dwarfBuildFrameSection(img, cfi, ds)
|
||||
return ds
|
||||
}
|
||||
|
||||
// dwarfBuildLineSection builds a complete .debug_line section.
|
||||
func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte {
|
||||
// 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
|
||||
|
||||
@@ -120,15 +157,25 @@ func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte {
|
||||
// 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
|
||||
// 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)
|
||||
|
||||
@@ -176,8 +223,11 @@ func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte {
|
||||
|
||||
// Patch unit_length.
|
||||
le.PutUint32(b[headerStart:], uint32(len(b)-headerStart-4))
|
||||
// Patch header_length.
|
||||
le.PutUint32(b[headerStart+6:], uint32(headerEnd-headerStart-10))
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -195,14 +245,16 @@ func dwarfBuildInfoSection(img *Image, srcFile string, ds *dwarfSections) []byte
|
||||
|
||||
// DW_TAG_compile_unit (abbrev 1).
|
||||
b = append(b, 1) // abbreviation code
|
||||
// DW_AT_low_pc: address of .text start.
|
||||
infoRelocBase := len(b)
|
||||
// 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
|
||||
ds.infoRelocs = append(ds.infoRelocs, dwarfReloc{
|
||||
off: uint64(infoRelocBase),
|
||||
name: img.Funcs[0].Name,
|
||||
addend: 0,
|
||||
})
|
||||
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).
|
||||
@@ -229,8 +281,9 @@ func dwarfBuildInfoSection(img *Image, srcFile string, ds *dwarfSections) []byte
|
||||
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_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.
|
||||
@@ -253,32 +306,61 @@ 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 {
|
||||
return binary.AppendVarint(b, v)
|
||||
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) []byte {
|
||||
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, 16) // return address register (LR on arm64, RIP on amd64)
|
||||
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, 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)
|
||||
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))
|
||||
|
||||
@@ -287,7 +369,12 @@ func dwarfBuildFrameSection(img *Image) []byte {
|
||||
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).
|
||||
// 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))
|
||||
|
||||
Reference in New Issue
Block a user