fix(elf): relocation records, DWARF tables and per-architecture frame data
Assisted-by: GLM 5.3
This commit is contained in:
+49
-7
@@ -42,8 +42,10 @@ const (
|
|||||||
sttSection = 3
|
sttSection = 3
|
||||||
stInfoShift = 4
|
stInfoShift = 4
|
||||||
|
|
||||||
rX8664PC32 = 2
|
rX8664PC32 = 2
|
||||||
rX8664TPOFF32 = 20
|
// R_X86_64_TPOFF32 (debug/elf): the local-exec TLS offset the stack
|
||||||
|
// guard loads from FS. 20 is R_X86_64_TLSLD, a different relocation.
|
||||||
|
rX8664TPOFF32 = 23
|
||||||
)
|
)
|
||||||
|
|
||||||
// elfSym is one symbol-table entry in construction.
|
// elfSym is one symbol-table entry in construction.
|
||||||
@@ -219,7 +221,7 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
for _, r := range relas {
|
for _, r := range relas {
|
||||||
var b [24]byte
|
var b [24]byte
|
||||||
le.PutUint64(b[0:], r.off)
|
le.PutUint64(b[0:], r.off)
|
||||||
le.PutUint64(b[8:], uint64(r.sym)<<32|rX8664PC32)
|
le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ))
|
||||||
le.PutUint64(b[16:], uint64(r.addend))
|
le.PutUint64(b[16:], uint64(r.addend))
|
||||||
out = append(out, b[:]...)
|
out = append(out, b[:]...)
|
||||||
}
|
}
|
||||||
@@ -228,15 +230,32 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
shstrOff := len(out)
|
shstrOff := len(out)
|
||||||
out = append(out, stSections.bytes()...)
|
out = append(out, stSections.bytes()...)
|
||||||
|
|
||||||
// DWARF debug sections (no relocations, the linker resolves DWARF fixups).
|
// DWARF debug sections; the address placeholders they leave are carried
|
||||||
|
// as .rela.debug_info/.rela.debug_line entries the system linker applies.
|
||||||
dwAlign := func(n int) {
|
dwAlign := func(n int) {
|
||||||
for len(out)%n != 0 {
|
for len(out)%n != 0 {
|
||||||
out = append(out, 0)
|
out = append(out, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign)
|
dw := appendDWARFSections(&out, img, dwarfSourceName(img), symIdx, dwAlign, cfiAMD64)
|
||||||
|
dwarfStart := 0 // section index of .debug_abbrev, set when DWARF is present
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
nSections += 4 // .debug_abbrev, .debug_info, .debug_line, .debug_line_str
|
// Five DWARF sections: .debug_abbrev, .debug_info, .debug_line,
|
||||||
|
// .debug_line_str and .debug_frame (the CIE is unconditional, so
|
||||||
|
// the frame section is always present), plus the relocation
|
||||||
|
// sections below when they carry entries.
|
||||||
|
dwarfStart = nSections
|
||||||
|
nSections += 5
|
||||||
|
appendDWARFRelas(&out, dw, rX8664Abs64, dwAlign)
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
align(8)
|
align(8)
|
||||||
@@ -267,14 +286,37 @@ func (img *Image) ELFObject() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
|
|
||||||
// DWARF section headers.
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
// secIdx is a running section index: each putSh below emits the
|
||||||
|
// next header, and the sh_info of a .rela section names the index
|
||||||
|
// of the section it relocates.
|
||||||
|
secIdx := dwarfStart
|
||||||
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
||||||
|
secInfoIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_info", shtRela, 0, dw.infoRelaOff, 24*dw.infoRelaCount, secSymtab, secInfoIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
||||||
|
secLineIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_line", shtRela, 0, dw.lineRelaOff, 24*dw.lineRelaCount, secSymtab, secLineIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
if dw.frameSize > 0 {
|
if dw.frameSize > 0 {
|
||||||
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
||||||
|
secFrameIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_frame", shtRela, 0, dw.frameRelaOff, 24*dw.frameRelaCount, secSymtab, secFrameIdx, 8, 24)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+161
-74
@@ -12,43 +12,74 @@ import (
|
|||||||
// self-contained sections because the system linker only performs fixup
|
// self-contained sections because the system linker only performs fixup
|
||||||
// relocations, not assembly.
|
// 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
|
// 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 {
|
func dwarfAbbrevTable() []byte {
|
||||||
var b []byte
|
var b []byte
|
||||||
// Abbrev 1: DW_TAG_compile_unit
|
// Abbrev 1: DW_TAG_compile_unit.
|
||||||
b = append(b, 1) // abbreviation code
|
b = append(b, 1) // abbreviation code
|
||||||
b = append(b, 0x11) // DW_TAG_compile_unit
|
b = appendUleb(b, dwTagCompUnit) // DW_TAG_compile_unit
|
||||||
b = append(b, 1) // DW_CHILDREN_yes
|
b = append(b, 1) // DW_CHILDREN_yes
|
||||||
b = appendUleb(b, 0x1b) // DW_AT_low_pc
|
b = appendUleb(b, dwAtLowPC) // DW_AT_low_pc
|
||||||
b = appendUleb(b, 0x01) // DW_FORM_addr
|
b = appendUleb(b, dwFormAddr) // DW_FORM_addr
|
||||||
b = appendUleb(b, 0x29) // DW_AT_high_pc
|
b = appendUleb(b, dwAtHighPC) // DW_AT_high_pc
|
||||||
b = appendUleb(b, 0x07) // DW_FORM_data8
|
b = appendUleb(b, dwFormData8) // DW_FORM_data8
|
||||||
b = appendUleb(b, 0x10) // DW_AT_stmt_list
|
b = appendUleb(b, dwAtStmtList) // DW_AT_stmt_list
|
||||||
b = appendUleb(b, 0x25) // DW_FORM_sec_offset
|
b = appendUleb(b, dwFormSecOff) // DW_FORM_sec_offset (4 bytes here)
|
||||||
b = appendUleb(b, 0x01) // DW_AT_name
|
b = appendUleb(b, dwAtName) // DW_AT_name
|
||||||
b = appendUleb(b, 0x08) // DW_FORM_string
|
b = appendUleb(b, dwFormString) // DW_FORM_string
|
||||||
b = appendUleb(b, 0) // end of attributes
|
b = appendUleb(b, 0) // end of attributes: attr 0
|
||||||
|
b = appendUleb(b, 0) // ... paired with form 0
|
||||||
|
|
||||||
// Abbrev 2: DW_TAG_subprogram
|
// Abbrev 2: DW_TAG_subprogram.
|
||||||
b = append(b, 2) // abbreviation code
|
b = append(b, 2) // abbreviation code
|
||||||
b = append(b, 0x2e) // DW_TAG_subprogram
|
b = appendUleb(b, dwTagSubprog) // DW_TAG_subprogram
|
||||||
b = append(b, 0) // DW_CHILDREN_no
|
b = append(b, 0) // DW_CHILDREN_no
|
||||||
b = appendUleb(b, 0x03) // DW_AT_name
|
b = appendUleb(b, dwAtName) // DW_AT_name
|
||||||
b = appendUleb(b, 0x08) // DW_FORM_string
|
b = appendUleb(b, dwFormString) // DW_FORM_string
|
||||||
b = appendUleb(b, 0x11) // DW_AT_low_pc
|
b = appendUleb(b, dwAtLowPC) // DW_AT_low_pc
|
||||||
b = appendUleb(b, 0x01) // DW_FORM_addr
|
b = appendUleb(b, dwFormAddr) // DW_FORM_addr
|
||||||
b = appendUleb(b, 0x29) // DW_AT_high_pc
|
b = appendUleb(b, dwAtHighPC) // DW_AT_high_pc
|
||||||
b = appendUleb(b, 0x07) // DW_FORM_data8
|
b = appendUleb(b, dwFormData8) // DW_FORM_data8
|
||||||
b = appendUleb(b, 0x3f) // DW_AT_frame_base
|
b = appendUleb(b, dwAtFrameBase) // DW_AT_frame_base
|
||||||
b = appendUleb(b, 0x18) // DW_FORM_exprloc
|
b = appendUleb(b, dwFormExprloc) // DW_FORM_exprloc
|
||||||
b = appendUleb(b, 0x3b) // DW_AT_decl_file
|
b = appendUleb(b, dwAtDeclFile) // DW_AT_decl_file
|
||||||
b = appendUleb(b, 0x0b) // DW_FORM_data1
|
b = appendUleb(b, dwFormData1) // DW_FORM_data1
|
||||||
b = appendUleb(b, 0x37) // DW_AT_decl_line
|
b = appendUleb(b, dwAtDeclLine) // DW_AT_decl_line
|
||||||
b = appendUleb(b, 0x0b) // DW_FORM_data1
|
b = appendUleb(b, dwFormData1) // DW_FORM_data1
|
||||||
b = appendUleb(b, 0x63) // DW_AT_external
|
b = appendUleb(b, dwAtExternal) // DW_AT_external
|
||||||
b = appendUleb(b, 0x0b) // DW_FORM_flag
|
b = appendUleb(b, 0x0c) // DW_FORM_flag (one byte, 0 or 1)
|
||||||
b = appendUleb(b, 0) // end of attributes
|
b = appendUleb(b, 0) // end of attributes: attr 0
|
||||||
|
b = appendUleb(b, 0) // ... paired with form 0
|
||||||
|
|
||||||
// End of table.
|
// End of table.
|
||||||
b = append(b, 0)
|
b = append(b, 0)
|
||||||
@@ -68,6 +99,9 @@ type dwarfSections struct {
|
|||||||
infoRelocs []dwarfReloc
|
infoRelocs []dwarfReloc
|
||||||
// Relocations for .debug_line: (offset, symbol name, addend).
|
// Relocations for .debug_line: (offset, symbol name, addend).
|
||||||
lineRelocs []dwarfReloc
|
lineRelocs []dwarfReloc
|
||||||
|
// Relocations for .debug_frame: (offset, symbol name, addend), one per
|
||||||
|
// FDE initial_location.
|
||||||
|
frameRelocs []dwarfReloc
|
||||||
}
|
}
|
||||||
|
|
||||||
type dwarfReloc struct {
|
type dwarfReloc struct {
|
||||||
@@ -76,8 +110,9 @@ type dwarfReloc struct {
|
|||||||
addend int64
|
addend int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitDWARF generates complete DWARF5 sections for the image.
|
// emitDWARF generates complete DWARF5 sections for the image. cfi carries
|
||||||
func emitDWARF(img *Image, srcFile string) *dwarfSections {
|
// the architecture's .debug_frame register conventions.
|
||||||
|
func emitDWARF(img *Image, srcFile string, cfi cfiArch) *dwarfSections {
|
||||||
ds := &dwarfSections{}
|
ds := &dwarfSections{}
|
||||||
ds.debugAbbrev = dwarfAbbrevTable()
|
ds.debugAbbrev = dwarfAbbrevTable()
|
||||||
|
|
||||||
@@ -86,19 +121,21 @@ func emitDWARF(img *Image, srcFile string) *dwarfSections {
|
|||||||
lineStr.add(srcFile)
|
lineStr.add(srcFile)
|
||||||
ds.debugLineStr = lineStr.bytes()
|
ds.debugLineStr = lineStr.bytes()
|
||||||
|
|
||||||
// Build .debug_line.
|
// Build .debug_line; the file table references the source name through
|
||||||
ds.debugLine = dwarfBuildLineSection(img, ds)
|
// its offset in .debug_line_str.
|
||||||
|
ds.debugLine = dwarfBuildLineSection(img, uint32(lineStr.at(srcFile)), ds)
|
||||||
|
|
||||||
// Build .debug_info.
|
// Build .debug_info.
|
||||||
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
|
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
|
||||||
|
|
||||||
// Build .debug_frame.
|
// Build .debug_frame.
|
||||||
ds.debugFrame = dwarfBuildFrameSection(img)
|
ds.debugFrame = dwarfBuildFrameSection(img, cfi, ds)
|
||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
// dwarfBuildLineSection builds a complete .debug_line section.
|
// dwarfBuildLineSection builds a complete .debug_line section. srcStrOff is
|
||||||
func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte {
|
// the source file name's offset in .debug_line_str.
|
||||||
|
func dwarfBuildLineSection(img *Image, srcStrOff uint32, ds *dwarfSections) []byte {
|
||||||
var b []byte
|
var b []byte
|
||||||
le := binary.LittleEndian
|
le := binary.LittleEndian
|
||||||
|
|
||||||
@@ -120,15 +157,25 @@ func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte {
|
|||||||
// Standard opcode lengths (opcode 1..opcode_base-1).
|
// Standard opcode lengths (opcode 1..opcode_base-1).
|
||||||
b = append(b, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0)
|
b = append(b, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0)
|
||||||
|
|
||||||
// Directory table (DWARF5 format).
|
// Directory table (DWARF5 §6.2.4): entry format descriptors followed by
|
||||||
b = append(b, 0) // one directory entry (index 0 = empty)
|
// the entries. One directory, the compilation directory, whose path is
|
||||||
// File table.
|
// the empty string at .debug_line_str offset 0.
|
||||||
b = appendUleb(b, 1) // file count
|
b = append(b, 1) // directory_entry_format_count
|
||||||
// File 1: name index into .debug_line_str, dir index, time, size.
|
b = appendUleb(b, dwLnctPath) // DW_LNCT_path
|
||||||
b = appendUleb(b, 0) // name (index 0 in line_str)
|
b = appendUleb(b, dwFormLineStrp) // DW_FORM_line_strp
|
||||||
b = appendUleb(b, 0) // directory index
|
b = appendUleb(b, 1) // directories_count
|
||||||
b = appendUleb(b, 0) // last modification time
|
b = le.AppendUint32(b, 0) // .debug_line_str offset of ""
|
||||||
b = appendUleb(b, 0) // file size
|
|
||||||
|
// 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)
|
headerEnd := len(b)
|
||||||
|
|
||||||
@@ -176,8 +223,11 @@ func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte {
|
|||||||
|
|
||||||
// Patch unit_length.
|
// Patch unit_length.
|
||||||
le.PutUint32(b[headerStart:], uint32(len(b)-headerStart-4))
|
le.PutUint32(b[headerStart:], uint32(len(b)-headerStart-4))
|
||||||
// Patch header_length.
|
// Patch header_length. In the v5 header it follows the one-byte
|
||||||
le.PutUint32(b[headerStart+6:], uint32(headerEnd-headerStart-10))
|
// 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
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,14 +245,16 @@ func dwarfBuildInfoSection(img *Image, srcFile string, ds *dwarfSections) []byte
|
|||||||
|
|
||||||
// DW_TAG_compile_unit (abbrev 1).
|
// DW_TAG_compile_unit (abbrev 1).
|
||||||
b = append(b, 1) // abbreviation code
|
b = append(b, 1) // abbreviation code
|
||||||
// DW_AT_low_pc: address of .text start.
|
// DW_AT_low_pc: address of .text start. A data-only image has no
|
||||||
infoRelocBase := len(b)
|
// 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
|
b = le.AppendUint64(b, 0) // placeholder
|
||||||
ds.infoRelocs = append(ds.infoRelocs, dwarfReloc{
|
if len(img.Funcs) > 0 {
|
||||||
off: uint64(infoRelocBase),
|
ds.infoRelocs = append(ds.infoRelocs, dwarfReloc{
|
||||||
name: img.Funcs[0].Name,
|
off: uint64(len(b) - 8),
|
||||||
addend: 0,
|
name: img.Funcs[0].Name,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
// DW_AT_high_pc: size of .text.
|
// DW_AT_high_pc: size of .text.
|
||||||
b = le.AppendUint64(b, uint64(len(img.Code)))
|
b = le.AppendUint64(b, uint64(len(img.Code)))
|
||||||
// DW_AT_stmt_list: offset into .debug_line (0).
|
// 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))
|
b = le.AppendUint64(b, uint64(fn.Size))
|
||||||
// DW_AT_frame_base: DW_OP_call_frame_cfa.
|
// DW_AT_frame_base: DW_OP_call_frame_cfa.
|
||||||
b = append(b, 1, 0x9c)
|
b = append(b, 1, 0x9c)
|
||||||
// DW_AT_decl_file: file index 1.
|
// DW_AT_decl_file: the single file-table entry, index 0 (v5 indexes
|
||||||
b = append(b, 1)
|
// files from 0).
|
||||||
|
b = append(b, 0)
|
||||||
// DW_AT_decl_line.
|
// DW_AT_decl_line.
|
||||||
b = append(b, uint8(fn.Line))
|
b = append(b, uint8(fn.Line))
|
||||||
// DW_AT_external.
|
// DW_AT_external.
|
||||||
@@ -253,32 +306,61 @@ func appendUleb(b []byte, v uint64) []byte {
|
|||||||
return binary.AppendUvarint(b, v)
|
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 {
|
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
|
// dwarfBuildFrameSection builds a .debug_frame section with CFI for stack
|
||||||
// unwinding. It emits one CIE and one FDE per function, encoding the
|
// unwinding. It emits one CIE and one FDE per function, encoding the
|
||||||
// CFA (Canonical Frame Address) rule changes at each stack-adjustment
|
// CFA (Canonical Frame Address) rule changes at each stack-adjustment
|
||||||
// boundary recorded in FuncLayout.Spadj.
|
// boundary recorded in FuncLayout.Spadj.
|
||||||
func dwarfBuildFrameSection(img *Image) []byte {
|
func dwarfBuildFrameSection(img *Image, cfi cfiArch, ds *dwarfSections) []byte {
|
||||||
var b []byte
|
var b []byte
|
||||||
le := binary.LittleEndian
|
le := binary.LittleEndian
|
||||||
|
|
||||||
// CIE (Common Information Entry).
|
// CIE (Common Information Entry).
|
||||||
cieStart := len(b)
|
cieStart := len(b)
|
||||||
b = append(b, 0, 0, 0, 0) // length (placeholder)
|
b = append(b, 0, 0, 0, 0) // length (placeholder)
|
||||||
b = le.AppendUint32(b, 0xFFFFFFFF) // CIE marker
|
b = le.AppendUint32(b, 0xFFFFFFFF) // CIE marker
|
||||||
b = append(b, 3) // version (DWARF3, widely supported)
|
b = append(b, 3) // version (DWARF3, widely supported)
|
||||||
b = append(b, 0) // augmentation (empty)
|
b = append(b, 0) // augmentation (empty)
|
||||||
b = appendUleb(b, 1) // code alignment
|
b = appendUleb(b, 1) // code alignment
|
||||||
b = appendSleb(b, -8) // data alignment (-8 for 64-bit)
|
b = appendSleb(b, -8) // data alignment (-8 for 64-bit)
|
||||||
b = appendUleb(b, 16) // return address register (LR on arm64, RIP on amd64)
|
b = appendUleb(b, uint64(cfi.raReg)) // return address register
|
||||||
// Initial CFA rule: DW_CFA_def_cfa (SP, 0)
|
// Initial CFA rule: DW_CFA_def_cfa (SP, 0)
|
||||||
b = append(b, 0x0c) // DW_CFA_def_cfa
|
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, uint64(cfi.cfaReg)) // the architecture's stack pointer
|
||||||
b = appendUleb(b, 0) // offset: 0
|
b = appendUleb(b, 0) // offset: 0
|
||||||
b = append(b, 0) // DW_CFA_nop (padding)
|
b = append(b, 0) // DW_CFA_nop (padding)
|
||||||
// Patch CIE length.
|
// Patch CIE length.
|
||||||
le.PutUint32(b[cieStart:], uint32(len(b)-cieStart-4))
|
le.PutUint32(b[cieStart:], uint32(len(b)-cieStart-4))
|
||||||
|
|
||||||
@@ -287,7 +369,12 @@ func dwarfBuildFrameSection(img *Image) []byte {
|
|||||||
fdeStart := len(b)
|
fdeStart := len(b)
|
||||||
b = append(b, 0, 0, 0, 0) // length (placeholder)
|
b = append(b, 0, 0, 0, 0) // length (placeholder)
|
||||||
b = le.AppendUint32(b, uint32(cieStart)) // CIE pointer (offset from start)
|
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))
|
b = le.AppendUint64(b, uint64(fn.Offset))
|
||||||
// Address range: function size.
|
// Address range: function size.
|
||||||
b = le.AppendUint64(b, uint64(fn.Size))
|
b = le.AppendUint64(b, uint64(fn.Size))
|
||||||
|
|||||||
+84
-24
@@ -3,6 +3,17 @@
|
|||||||
|
|
||||||
package asm
|
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
|
// dwarfELFSections holds the laid-out DWARF sections ready for inclusion
|
||||||
// in an ELF file.
|
// in an ELF file.
|
||||||
type dwarfELFSections struct {
|
type dwarfELFSections struct {
|
||||||
@@ -11,15 +22,23 @@ type dwarfELFSections struct {
|
|||||||
lineOff, lineSize int
|
lineOff, lineSize int
|
||||||
lineStrOff, lineStrSize int
|
lineStrOff, lineStrSize int
|
||||||
frameOff, frameSize int
|
frameOff, frameSize int
|
||||||
// Relocations for .debug_info address references.
|
// .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
|
infoRelocs []elfDwarfReloc
|
||||||
// Relocations for .debug_line address references.
|
// Relocations for .debug_line address references, section-relative.
|
||||||
lineRelocs []elfDwarfReloc
|
lineRelocs []elfDwarfReloc
|
||||||
|
// Relocations for .debug_frame FDE initial locations, section-relative.
|
||||||
|
frameRelocs []elfDwarfReloc
|
||||||
}
|
}
|
||||||
|
|
||||||
type elfDwarfReloc struct {
|
type elfDwarfReloc struct {
|
||||||
off uint64
|
off uint64 // offset within the target section
|
||||||
sym int // symbol index in .symtab
|
sym int // symbol index in .symtab
|
||||||
addend int64
|
addend int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +48,9 @@ type elfDwarfReloc struct {
|
|||||||
//
|
//
|
||||||
// symIdx maps function names to their .symtab indices (needed for relocations
|
// symIdx maps function names to their .symtab indices (needed for relocations
|
||||||
// against .text symbols). The map uses objectName format (pkg.name); the
|
// against .text symbols). The map uses objectName format (pkg.name); the
|
||||||
// DWARF code uses bare function names, so we build a reverse lookup.
|
// DWARF code uses bare function names, so we build a reverse lookup. cfi
|
||||||
func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[string]int, align func(int)) *dwarfELFSections {
|
// 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.
|
// Build a lookup from bare function name to symbol index.
|
||||||
nameToIdx := make(map[string]int, len(symIdx))
|
nameToIdx := make(map[string]int, len(symIdx))
|
||||||
for name, idx := range symIdx {
|
for name, idx := range symIdx {
|
||||||
@@ -45,7 +65,7 @@ func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[str
|
|||||||
}
|
}
|
||||||
nameToIdx[name] = idx
|
nameToIdx[name] = idx
|
||||||
}
|
}
|
||||||
ds := emitDWARF(img, srcFile)
|
ds := emitDWARF(img, srcFile, cfi)
|
||||||
if ds == nil || len(ds.debugAbbrev) == 0 {
|
if ds == nil || len(ds.debugAbbrev) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -68,15 +88,11 @@ func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[str
|
|||||||
align(1)
|
align(1)
|
||||||
result.lineOff = len(*out)
|
result.lineOff = len(*out)
|
||||||
result.lineSize = len(ds.debugLine)
|
result.lineSize = len(ds.debugLine)
|
||||||
lineBase := len(*out)
|
|
||||||
*out = append(*out, ds.debugLine...)
|
*out = append(*out, ds.debugLine...)
|
||||||
|
|
||||||
// Patch .debug_line relocations: replace placeholder addresses with
|
|
||||||
// actual .text offsets via symbol lookup.
|
|
||||||
for _, dr := range ds.lineRelocs {
|
for _, dr := range ds.lineRelocs {
|
||||||
if idx, ok := nameToIdx[dr.name]; ok {
|
if idx, ok := nameToIdx[dr.name]; ok {
|
||||||
result.lineRelocs = append(result.lineRelocs, elfDwarfReloc{
|
result.lineRelocs = append(result.lineRelocs, elfDwarfReloc{
|
||||||
off: uint64(lineBase) + dr.off,
|
off: dr.off,
|
||||||
sym: idx,
|
sym: idx,
|
||||||
addend: dr.addend,
|
addend: dr.addend,
|
||||||
})
|
})
|
||||||
@@ -87,33 +103,77 @@ func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[str
|
|||||||
align(1)
|
align(1)
|
||||||
result.infoOff = len(*out)
|
result.infoOff = len(*out)
|
||||||
result.infoSize = len(ds.debugInfo)
|
result.infoSize = len(ds.debugInfo)
|
||||||
infoBase := len(*out)
|
|
||||||
*out = append(*out, ds.debugInfo...)
|
*out = append(*out, ds.debugInfo...)
|
||||||
|
|
||||||
// .debug_frame
|
|
||||||
if len(ds.debugFrame) > 0 {
|
|
||||||
align(1)
|
|
||||||
result.frameOff = len(*out)
|
|
||||||
result.frameSize = len(ds.debugFrame)
|
|
||||||
*out = append(*out, ds.debugFrame...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch .debug_info relocations.
|
|
||||||
for _, dr := range ds.infoRelocs {
|
for _, dr := range ds.infoRelocs {
|
||||||
if idx, ok := nameToIdx[dr.name]; ok {
|
if idx, ok := nameToIdx[dr.name]; ok {
|
||||||
result.infoRelocs = append(result.infoRelocs, elfDwarfReloc{
|
result.infoRelocs = append(result.infoRelocs, elfDwarfReloc{
|
||||||
off: uint64(infoBase) + dr.off,
|
off: dr.off,
|
||||||
sym: idx,
|
sym: idx,
|
||||||
addend: dr.addend,
|
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
|
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.
|
// dwarfSectionNames returns the DWARF section names for the string table.
|
||||||
var dwarfSectionNames = []string{
|
var dwarfSectionNames = []string{
|
||||||
".debug_abbrev", ".debug_info", ".debug_line", ".debug_line_str",
|
".debug_abbrev", ".debug_info", ".debug_line", ".debug_line_str",
|
||||||
".debug_frame", ".rela.debug_info", ".rela.debug_line",
|
".debug_frame", ".rela.debug_info", ".rela.debug_line",
|
||||||
|
".rela.debug_frame",
|
||||||
}
|
}
|
||||||
|
|||||||
+303
-12
@@ -4,11 +4,313 @@
|
|||||||
package asm
|
package asm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ulebIter reads ULEB128 values, the .debug_abbrev and line-header
|
||||||
|
// encoding.
|
||||||
|
type ulebIter struct {
|
||||||
|
b []byte
|
||||||
|
i int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ulebIter) uleb(t *testing.T) uint64 {
|
||||||
|
t.Helper()
|
||||||
|
v, n := binary.Uvarint(r.b[r.i:])
|
||||||
|
if n <= 0 {
|
||||||
|
t.Fatalf("bad ULEB at %d", r.i)
|
||||||
|
}
|
||||||
|
r.i += n
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ulebIter) byteAt(t *testing.T) byte {
|
||||||
|
t.Helper()
|
||||||
|
if r.i >= len(r.b) {
|
||||||
|
t.Fatalf("read past end at %d", r.i)
|
||||||
|
}
|
||||||
|
c := r.b[r.i]
|
||||||
|
r.i++
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ulebIter) uint32At(t *testing.T) uint32 {
|
||||||
|
t.Helper()
|
||||||
|
v := binary.LittleEndian.Uint32(r.b[r.i:])
|
||||||
|
r.i += 4
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleb reads a signed LEB128, the DWARF encoding (sign-extended two's
|
||||||
|
// complement, not Go's zigzag varint).
|
||||||
|
func (r *ulebIter) sleb(t *testing.T) int64 {
|
||||||
|
t.Helper()
|
||||||
|
var v int64
|
||||||
|
var shift uint
|
||||||
|
for {
|
||||||
|
c := r.byteAt(t)
|
||||||
|
v |= int64(c&0x7f) << shift
|
||||||
|
shift += 7
|
||||||
|
if c&0x80 == 0 {
|
||||||
|
if c&0x40 != 0 {
|
||||||
|
v |= -1 << shift
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dwarfAttr is one attribute/form pair of an abbreviation.
|
||||||
|
type dwarfAttr struct{ attr, form uint64 }
|
||||||
|
|
||||||
|
// dwarfAbbrev is one parsed abbreviation declaration.
|
||||||
|
type dwarfAbbrev struct {
|
||||||
|
code uint64
|
||||||
|
tag uint64
|
||||||
|
children bool
|
||||||
|
attrs []dwarfAttr
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAbbrevs walks a .debug_abbrev table: abbreviation code, tag,
|
||||||
|
// children flag, then attr/form ULEB pairs terminated by a double zero.
|
||||||
|
func parseAbbrevs(t *testing.T, b []byte) map[uint64]dwarfAbbrev {
|
||||||
|
t.Helper()
|
||||||
|
out := map[uint64]dwarfAbbrev{}
|
||||||
|
r := &ulebIter{b: b}
|
||||||
|
for {
|
||||||
|
code := r.uleb(t)
|
||||||
|
if code == 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
ab := dwarfAbbrev{code: code, tag: r.uleb(t)}
|
||||||
|
ab.children = r.byteAt(t) == 1
|
||||||
|
for {
|
||||||
|
attr := r.uleb(t)
|
||||||
|
form := r.uleb(t)
|
||||||
|
if attr == 0 && form == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if attr == 0 || form == 0 {
|
||||||
|
t.Fatalf("abbrev %d: half-terminated attr/form pair (%d, %d)", code, attr, form)
|
||||||
|
}
|
||||||
|
ab.attrs = append(ab.attrs, dwarfAttr{attr, form})
|
||||||
|
}
|
||||||
|
out[code] = ab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eqAttrs(t *testing.T, ab dwarfAbbrev, want []dwarfAttr) {
|
||||||
|
t.Helper()
|
||||||
|
if len(ab.attrs) != len(want) {
|
||||||
|
t.Fatalf("abbrev %d attrs = %v, want %v", ab.code, ab.attrs, want)
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ab.attrs[i] != w {
|
||||||
|
t.Fatalf("abbrev %d attr %d = (%#x, %#x), want (%#x, %#x)", ab.code, i, ab.attrs[i].attr, ab.attrs[i].form, w.attr, w.form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDwarfAbbrevTable walks the abbreviation table as a consumer does and
|
||||||
|
// checks the attribute/form sets against the constants the toolchain uses
|
||||||
|
// (cmd/internal/dwarf/dwarf_defs.go). A wrong constant here renames an
|
||||||
|
// attribute (0x1b is comp_dir, not low_pc; 0x29 and 0x37 are bounds and
|
||||||
|
// count) and a wrong form desynchronises the DIE parse: 0x25 is strx1, one
|
||||||
|
// byte, where the writer emits four for a section offset.
|
||||||
|
func TestDwarfAbbrevTable(t *testing.T) {
|
||||||
|
abbrev := dwarfAbbrevTable()
|
||||||
|
if len(abbrev) == 0 {
|
||||||
|
t.Fatal("empty abbrev table")
|
||||||
|
}
|
||||||
|
// Must end with a zero byte (end of table).
|
||||||
|
if abbrev[len(abbrev)-1] != 0 {
|
||||||
|
t.Fatalf("abbrev table last byte = %d, want 0", abbrev[len(abbrev)-1])
|
||||||
|
}
|
||||||
|
abs := parseAbbrevs(t, abbrev)
|
||||||
|
if len(abs) != 2 {
|
||||||
|
t.Fatalf("abbreviations = %d, want 2", len(abs))
|
||||||
|
}
|
||||||
|
cu, ok := abs[1]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing abbreviation 1 (compile unit)")
|
||||||
|
}
|
||||||
|
if cu.tag != dwTagCompUnit || !cu.children {
|
||||||
|
t.Errorf("abbrev 1: tag %#x children %v, want compile unit with children", cu.tag, cu.children)
|
||||||
|
}
|
||||||
|
eqAttrs(t, cu, []dwarfAttr{
|
||||||
|
{dwAtLowPC, dwFormAddr},
|
||||||
|
{dwAtHighPC, dwFormData8},
|
||||||
|
{dwAtStmtList, dwFormSecOff},
|
||||||
|
{dwAtName, dwFormString},
|
||||||
|
})
|
||||||
|
sp, ok := abs[2]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing abbreviation 2 (subprogram)")
|
||||||
|
}
|
||||||
|
if sp.tag != dwTagSubprog || sp.children {
|
||||||
|
t.Errorf("abbrev 2: tag %#x children %v, want subprogram without children", sp.tag, sp.children)
|
||||||
|
}
|
||||||
|
eqAttrs(t, sp, []dwarfAttr{
|
||||||
|
{dwAtName, dwFormString},
|
||||||
|
{dwAtLowPC, dwFormAddr},
|
||||||
|
{dwAtHighPC, dwFormData8},
|
||||||
|
{dwAtFrameBase, dwFormExprloc},
|
||||||
|
{dwAtDeclFile, dwFormData1},
|
||||||
|
{dwAtDeclLine, dwFormData1},
|
||||||
|
{dwAtExternal, 0x0c}, // DW_FORM_flag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDwarfLineHeaderV5 parses the .debug_line header under DWARF5 rules:
|
||||||
|
// the directory and file tables are format-descriptor lists, not the
|
||||||
|
// DWARF2-4 shape of null-terminated strings, and the file entry references
|
||||||
|
// the source name through .debug_line_str.
|
||||||
|
func TestDwarfLineHeaderV5(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOVQ a+0(FP), AX
|
||||||
|
MOVQ b+8(FP), BX
|
||||||
|
ADDQ BX, AX
|
||||||
|
MOVQ AX, ret+16(FP)
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("test_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("assemble: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds := emitDWARF(img, "test_amd64.s", cfiAMD64)
|
||||||
|
r := &ulebIter{b: ds.debugLine}
|
||||||
|
r.uint32At(t) // unit_length
|
||||||
|
if v := binary.LittleEndian.Uint16(ds.debugLine[4:]); v != 5 {
|
||||||
|
t.Fatalf("version = %d, want 5", v)
|
||||||
|
}
|
||||||
|
r.i = 6
|
||||||
|
r.byteAt(t) // address_size
|
||||||
|
r.byteAt(t) // segment_selector_size
|
||||||
|
r.uint32At(t) // header_length
|
||||||
|
r.byteAt(t) // minimum_instruction_length
|
||||||
|
r.byteAt(t) // maximum_ops_per_instruction
|
||||||
|
r.byteAt(t) // default_is_stmt
|
||||||
|
r.byteAt(t) // line_base
|
||||||
|
r.byteAt(t) // line_range
|
||||||
|
opcodeBase := r.byteAt(t)
|
||||||
|
for range int(opcodeBase) - 1 {
|
||||||
|
r.byteAt(t) // standard opcode lengths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory table (DWARF5 §6.2.4).
|
||||||
|
if n := r.byteAt(t); n != 1 {
|
||||||
|
t.Fatalf("directory_entry_format_count = %d, want 1", n)
|
||||||
|
}
|
||||||
|
if lnct := r.uleb(t); lnct != dwLnctPath {
|
||||||
|
t.Errorf("directory content type = %#x, want DW_LNCT_path", lnct)
|
||||||
|
}
|
||||||
|
if form := r.uleb(t); form != dwFormLineStrp {
|
||||||
|
t.Errorf("directory form = %#x, want DW_FORM_line_strp", form)
|
||||||
|
}
|
||||||
|
if n := r.uleb(t); n != 1 {
|
||||||
|
t.Fatalf("directories_count = %d, want 1", n)
|
||||||
|
}
|
||||||
|
if off := r.uint32At(t); off != 0 {
|
||||||
|
t.Errorf("compilation directory line_strp = %d, want 0 (the empty string)", off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File table (DWARF5 §6.2.5).
|
||||||
|
if n := r.byteAt(t); n != 2 {
|
||||||
|
t.Fatalf("file_name_entry_format_count = %d, want 2", n)
|
||||||
|
}
|
||||||
|
if lnct := r.uleb(t); lnct != dwLnctPath {
|
||||||
|
t.Errorf("file content type = %#x, want DW_LNCT_path", lnct)
|
||||||
|
}
|
||||||
|
if form := r.uleb(t); form != dwFormLineStrp {
|
||||||
|
t.Errorf("file path form = %#x, want DW_FORM_line_strp", form)
|
||||||
|
}
|
||||||
|
if lnct := r.uleb(t); lnct != dwLnctDirIndex {
|
||||||
|
t.Errorf("file content type = %#x, want DW_LNCT_directory_index", lnct)
|
||||||
|
}
|
||||||
|
if form := r.uleb(t); form != dwFormUdata {
|
||||||
|
t.Errorf("file dir-index form = %#x, want DW_FORM_udata", form)
|
||||||
|
}
|
||||||
|
if n := r.uleb(t); n != 1 {
|
||||||
|
t.Fatalf("file_names_count = %d, want 1", n)
|
||||||
|
}
|
||||||
|
strOff := r.uint32At(t)
|
||||||
|
if dirIdx := r.uleb(t); dirIdx != 0 {
|
||||||
|
t.Errorf("file directory index = %d, want 0", dirIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The file entry's line_strp must resolve to the source name.
|
||||||
|
end := int(strOff) + len("test_amd64.s")
|
||||||
|
if int(strOff) >= len(ds.debugLineStr) || !bytes.Equal(ds.debugLineStr[strOff:end], []byte("test_amd64.s")) {
|
||||||
|
t.Errorf("file entry line_strp %d does not name the source: %q", strOff, ds.debugLineStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fixed header fields: address_size 8 and a header_length that
|
||||||
|
// points just past the file table (the patch site is offset 8 in the
|
||||||
|
// v5 header, and the field counts from its own end).
|
||||||
|
if ds.debugLine[6] != 8 || ds.debugLine[7] != 0 {
|
||||||
|
t.Errorf("address_size/segment_selector = %d/%d, want 8/0", ds.debugLine[6], ds.debugLine[7])
|
||||||
|
}
|
||||||
|
if hl := binary.LittleEndian.Uint32(ds.debugLine[8:]); hl != uint32(r.i-12) {
|
||||||
|
t.Errorf("header_length = %d, want %d (the byte after the file table is %d)", hl, r.i-12, r.i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDwarfFrameCIEArch checks the shared CIE carries each architecture's
|
||||||
|
// stack-pointer and return-address registers: the values the Go linker
|
||||||
|
// writes (cmd/link/internal/<arch>/l.go dwarfRegSP/dwarfRegLR).
|
||||||
|
func TestDwarfFrameCIEArch(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
cfi cfiArch
|
||||||
|
}{
|
||||||
|
{"amd64", cfiAMD64},
|
||||||
|
{"arm64", cfiARM64},
|
||||||
|
{"riscv64", cfiRISCV64},
|
||||||
|
{"loong64", cfiLOONG64},
|
||||||
|
} {
|
||||||
|
frame := dwarfBuildFrameSection(&Image{}, tc.cfi, &dwarfSections{})
|
||||||
|
r := &ulebIter{b: frame}
|
||||||
|
r.uint32At(t) // length
|
||||||
|
if cid := r.uint32At(t); cid != 0xFFFFFFFF {
|
||||||
|
t.Errorf("%s: CIE id = %#x, want 0xffffffff", tc.name, cid)
|
||||||
|
}
|
||||||
|
if v := r.byteAt(t); v != 3 {
|
||||||
|
t.Errorf("%s: CIE version = %d, want 3", tc.name, v)
|
||||||
|
}
|
||||||
|
if aug := r.byteAt(t); aug != 0 {
|
||||||
|
t.Errorf("%s: CIE augmentation = %d, want 0", tc.name, aug)
|
||||||
|
}
|
||||||
|
if ca := r.uleb(t); ca != 1 {
|
||||||
|
t.Errorf("%s: code alignment = %d, want 1", tc.name, ca)
|
||||||
|
}
|
||||||
|
if da := r.sleb(t); da != -8 {
|
||||||
|
t.Errorf("%s: data alignment = %d, want -8 (signed LEB128, not zigzag)", tc.name, da)
|
||||||
|
}
|
||||||
|
if ra := r.uleb(t); ra != uint64(tc.cfi.raReg) {
|
||||||
|
t.Errorf("%s: return-address register = %d, want %d", tc.name, ra, tc.cfi.raReg)
|
||||||
|
}
|
||||||
|
if op := r.byteAt(t); op != 0x0c {
|
||||||
|
t.Errorf("%s: expected DW_CFA_def_cfa, got opcode %#x", tc.name, op)
|
||||||
|
}
|
||||||
|
if cfa := r.uleb(t); cfa != uint64(tc.cfi.cfaReg) {
|
||||||
|
t.Errorf("%s: CFA register = %d, want %d", tc.name, cfa, tc.cfi.cfaReg)
|
||||||
|
}
|
||||||
|
if off := r.uleb(t); off != 0 {
|
||||||
|
t.Errorf("%s: CFA offset = %d, want 0", tc.name, off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEmitDWARF(t *testing.T) {
|
func TestEmitDWARF(t *testing.T) {
|
||||||
src := `#include "textflag.h"
|
src := `#include "textflag.h"
|
||||||
TEXT ·add(SB), NOSPLIT, $0-24
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
@@ -27,7 +329,7 @@ TEXT ·add(SB), NOSPLIT, $0-24
|
|||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ds := emitDWARF(img, "test_amd64.s")
|
ds := emitDWARF(img, "test_amd64.s", cfiAMD64)
|
||||||
|
|
||||||
// .debug_abbrev must not be empty and must start with abbrev code 1.
|
// .debug_abbrev must not be empty and must start with abbrev code 1.
|
||||||
if len(ds.debugAbbrev) == 0 {
|
if len(ds.debugAbbrev) == 0 {
|
||||||
@@ -68,14 +370,3 @@ TEXT ·add(SB), NOSPLIT, $0-24
|
|||||||
t.Fatal("no .debug_info relocations")
|
t.Fatal("no .debug_info relocations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDwarfAbbrevTable(t *testing.T) {
|
|
||||||
abbrev := dwarfAbbrevTable()
|
|
||||||
if len(abbrev) == 0 {
|
|
||||||
t.Fatal("empty abbrev table")
|
|
||||||
}
|
|
||||||
// Must end with a zero byte (end of table).
|
|
||||||
if abbrev[len(abbrev)-1] != 0 {
|
|
||||||
t.Fatalf("abbrev table last byte = %d, want 0", abbrev[len(abbrev)-1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+442
@@ -12,6 +12,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -211,6 +212,75 @@ func TestELFObject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestELFObjectTLSGuardReloc checks that a non-NOSPLIT function's stack
|
||||||
|
// guard carries an R_X86_64_TPOFF32 relocation against the null symbol in
|
||||||
|
// .rela.text. The serialisation must honour the record's type field: a
|
||||||
|
// hardcoded R_X86_64_PC32 mislinks the TLS load as an ordinary
|
||||||
|
// PC-relative reference.
|
||||||
|
func TestELFObjectTLSGuardReloc(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("g_amd64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·grow(SB), $0
|
||||||
|
CALL ·other(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·other(SB), NOSPLIT, $0
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
var haveTLS bool
|
||||||
|
for _, fn := range img.Funcs {
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
if r.Kind == RelTLSLE {
|
||||||
|
haveTLS = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !haveTLS {
|
||||||
|
t.Fatal("test source produced no RelTLSLE relocation")
|
||||||
|
}
|
||||||
|
obj, err := img.ELFObject()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFObject: %v", err)
|
||||||
|
}
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
relaSec := ef.Section(".rela.text")
|
||||||
|
if relaSec == nil {
|
||||||
|
t.Fatal("missing .rela.text")
|
||||||
|
}
|
||||||
|
raw, err := relaSec.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for i := 0; i+24 <= len(raw); i += 24 {
|
||||||
|
e := raw[i:]
|
||||||
|
info := binary.LittleEndian.Uint64(e[8:])
|
||||||
|
typ := info & 0xffffffff
|
||||||
|
sym := int(info >> 32)
|
||||||
|
if typ == uint64(elf.R_X86_64_TPOFF32) {
|
||||||
|
found = true
|
||||||
|
if sym != 0 {
|
||||||
|
t.Errorf("TPOFF32 relocation against symbol %d, want 0 (the null symbol)", sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("no R_X86_64_TPOFF32 relocation in .rela.text (%d bytes)", len(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestELFObjectNoRelocations checks a file with no static-symbol references
|
// TestELFObjectNoRelocations checks a file with no static-symbol references
|
||||||
// emits a valid object without a .rela.text section.
|
// emits a valid object without a .rela.text section.
|
||||||
func TestELFObjectNoRelocations(t *testing.T) {
|
func TestELFObjectNoRelocations(t *testing.T) {
|
||||||
@@ -253,6 +323,238 @@ TEXT ·nop(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// elfSectionHeaderCount returns the e_shnum the ELF header declares.
|
||||||
|
func elfSectionHeaderCount(t *testing.T, obj []byte) int {
|
||||||
|
t.Helper()
|
||||||
|
return int(binary.LittleEndian.Uint16(obj[60:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkELFSectionAccounting verifies the number of section headers the
|
||||||
|
// writer physically laid out equals e_shnum: every DWARF section written
|
||||||
|
// after .shstrtab must be counted, or the last ones (always .debug_frame)
|
||||||
|
// are invisible to every consumer, debug/elf included.
|
||||||
|
func checkELFSectionAccounting(t *testing.T, obj []byte) {
|
||||||
|
t.Helper()
|
||||||
|
shoff := int(binary.LittleEndian.Uint64(obj[40:]))
|
||||||
|
shentsize := int(binary.LittleEndian.Uint16(obj[58:]))
|
||||||
|
shnum := elfSectionHeaderCount(t, obj)
|
||||||
|
if shentsize != 64 {
|
||||||
|
t.Fatalf("e_shentsize = %d, want 64", shentsize)
|
||||||
|
}
|
||||||
|
if (len(obj)-shoff)%shentsize != 0 {
|
||||||
|
t.Fatalf("section header table is not a whole number of entries: shoff=%d len=%d", shoff, len(obj))
|
||||||
|
}
|
||||||
|
if present := (len(obj) - shoff) / shentsize; present != shnum {
|
||||||
|
t.Errorf("e_shnum = %d but %d section headers are laid out", shnum, present)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestELFDWARFSectionAccounting runs the header accounting check over all
|
||||||
|
// four architecture emitters, and additionally checks the .debug_frame
|
||||||
|
// section is visible (its data aligned as its header declares).
|
||||||
|
func TestELFDWARFSectionAccounting(t *testing.T) {
|
||||||
|
parse := func(name, src string) *ast.File {
|
||||||
|
f, errs := parser.Parse(name, src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse %s: %v", name, errs)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
img *Image
|
||||||
|
emit func(*Image) ([]byte, error)
|
||||||
|
}{
|
||||||
|
{"amd64", elfTestImage(t), (*Image).ELFObject},
|
||||||
|
{"arm64", mustImage(t, func() (*Image, error) {
|
||||||
|
return AssembleFileARM64(parse("k_arm64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOVD a+0(FP), R4
|
||||||
|
MOVD b+8(FP), R5
|
||||||
|
ADD R5, R4, R4
|
||||||
|
MOVD R4, ret+16(FP)
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
}), (*Image).ELFAARCH64Object},
|
||||||
|
{"riscv64", mustImage(t, func() (*Image, error) {
|
||||||
|
return AssembleFileRISCV(parse("k_riscv64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·sb(SB), NOSPLIT, $0-0
|
||||||
|
MOV $answer<>(SB), X10
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL answer<>(SB), RODATA, $8
|
||||||
|
DATA answer<>+0(SB)/8, $42
|
||||||
|
`))
|
||||||
|
}), (*Image).ELFRISCVObject},
|
||||||
|
{"loong64", mustImage(t, func() (*Image, error) {
|
||||||
|
return AssembleFileLOONG64(parse("k_loong64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOVV a+0(FP), R4
|
||||||
|
MOVV b+8(FP), R5
|
||||||
|
ADDV R5, R4, R4
|
||||||
|
MOVV R4, ret+16(FP)
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
}), (*Image).ELFLOONG64Object},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
obj, err := tc.emit(tc.img)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: emit: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
checkELFSectionAccounting(t, obj)
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: parse emitted object: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
frame := ef.Section(".debug_frame")
|
||||||
|
if frame == nil {
|
||||||
|
t.Errorf("%s: .debug_frame invisible to debug/elf (e_shnum too small?)", tc.name)
|
||||||
|
ef.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.Offset%8 != 0 || frame.Addralign != 8 {
|
||||||
|
t.Errorf("%s: .debug_frame offset %d align %d, want offset%%8==0 align 8", tc.name, frame.Offset, frame.Addralign)
|
||||||
|
}
|
||||||
|
ef.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustImage(t *testing.T, f func() (*Image, error)) *Image {
|
||||||
|
t.Helper()
|
||||||
|
img, err := f()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestELFDWARFRelocations checks the .rela.debug_info and .rela.debug_line
|
||||||
|
// sections exist and carry absolute 64-bit relocations against the
|
||||||
|
// function symbols, with r_offsets inside their target sections.
|
||||||
|
func TestELFDWARFRelocations(t *testing.T) {
|
||||||
|
img := elfTestImage(t)
|
||||||
|
obj, err := img.ELFObject()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFObject: %v", err)
|
||||||
|
}
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
// The DWARF must record the assembled file's path (threaded through
|
||||||
|
// Image.SourcePath), not a placeholder name.
|
||||||
|
info, err := ef.Section(".debug_info").Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if img.SourcePath != "t_amd64.s" || !bytes.Contains(info, []byte(img.SourcePath)) {
|
||||||
|
t.Errorf("DWARF compilation unit does not name the source %q", img.SourcePath)
|
||||||
|
}
|
||||||
|
for _, tc := range []struct {
|
||||||
|
rela string
|
||||||
|
target string
|
||||||
|
want uint32
|
||||||
|
}{
|
||||||
|
{".rela.debug_info", ".debug_info", rX8664Abs64},
|
||||||
|
{".rela.debug_line", ".debug_line", rX8664Abs64},
|
||||||
|
{".rela.debug_frame", ".debug_frame", rX8664Abs64},
|
||||||
|
} {
|
||||||
|
rs := ef.Section(tc.rela)
|
||||||
|
if rs == nil {
|
||||||
|
t.Fatalf("missing %s", tc.rela)
|
||||||
|
}
|
||||||
|
if rs.Type != elf.SHT_RELA {
|
||||||
|
t.Errorf("%s: type %v, want SHT_RELA", tc.rela, rs.Type)
|
||||||
|
}
|
||||||
|
target := ef.Section(tc.target)
|
||||||
|
if target == nil {
|
||||||
|
t.Fatalf("missing %s", tc.target)
|
||||||
|
}
|
||||||
|
if rs.Link == 0 || ef.Sections[rs.Info] != target {
|
||||||
|
t.Errorf("%s: link %d info %d, want the symtab and %s", tc.rela, rs.Link, rs.Info, tc.target)
|
||||||
|
}
|
||||||
|
b, err := rs.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// .debug_line has one address per function; .debug_info adds the
|
||||||
|
// compile unit's own low_pc.
|
||||||
|
want := len(img.Funcs)
|
||||||
|
if tc.target == ".debug_info" {
|
||||||
|
want++
|
||||||
|
}
|
||||||
|
if len(b)/24 != want {
|
||||||
|
t.Errorf("%s: %d entries, want %d", tc.rela, len(b)/24, want)
|
||||||
|
}
|
||||||
|
for i := 0; i+24 <= len(b); i += 24 {
|
||||||
|
r_offset := binary.LittleEndian.Uint64(b[i:])
|
||||||
|
info := binary.LittleEndian.Uint64(b[i+8:])
|
||||||
|
typ := uint32(info)
|
||||||
|
sym := int(info >> 32)
|
||||||
|
if typ != tc.want {
|
||||||
|
t.Errorf("%s entry %d: type %d, want R_X86_64_64 (%d)", tc.rela, i/24, typ, tc.want)
|
||||||
|
}
|
||||||
|
if r_offset >= uint64(target.Size) {
|
||||||
|
t.Errorf("%s entry %d: r_offset %d outside %s (%d bytes)", tc.rela, i/24, r_offset, tc.target, target.Size)
|
||||||
|
}
|
||||||
|
if sym == 0 {
|
||||||
|
t.Errorf("%s entry %d: against the null symbol", tc.rela, i/24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestELFDataOnly checks a source with GLOBL data and no TEXT emits a valid
|
||||||
|
// ELF object: the DWARF compilation unit of a code-less image has no
|
||||||
|
// function to relocate against and must not reach for one.
|
||||||
|
func TestELFDataOnly(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("d0_amd64.s", `
|
||||||
|
GLOBL table<>(SB), RODATA, $8
|
||||||
|
DATA table<>+0(SB)/8, $12345
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFObject()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFObject: %v", err)
|
||||||
|
}
|
||||||
|
checkELFSectionAccounting(t, obj)
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, s := range syms {
|
||||||
|
if s.Name == "table" && s.Size == 8 {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("data symbol table missing: %v", syms)
|
||||||
|
}
|
||||||
|
if ef.Section(".rela.debug_info") != nil || ef.Section(".rela.debug_line") != nil {
|
||||||
|
t.Error("data-only image must not emit DWARF address relocations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestELFLinkAndRun is the end-to-end check: assemble the test functions,
|
// TestELFLinkAndRun is the end-to-end check: assemble the test functions,
|
||||||
// link the emitted object with a C driver that defines the external symbol,
|
// link the emitted object with a C driver that defines the external symbol,
|
||||||
// and run the result. Skipped when no C compiler is available.
|
// and run the result. Skipped when no C compiler is available.
|
||||||
@@ -307,4 +609,144 @@ int main(void) {
|
|||||||
if got := string(run); got != "42 42 7\n" {
|
if got := string(run); got != "42 42 7\n" {
|
||||||
t.Errorf("output %q, want \"42 42 7\\n\"", got)
|
t.Errorf("output %q, want \"42 42 7\\n\"", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The DWARF addresses must have resolved at link time: the .debug_info
|
||||||
|
// placeholders were carried by .rela.debug_info, so every subprogram's
|
||||||
|
// low_pc must now equal its linked symbol address.
|
||||||
|
bin, err := os.ReadFile(appPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
lef, err := elf.NewFile(bytes.NewReader(bin))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse linked binary: %v", err)
|
||||||
|
}
|
||||||
|
defer lef.Close()
|
||||||
|
syms, err := lef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
addrByName := map[string]uint64{}
|
||||||
|
for _, s := range syms {
|
||||||
|
if elf.ST_TYPE(s.Info) == elf.STT_FUNC && s.Value != 0 {
|
||||||
|
addrByName[s.Name] = s.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lowPCs := dwarfSubprogramLowPCs(t, lef)
|
||||||
|
if len(lowPCs) == 0 {
|
||||||
|
t.Fatal("no subprogram DW_AT_low_pc parsed from the linked binary")
|
||||||
|
}
|
||||||
|
for name, pc := range lowPCs {
|
||||||
|
addr, ok := addrByName[name]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("subprogram %q not in the linked symbol table", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pc != addr {
|
||||||
|
t.Errorf("subprogram %q: DW_AT_low_pc = %#x, linked address %#x (DWARF relocation unresolved)", name, pc, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dwarfSubprogramLowPCs walks the linked binary's .debug_info with its own
|
||||||
|
// .debug_abbrev and returns each DW_TAG_subprogram's DW_AT_low_pc by name.
|
||||||
|
func dwarfSubprogramLowPCs(t *testing.T, ef *elf.File) map[string]uint64 {
|
||||||
|
t.Helper()
|
||||||
|
abbrevSec := ef.Section(".debug_abbrev")
|
||||||
|
infoSec := ef.Section(".debug_info")
|
||||||
|
if abbrevSec == nil || infoSec == nil {
|
||||||
|
t.Fatal("linked binary lacks .debug_abbrev or .debug_info")
|
||||||
|
}
|
||||||
|
abbrev, err := abbrevSec.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
info, err := infoSec.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
abs := parseAbbrevs(t, abbrev)
|
||||||
|
le := binary.LittleEndian
|
||||||
|
out := map[string]uint64{}
|
||||||
|
r := &ulebIter{b: info}
|
||||||
|
r.uint32At(t) // unit_length
|
||||||
|
if v := le.Uint16(info[4:]); v != 5 {
|
||||||
|
t.Fatalf(".debug_info version %d, want 5", v)
|
||||||
|
}
|
||||||
|
r.i = 6
|
||||||
|
r.byteAt(t) // unit_type
|
||||||
|
r.byteAt(t) // address_size
|
||||||
|
r.uint32At(t) // debug_abbrev_offset
|
||||||
|
var name string
|
||||||
|
var lowPC uint64
|
||||||
|
for r.i < len(r.b) {
|
||||||
|
code := r.uleb(t)
|
||||||
|
if code == 0 {
|
||||||
|
continue // end of the CU's children
|
||||||
|
}
|
||||||
|
ab, ok := abs[code]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown abbreviation code %d", code)
|
||||||
|
}
|
||||||
|
name, lowPC = "", 0
|
||||||
|
for _, a := range ab.attrs {
|
||||||
|
switch a.attr {
|
||||||
|
case dwAtName:
|
||||||
|
readFormKeep(t, r, a.form, &name, nil)
|
||||||
|
case dwAtLowPC:
|
||||||
|
readFormKeep(t, r, a.form, nil, &lowPC)
|
||||||
|
default:
|
||||||
|
readFormSkip(t, r, a.form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ab.tag == dwTagSubprog && name != "" {
|
||||||
|
out[name] = lowPC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFormKeep reads one DIE attribute value, keeping a string or an
|
||||||
|
// address into the pointer it was given (nil keeps nothing).
|
||||||
|
func readFormKeep(t *testing.T, r *ulebIter, form uint64, name *string, addr *uint64) {
|
||||||
|
t.Helper()
|
||||||
|
switch form {
|
||||||
|
case dwFormString:
|
||||||
|
end := r.i
|
||||||
|
for end < len(r.b) && r.b[end] != 0 {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if name != nil {
|
||||||
|
*name = string(r.b[r.i:end])
|
||||||
|
}
|
||||||
|
r.i = end + 1
|
||||||
|
case dwFormAddr:
|
||||||
|
if addr != nil {
|
||||||
|
*addr = binary.LittleEndian.Uint64(r.b[r.i:])
|
||||||
|
}
|
||||||
|
r.i += 8
|
||||||
|
default:
|
||||||
|
readFormSkip(t, r, form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFormSkip(t *testing.T, r *ulebIter, form uint64) {
|
||||||
|
t.Helper()
|
||||||
|
switch form {
|
||||||
|
case dwFormString:
|
||||||
|
for r.i < len(r.b) && r.b[r.i] != 0 {
|
||||||
|
r.i++
|
||||||
|
}
|
||||||
|
r.i++
|
||||||
|
case dwFormAddr, dwFormData8:
|
||||||
|
r.i += 8
|
||||||
|
case dwFormSecOff:
|
||||||
|
r.i += 4
|
||||||
|
case dwFormExprloc:
|
||||||
|
r.i += int(r.uleb(t))
|
||||||
|
case dwFormData1, 0x0c:
|
||||||
|
r.i++
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported form %#x", form)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-22
@@ -81,10 +81,15 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build relocations. Each SB reference is an ADRP pair:
|
// Build relocations. Each SB reference is an ADRP pair:
|
||||||
// ADRP Rd, 0 → R_AARCH64_ADR_PREL_PG_HI21
|
// ADRP Rd, 0 → R_AARCH64_ADR_PREL_PG_HI21 at the ADRP
|
||||||
// ADD → R_AARCH64_ADD_ABS_LO12_NC
|
// ADD → R_AARCH64_ADD_ABS_LO12_NC at the ADD word
|
||||||
// LDR/STR X → R_AARCH64_LDST64_ABS_LO12_NC
|
// LDR/STR X → R_AARCH64_LDST64_ABS_LO12_NC at the LDR/STR word
|
||||||
// BL → R_AARCH64_CALL26
|
// BL → R_AARCH64_CALL26
|
||||||
|
// cmd/link's own conversion emits the HI21 at sectoff and the LO12 at
|
||||||
|
// sectoff+4 (cmd/link/internal/arm64/asm.go), so the ADD or load word
|
||||||
|
// carries the page-offset relocation, never a second HI21. The
|
||||||
|
// assembler records two RelArm64Addr relocs per ADRP+ADD pair (one per
|
||||||
|
// word), so the second of the pair is consumed here.
|
||||||
// Addends stay raw: ADR_PREL_PG_HI21 and the ABS_LO12_NC forms resolve
|
// Addends stay raw: ADR_PREL_PG_HI21 and the ABS_LO12_NC forms resolve
|
||||||
// against S+A, and CALL26 branches take the branch instruction's own
|
// against S+A, and CALL26 branches take the branch instruction's own
|
||||||
// place as the PC-relative base, so subtracting the field width (the
|
// place as the PC-relative base, so subtracting the field width (the
|
||||||
@@ -97,28 +102,34 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
var relas []elfRela
|
var relas []elfRela
|
||||||
for _, fn := range img.Funcs {
|
for _, fn := range img.Funcs {
|
||||||
for _, r := range fn.Relocs {
|
for i := 0; i < len(fn.Relocs); i++ {
|
||||||
|
r := fn.Relocs[i]
|
||||||
idx, ok := symIdx[r.Name]
|
idx, ok := symIdx[r.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
||||||
}
|
}
|
||||||
var typ uint32
|
switch r.Kind {
|
||||||
switch {
|
case RelArm64Branch:
|
||||||
case r.Kind == RelArm64Branch:
|
relas = append(relas, elfRela{
|
||||||
typ = rArm64Call26
|
off: uint64(fn.Offset + r.Off), typ: rArm64Call26, sym: idx, addend: r.Addend,
|
||||||
case r.Kind == RelArm64LDST64 && r.Off%4 == 4:
|
})
|
||||||
typ = rArm64Ldst64Lo12NC
|
case RelArm64Addr:
|
||||||
case r.Kind == RelArm64Addr && r.Off%4 == 4:
|
// ADRP+ADD: the pair's second reloc (at Off+4) is the
|
||||||
typ = rArm64AddAbsLo12NC
|
// assembler's twin of the same pair; skip it.
|
||||||
|
relas = append(relas,
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend},
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64AddAbsLo12NC, sym: idx, addend: r.Addend},
|
||||||
|
)
|
||||||
|
i++
|
||||||
|
case RelArm64LDST64:
|
||||||
|
// ADRP+LDR/STR: one assembler reloc covers the pair.
|
||||||
|
relas = append(relas,
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend},
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64Ldst64Lo12NC, sym: idx, addend: r.Addend},
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
typ = rArm64PrelPgHi21
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
relas = append(relas, elfRela{
|
|
||||||
off: uint64(fn.Offset + r.Off),
|
|
||||||
typ: typ,
|
|
||||||
sym: idx,
|
|
||||||
addend: r.Addend,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,15 +204,32 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
shstrOff := len(out)
|
shstrOff := len(out)
|
||||||
out = append(out, stSections.bytes()...)
|
out = append(out, stSections.bytes()...)
|
||||||
|
|
||||||
// DWARF debug sections.
|
// DWARF debug sections; the address placeholders they leave are carried
|
||||||
|
// as .rela.debug_info/.rela.debug_line entries the system linker applies.
|
||||||
dwAlign := func(n int) {
|
dwAlign := func(n int) {
|
||||||
for len(out)%n != 0 {
|
for len(out)%n != 0 {
|
||||||
out = append(out, 0)
|
out = append(out, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign)
|
dw := appendDWARFSections(&out, img, dwarfSourceName(img), symIdx, dwAlign, cfiARM64)
|
||||||
|
dwarfStart := 0 // section index of .debug_abbrev, set when DWARF is present
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
nSections += 4
|
// Five DWARF sections: .debug_abbrev, .debug_info, .debug_line,
|
||||||
|
// .debug_line_str and .debug_frame (the CIE is unconditional, so
|
||||||
|
// the frame section is always present), plus the relocation
|
||||||
|
// sections below when they carry entries.
|
||||||
|
dwarfStart = nSections
|
||||||
|
nSections += 5
|
||||||
|
appendDWARFRelas(&out, dw, rAARCH64Abs64, dwAlign)
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
align(8)
|
align(8)
|
||||||
@@ -230,13 +258,37 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) {
|
|||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
// secIdx is a running section index: each putSh below emits the
|
||||||
|
// next header, and the sh_info of a .rela section names the index
|
||||||
|
// of the section it relocates.
|
||||||
|
secIdx := dwarfStart
|
||||||
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
||||||
|
secInfoIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_info", shtRela, 0, dw.infoRelaOff, 24*dw.infoRelaCount, secSymtab, secInfoIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
||||||
|
secLineIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_line", shtRela, 0, dw.lineRelaOff, 24*dw.lineRelaCount, secSymtab, secLineIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
if dw.frameSize > 0 {
|
if dw.frameSize > 0 {
|
||||||
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
||||||
|
secFrameIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_frame", shtRela, 0, dw.frameRelaOff, 24*dw.frameRelaCount, secSymtab, secFrameIdx, 8, 24)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+58
-1
@@ -6,6 +6,7 @@ package asm
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
@@ -28,6 +29,7 @@ TEXT ·add(SB), NOSPLIT, $0-24
|
|||||||
|
|
||||||
TEXT ·getanswer(SB), NOSPLIT, $0-8
|
TEXT ·getanswer(SB), NOSPLIT, $0-8
|
||||||
MOVD answer<>(SB), R4
|
MOVD answer<>(SB), R4
|
||||||
|
MOVD $answer<>(SB), R5
|
||||||
MOVD R4, ret+0(FP)
|
MOVD R4, ret+0(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
@@ -102,8 +104,63 @@ DATA answer<>+0(SB)/8, $42
|
|||||||
// Check that .rela.text exists (getanswer has SB reference).
|
// Check that .rela.text exists (getanswer has SB reference).
|
||||||
relaText := ef.Section(".rela.text")
|
relaText := ef.Section(".rela.text")
|
||||||
if relaText == nil {
|
if relaText == nil {
|
||||||
t.Error("missing .rela.text section")
|
t.Fatal("missing .rela.text section")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The SB references of getanswer form two ADRP pairs: the load
|
||||||
|
// (MOVD answer<>(SB), R4) is ADRP+LDR carrying HI21 at the ADRP and
|
||||||
|
// LDST64_ABS_LO12_NC at the LDR word, and the address-of
|
||||||
|
// (MOVD $answer<>(SB), R5) is ADRP+ADD carrying HI21 and
|
||||||
|
// ADD_ABS_LO12_NC. cmd/link's own conversion emits exactly this
|
||||||
|
// sectoff / sectoff+4 pairing; a second HI21 at the ADD or LDR word
|
||||||
|
// corrupts the pair.
|
||||||
|
raw, err := relaText.Data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(raw)%24 != 0 || len(raw)/24 != 4 {
|
||||||
|
t.Fatalf(".rela.text has %d bytes, want four 24-byte entries", len(raw))
|
||||||
|
}
|
||||||
|
wantRela := []struct {
|
||||||
|
typ elf.R_AARCH64
|
||||||
|
off uint64 // relative to the getanswer function start
|
||||||
|
}{
|
||||||
|
{elf.R_AARCH64_ADR_PREL_PG_HI21, 0},
|
||||||
|
{elf.R_AARCH64_LDST64_ABS_LO12_NC, 4},
|
||||||
|
{elf.R_AARCH64_ADR_PREL_PG_HI21, 8},
|
||||||
|
{elf.R_AARCH64_ADD_ABS_LO12_NC, 12},
|
||||||
|
}
|
||||||
|
getanswer := byNameElf(t, ef, "getanswer")
|
||||||
|
for i, w := range wantRela {
|
||||||
|
e := raw[i*24 : (i+1)*24]
|
||||||
|
off := binary.LittleEndian.Uint64(e[0:])
|
||||||
|
info := binary.LittleEndian.Uint64(e[8:])
|
||||||
|
typ := elf.R_AARCH64(info & 0xffffffff)
|
||||||
|
sym := int(info >> 32)
|
||||||
|
if typ != w.typ || off != getanswer.Value+w.off {
|
||||||
|
t.Errorf("reloc %d: type %v off %d, want %v at %d", i, typ, off, w.typ, getanswer.Value+w.off)
|
||||||
|
}
|
||||||
|
if sym != 3 { // NULL, .text, .data, then the first local: answer
|
||||||
|
t.Errorf("reloc %d: symbol index %d, want 3 (answer)", i, sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// byNameElf returns the symbol table entry for name from the raw .symtab,
|
||||||
|
// which carries every entry including the null and section symbols in order.
|
||||||
|
func byNameElf(t *testing.T, ef *elf.File, name string) elf.Symbol {
|
||||||
|
t.Helper()
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("symbols: %v", err)
|
||||||
|
}
|
||||||
|
for _, s := range syms {
|
||||||
|
if s.Name == name {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("symbol %q not found", name)
|
||||||
|
return elf.Symbol{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestELFAARCH64ObjectNoRelocations checks the ELF output when there are no
|
// TestELFAARCH64ObjectNoRelocations checks the ELF output when there are no
|
||||||
|
|||||||
+49
-3
@@ -13,6 +13,12 @@ import (
|
|||||||
const (
|
const (
|
||||||
emLOONGARCH = 258 // EM_LOONGARCH
|
emLOONGARCH = 258 // EM_LOONGARCH
|
||||||
|
|
||||||
|
// EF_LOONGARCH_ABI_DOUBLE_FLOAT | EF_LOONGARCH_OBJABI_V1: the flags the
|
||||||
|
// Go toolchain writes (cmd/link/internal/ld/elf.go: Flags = 0x43 for
|
||||||
|
// Loong64). System linkers refuse to merge ET_REL objects whose float
|
||||||
|
// ABI differs, so 0 (soft-float) would make the object unlinkable.
|
||||||
|
efLarchAbiDoubleObjV1 = 0x43
|
||||||
|
|
||||||
// LoongArch relocation types (the ELF psABI).
|
// LoongArch relocation types (the ELF psABI).
|
||||||
rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i)
|
rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i)
|
||||||
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
||||||
@@ -187,9 +193,25 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
out = append(out, 0)
|
out = append(out, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign)
|
dw := appendDWARFSections(&out, img, dwarfSourceName(img), symIdx, dwAlign, cfiLOONG64)
|
||||||
|
dwarfStart := 0 // section index of .debug_abbrev, set when DWARF is present
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
nSections += 4
|
// Five DWARF sections: .debug_abbrev, .debug_info, .debug_line,
|
||||||
|
// .debug_line_str and .debug_frame (the CIE is unconditional, so
|
||||||
|
// the frame section is always present), plus the relocation
|
||||||
|
// sections below when they carry entries.
|
||||||
|
dwarfStart = nSections
|
||||||
|
nSections += 5
|
||||||
|
appendDWARFRelas(&out, dw, rLarchAbs64, dwAlign)
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
align(8)
|
align(8)
|
||||||
@@ -218,13 +240,37 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
// secIdx is a running section index: each putSh below emits the
|
||||||
|
// next header, and the sh_info of a .rela section names the index
|
||||||
|
// of the section it relocates.
|
||||||
|
secIdx := dwarfStart
|
||||||
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
||||||
|
secInfoIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_info", shtRela, 0, dw.infoRelaOff, 24*dw.infoRelaCount, secSymtab, secInfoIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
||||||
|
secLineIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_line", shtRela, 0, dw.lineRelaOff, 24*dw.lineRelaCount, secSymtab, secLineIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
if dw.frameSize > 0 {
|
if dw.frameSize > 0 {
|
||||||
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
||||||
|
secFrameIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_frame", shtRela, 0, dw.frameRelaOff, 24*dw.frameRelaCount, secSymtab, secFrameIdx, 8, 24)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +283,7 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|||||||
le.PutUint64(hdr[24:], 0)
|
le.PutUint64(hdr[24:], 0)
|
||||||
le.PutUint64(hdr[32:], 0)
|
le.PutUint64(hdr[32:], 0)
|
||||||
le.PutUint64(hdr[40:], uint64(shoff))
|
le.PutUint64(hdr[40:], uint64(shoff))
|
||||||
le.PutUint32(hdr[48:], 0)
|
le.PutUint32(hdr[48:], efLarchAbiDoubleObjV1)
|
||||||
le.PutUint16(hdr[52:], 64)
|
le.PutUint16(hdr[52:], 64)
|
||||||
le.PutUint16(hdr[54:], 0)
|
le.PutUint16(hdr[54:], 0)
|
||||||
le.PutUint16(hdr[56:], 0)
|
le.PutUint16(hdr[56:], 0)
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ DATA answer<>+0(SB)/8, $42
|
|||||||
if ef.Type != elf.ET_REL || ef.Machine != elf.EM_LOONGARCH {
|
if ef.Type != elf.ET_REL || ef.Machine != elf.EM_LOONGARCH {
|
||||||
t.Errorf("type/machine = %v/%v, want ET_REL/EM_LOONGARCH", ef.Type, ef.Machine)
|
t.Errorf("type/machine = %v/%v, want ET_REL/EM_LOONGARCH", ef.Type, ef.Machine)
|
||||||
}
|
}
|
||||||
|
// The double-float ABI plus OBJABI_V1 flags the Go toolchain writes;
|
||||||
|
// system linkers refuse ABI-mismatched merges.
|
||||||
|
if flags := binary.LittleEndian.Uint32(obj[48:]); flags != efLarchAbiDoubleObjV1 {
|
||||||
|
t.Errorf("e_flags = %#x, want %#x (double-float, OBJABI_V1)", flags, efLarchAbiDoubleObjV1)
|
||||||
|
}
|
||||||
|
|
||||||
text := ef.Section(".text")
|
text := ef.Section(".text")
|
||||||
data := ef.Section(".data")
|
data := ef.Section(".data")
|
||||||
|
|||||||
+60
-11
@@ -13,8 +13,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
emRISCV = 243 // EM_RISCV
|
emRISCV = 243 // EM_RISCV
|
||||||
|
|
||||||
|
// EF_RISCV_FLOAT_ABI_DOUBLE: the double-precision float ABI the Go
|
||||||
|
// toolchain targets (cmd/link/internal/ld/elf.go writes Flags = 0x4 for
|
||||||
|
// RISCV64). System linkers refuse to merge ET_REL objects whose float
|
||||||
|
// ABI differs, so 0 (soft-float) would make the object unlinkable.
|
||||||
|
efRISCVFloatAbiDouble = 0x4
|
||||||
|
|
||||||
// RISC-V relocation types.
|
// RISC-V relocation types.
|
||||||
rRISCV32 = 1
|
|
||||||
rRISCVJAL = 17 // R_RISCV_JAL
|
rRISCVJAL = 17 // R_RISCV_JAL
|
||||||
rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20
|
rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20
|
||||||
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I
|
||||||
@@ -83,9 +88,14 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
// Build relocations. Each SB reference is an AUIPC + second-instruction
|
// Build relocations. Each SB reference is an AUIPC + second-instruction
|
||||||
// pair carrying a single relocation kind; the ELF writer expands it into
|
// pair carrying a single relocation kind; the ELF writer expands it into
|
||||||
// the R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_I/S pair the psABI expects.
|
// the R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_I/S pair the psABI expects.
|
||||||
// The HI20 carries the symbol addend; the LO12 addend is zero, matching
|
// The HI20 carries the symbol and its addend. The LO12's symbol must
|
||||||
// cmd/link's own ELF conversion (the LO12 resolves against the HI20's
|
// denote the AUIPC site the HI20 relocates (psABI §8.4.9: the pair is
|
||||||
// AUIPC location).
|
// resolved against the label of the AUIPC, not the target symbol;
|
||||||
|
// cmd/link generates one local text symbol per AUIPC for exactly this,
|
||||||
|
// cmd/link/internal/riscv64/asm.go). The .text section symbol with the
|
||||||
|
// AUIPC's section-relative offset as addend gives S + A = the AUIPC
|
||||||
|
// address, which is that label.
|
||||||
|
const secSymText = 1 // syms[1], the .text section symbol
|
||||||
type elfRela struct {
|
type elfRela struct {
|
||||||
off uint64
|
off uint64
|
||||||
typ uint32
|
typ uint32
|
||||||
@@ -99,21 +109,20 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
||||||
}
|
}
|
||||||
|
auipc := int64(fn.Offset + r.Off)
|
||||||
switch r.Kind {
|
switch r.Kind {
|
||||||
case RelRISCVPCRELIType:
|
case RelRISCVPCRELIType:
|
||||||
relas = append(relas,
|
relas = append(relas,
|
||||||
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVPCRELHI20, sym: idx, addend: r.Addend},
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVPCRELHI20, sym: idx, addend: r.Addend},
|
||||||
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVPCRELLO12I, sym: idx, addend: 0},
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVPCRELLO12I, sym: secSymText, addend: auipc},
|
||||||
)
|
)
|
||||||
case RelRISCVPCRELSType:
|
case RelRISCVPCRELSType:
|
||||||
relas = append(relas,
|
relas = append(relas,
|
||||||
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVPCRELHI20, sym: idx, addend: r.Addend},
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVPCRELHI20, sym: idx, addend: r.Addend},
|
||||||
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVPCRELLO12S, sym: idx, addend: 0},
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVPCRELLO12S, sym: secSymText, addend: auipc},
|
||||||
)
|
)
|
||||||
case RelRISCVJal:
|
case RelRISCVJal:
|
||||||
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVJAL, sym: idx, addend: r.Addend})
|
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVJAL, sym: idx, addend: r.Addend})
|
||||||
case RelPCRelAbs:
|
|
||||||
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCV32, sym: idx, addend: r.Addend})
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
@@ -196,9 +205,25 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
out = append(out, 0)
|
out = append(out, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign)
|
dw := appendDWARFSections(&out, img, dwarfSourceName(img), symIdx, dwAlign, cfiRISCV64)
|
||||||
|
dwarfStart := 0 // section index of .debug_abbrev, set when DWARF is present
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
nSections += 4
|
// Five DWARF sections: .debug_abbrev, .debug_info, .debug_line,
|
||||||
|
// .debug_line_str and .debug_frame (the CIE is unconditional, so
|
||||||
|
// the frame section is always present), plus the relocation
|
||||||
|
// sections below when they carry entries.
|
||||||
|
dwarfStart = nSections
|
||||||
|
nSections += 5
|
||||||
|
appendDWARFRelas(&out, dw, rRISCVAbs64, dwAlign)
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
nSections++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
align(8)
|
align(8)
|
||||||
@@ -227,13 +252,37 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
||||||
}
|
}
|
||||||
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
||||||
|
// DWARF section headers; their indices follow the write order.
|
||||||
if dw != nil {
|
if dw != nil {
|
||||||
|
// secIdx is a running section index: each putSh below emits the
|
||||||
|
// next header, and the sh_info of a .rela section names the index
|
||||||
|
// of the section it relocates.
|
||||||
|
secIdx := dwarfStart
|
||||||
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
||||||
|
secInfoIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.infoRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_info", shtRela, 0, dw.infoRelaOff, 24*dw.infoRelaCount, secSymtab, secInfoIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
||||||
|
secLineIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.lineRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_line", shtRela, 0, dw.lineRelaOff, 24*dw.lineRelaCount, secSymtab, secLineIdx, 8, 24)
|
||||||
|
secIdx++
|
||||||
|
}
|
||||||
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
||||||
|
secIdx++
|
||||||
if dw.frameSize > 0 {
|
if dw.frameSize > 0 {
|
||||||
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
||||||
|
secFrameIdx := secIdx
|
||||||
|
secIdx++
|
||||||
|
if dw.frameRelaCount > 0 {
|
||||||
|
putSh(".rela.debug_frame", shtRela, 0, dw.frameRelaOff, 24*dw.frameRelaCount, secSymtab, secFrameIdx, 8, 24)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +295,7 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
le.PutUint64(hdr[24:], 0)
|
le.PutUint64(hdr[24:], 0)
|
||||||
le.PutUint64(hdr[32:], 0)
|
le.PutUint64(hdr[32:], 0)
|
||||||
le.PutUint64(hdr[40:], uint64(shoff))
|
le.PutUint64(hdr[40:], uint64(shoff))
|
||||||
le.PutUint32(hdr[48:], 0)
|
le.PutUint32(hdr[48:], efRISCVFloatAbiDouble)
|
||||||
le.PutUint16(hdr[52:], 64)
|
le.PutUint16(hdr[52:], 64)
|
||||||
le.PutUint16(hdr[54:], 0)
|
le.PutUint16(hdr[54:], 0)
|
||||||
le.PutUint16(hdr[56:], 0)
|
le.PutUint16(hdr[56:], 0)
|
||||||
|
|||||||
+17
-10
@@ -25,6 +25,10 @@ type Image struct {
|
|||||||
Symbols map[string]int // static symbol → byte offset within the image
|
Symbols map[string]int // static symbol → byte offset within the image
|
||||||
DataSyms []DataSymbol // GLOBL symbols, in layout order
|
DataSyms []DataSymbol // GLOBL symbols, in layout order
|
||||||
Externals []string // referenced but undefined symbols, sorted
|
Externals []string // referenced but undefined symbols, sorted
|
||||||
|
// SourcePath is the assembled file's path, recorded in the DWARF
|
||||||
|
// sections in place of a placeholder name. Empty when the image was
|
||||||
|
// not built from a named file.
|
||||||
|
SourcePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FuncLayout describes one assembled function within an Image.
|
// FuncLayout describes one assembled function within an Image.
|
||||||
@@ -81,12 +85,9 @@ func (fl *FuncLayout) LineAt(offset int) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// RelocKind Reloc is one static-symbol reference within a function body: the disp32
|
// RelocKind discriminates the relocation a static-symbol reference needs;
|
||||||
// field at Off (function-relative) must reach the symbol plus Addend,
|
// the encoders record one per SB reference, and the object-file emitters map
|
||||||
// measured from After, the address just past the instruction. An External
|
// it to their format's relocation type.
|
||||||
// relocation names a symbol no GLOBL in the file defines; the object-file
|
|
||||||
// emitters carry it into the output's relocation table.
|
|
||||||
// RelocKind discriminates the type of relocation needed.
|
|
||||||
type RelocKind int
|
type RelocKind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -96,7 +97,6 @@ const (
|
|||||||
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
||||||
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
||||||
RelRISCVJal // R_RISCV_JAL (J-type call)
|
RelRISCVJal // R_RISCV_JAL (J-type call)
|
||||||
RelPCRelAbs // 32-bit absolute (R_RISCV_32)
|
|
||||||
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
||||||
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
||||||
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
||||||
@@ -106,6 +106,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Reloc struct {
|
type Reloc struct {
|
||||||
|
// Off is the function-relative offset of the field the linker patches
|
||||||
|
// and After the address just past the instruction, the base the
|
||||||
|
// assembler measures PC-relative displacements from. Name plus
|
||||||
|
// Addend select the target: the symbol plus the byte offset. An
|
||||||
|
// External relocation names a symbol no GLOBL in the file defines;
|
||||||
|
// the object-file emitters carry it into the output's relocation
|
||||||
|
// table.
|
||||||
Off int
|
Off int
|
||||||
After int
|
After int
|
||||||
Name string
|
Name string
|
||||||
@@ -150,7 +157,7 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
}
|
}
|
||||||
link := &linkInfo{symbols: known, allowExternal: true}
|
link := &linkInfo{symbols: known, allowExternal: true}
|
||||||
|
|
||||||
img := &Image{Symbols: map[string]int{}}
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||||
textOff := map[string]int{}
|
textOff := map[string]int{}
|
||||||
type asmFunc struct {
|
type asmFunc struct {
|
||||||
name string
|
name string
|
||||||
@@ -267,7 +274,7 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
img := &Image{Symbols: map[string]int{}}
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
t, ok := d.(*ast.Text)
|
t, ok := d.(*ast.Text)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -339,7 +346,7 @@ func AssembleFileLOONG64(f *ast.File) (*Image, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
img := &Image{Symbols: map[string]int{}}
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
t, ok := d.(*ast.Text)
|
t, ok := d.(*ast.Text)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user