2026-07-16 20:52:20 +02:00
|
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"debug/elf"
|
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"os/exec"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
2026-07-16 20:52:20 +02:00
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// The object-file tests share one source: two exported functions, one
|
|
|
|
|
|
// file-local constant reached through a relocation, and one external symbol
|
|
|
|
|
|
// the linker must resolve. The functions take their arguments in the System
|
|
|
|
|
|
// V registers (not the Go stack ABI) so a C driver can call them directly.
|
|
|
|
|
|
const elfTestSrc = `
|
|
|
|
|
|
#include "textflag.h"
|
|
|
|
|
|
|
|
|
|
|
|
TEXT ·addq(SB), NOSPLIT, $0
|
|
|
|
|
|
LEAQ (DI)(SI*1), AX
|
|
|
|
|
|
RET
|
|
|
|
|
|
|
|
|
|
|
|
TEXT ·getanswer(SB), NOSPLIT, $0
|
|
|
|
|
|
MOVQ answer<>(SB), AX
|
|
|
|
|
|
RET
|
|
|
|
|
|
|
|
|
|
|
|
TEXT ·useextern(SB), NOSPLIT, $0
|
|
|
|
|
|
MOVQ extvar(SB), AX
|
|
|
|
|
|
RET
|
|
|
|
|
|
|
|
|
|
|
|
GLOBL answer<>(SB), RODATA, $8
|
|
|
|
|
|
DATA answer<>+0(SB)/8, $42
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
func elfTestImage(t *testing.T) *Image {
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
f, errs := parser.Parse("t_amd64.s", elfTestSrc)
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
|
}
|
|
|
|
|
|
img, err := AssembleFile(f)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("AssembleFile: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return img
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestAssembleFileExternals checks that a reference to a symbol no GLOBL
|
2026-09-16 23:12:31 +02:00
|
|
|
|
// defines is recorded as an external relocation instead of failing; the
|
2026-07-16 20:52:20 +02:00
|
|
|
|
// raw image leaves the displacement zero, the object emitters carry it.
|
|
|
|
|
|
func TestAssembleFileExternals(t *testing.T) {
|
|
|
|
|
|
img := elfTestImage(t)
|
|
|
|
|
|
if len(img.Externals) != 1 || img.Externals[0] != "extvar" {
|
|
|
|
|
|
t.Fatalf("Externals = %v, want [extvar]", img.Externals)
|
|
|
|
|
|
}
|
|
|
|
|
|
var ext, local int
|
|
|
|
|
|
for _, fn := range img.Funcs {
|
|
|
|
|
|
for _, r := range fn.Relocs {
|
|
|
|
|
|
if r.External {
|
|
|
|
|
|
ext++
|
|
|
|
|
|
if r.Name != "extvar" {
|
|
|
|
|
|
t.Errorf("external reloc names %q, want extvar", r.Name)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
local++
|
|
|
|
|
|
if r.Name != "answer" {
|
|
|
|
|
|
t.Errorf("local reloc names %q, want answer", r.Name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ext != 1 || local != 1 {
|
|
|
|
|
|
t.Errorf("relocs = %d external, %d local; want 1 and 1", ext, local)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestELFObject checks the structure of the emitted ELF64 relocatable
|
|
|
|
|
|
// object: sections, the symbol table (bindings, types, values, sizes) and
|
|
|
|
|
|
// the .rela.text relocations, parsed back with debug/elf.
|
|
|
|
|
|
func TestELFObject(t *testing.T) {
|
|
|
|
|
|
img := elfTestImage(t)
|
|
|
|
|
|
obj, err := img.ELFObject()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("ELFObject: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
f, err := elf.NewFile(bytes.NewReader(obj))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("parse emitted object: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
|
|
if f.Type != elf.ET_REL || f.Machine != elf.EM_X86_64 {
|
|
|
|
|
|
t.Errorf("type/machine = %v/%v, want ET_REL/EM_X86_64", f.Type, f.Machine)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
text := f.Section(".text")
|
|
|
|
|
|
data := f.Section(".data")
|
|
|
|
|
|
if text == nil || data == nil {
|
|
|
|
|
|
t.Fatal("missing .text or .data section")
|
|
|
|
|
|
}
|
|
|
|
|
|
if text.Flags&elf.SHF_EXECINSTR == 0 || text.Flags&elf.SHF_ALLOC == 0 {
|
|
|
|
|
|
t.Errorf(".text flags = %v", text.Flags)
|
|
|
|
|
|
}
|
|
|
|
|
|
if data.Flags&elf.SHF_WRITE == 0 {
|
|
|
|
|
|
t.Errorf(".data flags = %v", data.Flags)
|
|
|
|
|
|
}
|
|
|
|
|
|
textData, err := text.Data()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if !bytes.Equal(textData, img.Code) {
|
|
|
|
|
|
t.Errorf(".text contents differ from the image code")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
syms, err := f.Symbols()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("symbols: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
byName := map[string]elf.Symbol{}
|
|
|
|
|
|
for _, s := range syms {
|
|
|
|
|
|
byName[s.Name] = s
|
|
|
|
|
|
}
|
|
|
|
|
|
wantSym := func(name string, bind elf.SymBind, typ elf.SymType, section elf.SectionIndex, size uint64) {
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
s, ok := byName[name]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
t.Errorf("symbol %q not found", name)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if elf.ST_BIND(s.Info) != bind || elf.ST_TYPE(s.Info) != typ {
|
|
|
|
|
|
t.Errorf("%s: bind/type = %v/%v, want %v/%v", name, elf.ST_BIND(s.Info), elf.ST_TYPE(s.Info), bind, typ)
|
|
|
|
|
|
}
|
|
|
|
|
|
if s.Section != section {
|
|
|
|
|
|
t.Errorf("%s: section = %v, want %v", name, s.Section, section)
|
|
|
|
|
|
}
|
|
|
|
|
|
if s.Size != size {
|
|
|
|
|
|
t.Errorf("%s: size = %d, want %d", name, s.Size, size)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// The emitted layout is fixed: 0 NULL, 1 .text, 2 .data.
|
|
|
|
|
|
if f.Sections[1].Name != ".text" || f.Sections[2].Name != ".data" {
|
|
|
|
|
|
t.Fatalf("section layout = %s, %s; want .text, .data", f.Sections[1].Name, f.Sections[2].Name)
|
|
|
|
|
|
}
|
|
|
|
|
|
textIdx := elf.SectionIndex(1)
|
|
|
|
|
|
dataIdx := elf.SectionIndex(2)
|
|
|
|
|
|
wantSym("addq", elf.STB_GLOBAL, elf.STT_FUNC, textIdx, 5)
|
|
|
|
|
|
wantSym("getanswer", elf.STB_GLOBAL, elf.STT_FUNC, textIdx, 8)
|
|
|
|
|
|
wantSym("useextern", elf.STB_GLOBAL, elf.STT_FUNC, textIdx, 8)
|
|
|
|
|
|
wantSym("answer", elf.STB_LOCAL, elf.STT_OBJECT, dataIdx, 8)
|
|
|
|
|
|
wantSym("extvar", elf.STB_GLOBAL, elf.STT_NOTYPE, elf.SHN_UNDEF, 0)
|
|
|
|
|
|
|
|
|
|
|
|
// Relocations: one for the file-local constant (resolving against the
|
|
|
|
|
|
// local data symbol) and one for the external (against the undefined
|
|
|
|
|
|
// global), both R_X86_64_PC32 with the −4 addend the PC-relative form
|
|
|
|
|
|
// needs. debug/elf does not surface rela entries, so read the section
|
|
|
|
|
|
// directly.
|
|
|
|
|
|
relaSec := f.Section(".rela.text")
|
|
|
|
|
|
if relaSec == nil {
|
|
|
|
|
|
t.Fatal("missing .rela.text")
|
|
|
|
|
|
}
|
|
|
|
|
|
raw, err := relaSec.Data()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(raw)%24 != 0 || len(raw)/24 != 2 {
|
|
|
|
|
|
t.Fatalf(".rela.text has %d bytes, want two 24-byte entries", len(raw))
|
|
|
|
|
|
}
|
|
|
|
|
|
// Symbol names straight from the raw tables: r_info carries an index
|
|
|
|
|
|
// into .symtab including the null entry, which debug/elf's Symbols()
|
|
|
|
|
|
// slice may not mirror.
|
|
|
|
|
|
symtabRaw, err := f.Section(".symtab").Data()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
strtabRaw, err := f.Section(".strtab").Data()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
symName := func(idx int) string {
|
|
|
|
|
|
stName := binary.LittleEndian.Uint32(symtabRaw[idx*24:])
|
|
|
|
|
|
end := bytes.IndexByte(strtabRaw[stName:], 0)
|
|
|
|
|
|
return string(strtabRaw[stName : int(stName)+end])
|
|
|
|
|
|
}
|
2026-08-29 15:40:31 +02:00
|
|
|
|
for i := range 2 {
|
2026-07-16 20:52:20 +02:00
|
|
|
|
e := raw[i*24 : (i+1)*24]
|
|
|
|
|
|
off := binary.LittleEndian.Uint64(e[0:])
|
|
|
|
|
|
info := binary.LittleEndian.Uint64(e[8:])
|
|
|
|
|
|
addend := int64(binary.LittleEndian.Uint64(e[16:]))
|
|
|
|
|
|
typ := info & 0xffffffff
|
|
|
|
|
|
sym := int(info >> 32)
|
|
|
|
|
|
if typ != uint64(elf.R_X86_64_PC32) {
|
|
|
|
|
|
t.Errorf("reloc %d: type %d, want R_X86_64_PC32", i, typ)
|
|
|
|
|
|
}
|
|
|
|
|
|
if addend != -4 {
|
|
|
|
|
|
t.Errorf("reloc %d: addend %d, want -4", i, addend)
|
|
|
|
|
|
}
|
|
|
|
|
|
if name := symName(sym); name != "answer" && name != "extvar" {
|
|
|
|
|
|
t.Errorf("reloc %d: symbol %q, want answer or extvar", i, name)
|
|
|
|
|
|
}
|
|
|
|
|
|
// The relocation offset lands on the disp32 field: the four bytes
|
|
|
|
|
|
// before a RET-terminated eight-byte MOVQ.
|
|
|
|
|
|
if off+4 > uint64(len(textData)) {
|
|
|
|
|
|
t.Errorf("reloc %d: offset %d outside .text", i, off)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
|
// 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))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
|
// TestELFObjectNoRelocations checks a file with no static-symbol references
|
|
|
|
|
|
// emits a valid object without a .rela.text section.
|
|
|
|
|
|
func TestELFObjectNoRelocations(t *testing.T) {
|
|
|
|
|
|
f, errs := parser.Parse("n_amd64.s", `
|
|
|
|
|
|
#include "textflag.h"
|
|
|
|
|
|
TEXT ·nop(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)
|
|
|
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
|
|
|
if ef.Section(".rela.text") != nil {
|
|
|
|
|
|
t.Error("unexpected .rela.text section")
|
|
|
|
|
|
}
|
|
|
|
|
|
syms, err := ef.Symbols()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
found := false
|
|
|
|
|
|
for _, s := range syms {
|
|
|
|
|
|
if s.Name == "nop" && elf.ST_TYPE(s.Info) == elf.STT_FUNC {
|
|
|
|
|
|
found = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !found {
|
|
|
|
|
|
t.Error("function symbol nop not found")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
|
// 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")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
|
// TestELFLinkAndRun is the end-to-end check: assemble the test functions,
|
|
|
|
|
|
// link the emitted object with a C driver that defines the external symbol,
|
|
|
|
|
|
// and run the result. Skipped when no C compiler is available.
|
|
|
|
|
|
func TestELFLinkAndRun(t *testing.T) {
|
|
|
|
|
|
cc, err := exec.LookPath("cc")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Skip("no C compiler available")
|
|
|
|
|
|
}
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
|
|
|
|
img := elfTestImage(t)
|
|
|
|
|
|
obj, err := img.ELFObject()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("ELFObject: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
objPath := filepath.Join(dir, "t.o")
|
|
|
|
|
|
if err := os.WriteFile(objPath, obj, 0o644); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const driver = `
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
|
|
long addq(long a, long b);
|
|
|
|
|
|
long getanswer(void);
|
|
|
|
|
|
long useextern(void);
|
|
|
|
|
|
|
|
|
|
|
|
long extvar = 7;
|
|
|
|
|
|
|
|
|
|
|
|
int main(void) {
|
|
|
|
|
|
printf("%ld %ld %ld\n", addq(41, 1), getanswer(), useextern());
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
`
|
|
|
|
|
|
driverPath := filepath.Join(dir, "driver.c")
|
|
|
|
|
|
if err := os.WriteFile(driverPath, []byte(driver), 0o644); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// -no-pie: the encoder emits R_X86_64_PC32 for external references,
|
|
|
|
|
|
// which a position-independent executable would reject (it wants
|
|
|
|
|
|
// PLT32/GOT relocations, a future increment).
|
|
|
|
|
|
appPath := filepath.Join(dir, "app")
|
|
|
|
|
|
out, err := exec.Command(cc, "-no-pie", "-o", appPath, driverPath, objPath).CombinedOutput()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("link failed: %v\n%s", err, out)
|
|
|
|
|
|
}
|
|
|
|
|
|
run, err := exec.Command(appPath).CombinedOutput()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("run failed: %v\n%s", err, run)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := string(run); got != "42 42 7\n" {
|
|
|
|
|
|
t.Errorf("output %q, want \"42 42 7\\n\"", got)
|
|
|
|
|
|
}
|
2026-09-19 23:49:13 +02:00
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
|
}
|
2026-09-20 19:15:05 +02:00
|
|
|
|
|
|
|
|
|
|
// TestELFObjectDataRelocation checks that a symbol-valued DATA field ("DATA
|
|
|
|
|
|
// s+0(SB)/8, $other(SB)") reaches the ELF object as a .rela.data entry: an
|
|
|
|
|
|
// absolute 64-bit relocation at the field's offset within .data, against
|
|
|
|
|
|
// the named symbol, external targets included.
|
|
|
|
|
|
func TestELFObjectDataRelocation(t *testing.T) {
|
|
|
|
|
|
f, errs := parser.Parse("t_amd64.s", `#include "textflag.h"
|
|
|
|
|
|
TEXT ·Keep(SB), NOSPLIT, $0-8
|
|
|
|
|
|
RET
|
|
|
|
|
|
GLOBL holder(SB), NOPTR, $24
|
|
|
|
|
|
DATA holder+0(SB)/8, $·Keep+5(SB)
|
|
|
|
|
|
DATA holder+8(SB)/8, $holder(SB)
|
|
|
|
|
|
DATA holder+16(SB)/8, $extvar(SB)
|
|
|
|
|
|
`)
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
|
}
|
|
|
|
|
|
img, err := AssembleFile(f)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("assemble: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
|
|
|
relaData := ef.Section(".rela.data")
|
|
|
|
|
|
if relaData == nil {
|
|
|
|
|
|
t.Fatal("missing .rela.data section")
|
|
|
|
|
|
}
|
|
|
|
|
|
if relaData.Link == 0 || ef.Sections[relaData.Link].Name != ".symtab" {
|
|
|
|
|
|
t.Errorf(".rela.data sh_link = %d, want the .symtab index", relaData.Link)
|
|
|
|
|
|
}
|
|
|
|
|
|
if ef.Sections[relaData.Info].Name != ".data" {
|
|
|
|
|
|
t.Errorf(".rela.data sh_info = %d, want the .data index", relaData.Info)
|
|
|
|
|
|
}
|
|
|
|
|
|
relas, err := relaData.Data()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
var got []struct {
|
|
|
|
|
|
off uint64
|
|
|
|
|
|
sym uint32
|
|
|
|
|
|
typ uint32
|
|
|
|
|
|
addend int64
|
|
|
|
|
|
}
|
|
|
|
|
|
for i := 0; i+24 <= len(relas); i += 24 {
|
|
|
|
|
|
got = append(got, struct {
|
|
|
|
|
|
off uint64
|
|
|
|
|
|
sym uint32
|
|
|
|
|
|
typ uint32
|
|
|
|
|
|
addend int64
|
|
|
|
|
|
}{
|
|
|
|
|
|
off: binary.LittleEndian.Uint64(relas[i:]),
|
|
|
|
|
|
// r_info packs the type in the low dword and the symbol index
|
|
|
|
|
|
// in the high dword.
|
|
|
|
|
|
typ: binary.LittleEndian.Uint32(relas[i+8:]),
|
|
|
|
|
|
sym: binary.LittleEndian.Uint32(relas[i+12:]),
|
|
|
|
|
|
addend: int64(binary.LittleEndian.Uint64(relas[i+16:])),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
// debug/elf hides the table's null entry, so raw index s names syms[s-1].
|
|
|
|
|
|
syms, err := ef.Symbols()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
name := func(idx uint32) string {
|
|
|
|
|
|
if idx >= 1 && int(idx) <= len(syms) {
|
|
|
|
|
|
return syms[idx-1].Name
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
// The offsets are data-section-relative: the field's DATA offset plus
|
|
|
|
|
|
// the symbol's position in .data (the layout aligns each symbol to 16).
|
|
|
|
|
|
base := uint64(0)
|
|
|
|
|
|
for _, d := range img.DataSyms {
|
|
|
|
|
|
if d.Name == "holder" {
|
|
|
|
|
|
base = uint64(d.Offset)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
want := []struct {
|
|
|
|
|
|
off uint64
|
|
|
|
|
|
typ uint32
|
|
|
|
|
|
addend int64
|
|
|
|
|
|
target string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{off: base + 0, typ: uint32(elf.R_X86_64_64), addend: 5, target: "Keep"},
|
|
|
|
|
|
{off: base + 8, typ: uint32(elf.R_X86_64_64), addend: 0, target: "holder"},
|
|
|
|
|
|
{off: base + 16, typ: uint32(elf.R_X86_64_64), addend: 0, target: "extvar"},
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(got) != len(want) {
|
|
|
|
|
|
t.Fatalf(".rela.data entries = %d, want %d", len(got), len(want))
|
|
|
|
|
|
}
|
|
|
|
|
|
for i, w := range want {
|
|
|
|
|
|
g := got[i]
|
|
|
|
|
|
if g.off != w.off || g.typ != w.typ || g.addend != w.addend {
|
|
|
|
|
|
t.Errorf("entry %d = {off %d typ %d addend %d}, want {off %d typ %d addend %d}",
|
|
|
|
|
|
i, g.off, g.typ, g.addend, w.off, w.typ, w.addend)
|
|
|
|
|
|
}
|
|
|
|
|
|
if n := name(g.sym); n != w.target {
|
|
|
|
|
|
t.Errorf("entry %d names %q, want %q", i, n, w.target)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|