2026-08-20 14:07:12 +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"
|
2026-09-19 23:49:13 +02:00
|
|
|
"encoding/binary"
|
2026-08-20 14:07:12 +02:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestELFAARCH64Object checks the structure of the emitted AArch64 ELF64
|
|
|
|
|
// relocatable object: sections, the symbol table (bindings, types, values,
|
|
|
|
|
// sizes) and the .rela.text relocation pair for the static-symbol load,
|
|
|
|
|
// parsed back with debug/elf.
|
|
|
|
|
func TestELFAARCH64Object(t *testing.T) {
|
|
|
|
|
f, errs := parser.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
|
|
|
|
|
|
|
|
|
|
TEXT ·getanswer(SB), NOSPLIT, $0-8
|
|
|
|
|
MOVD answer<>(SB), R4
|
2026-09-19 23:49:13 +02:00
|
|
|
MOVD $answer<>(SB), R5
|
2026-08-20 14:07:12 +02:00
|
|
|
MOVD R4, ret+0(FP)
|
|
|
|
|
RET
|
|
|
|
|
|
|
|
|
|
GLOBL answer<>(SB), RODATA, $8
|
|
|
|
|
DATA answer<>+0(SB)/8, $42
|
|
|
|
|
`)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
}
|
|
|
|
|
img, err := AssembleFileARM64(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("AssembleFileARM64: %v", err)
|
|
|
|
|
}
|
|
|
|
|
obj, err := img.ELFAARCH64Object()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ELFAARCH64Object: %v", err)
|
|
|
|
|
}
|
|
|
|
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("parse emitted object: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer ef.Close()
|
|
|
|
|
|
|
|
|
|
if ef.Type != elf.ET_REL || ef.Machine != elf.EM_AARCH64 {
|
|
|
|
|
t.Errorf("type/machine = %v/%v, want ET_REL/EM_AARCH64", ef.Type, ef.Machine)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := ef.Section(".text")
|
|
|
|
|
data := ef.Section(".data")
|
|
|
|
|
if text == nil || data == nil {
|
|
|
|
|
t.Fatal("missing .text or .data section")
|
|
|
|
|
}
|
|
|
|
|
if text.Size == 0 {
|
|
|
|
|
t.Error(".text section is empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
syms, err := ef.Symbols()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("symbols: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foundAdd, foundGetanswer, foundAnswer := false, false, false
|
|
|
|
|
for _, s := range syms {
|
|
|
|
|
switch s.Name {
|
|
|
|
|
case "add":
|
|
|
|
|
foundAdd = true
|
|
|
|
|
if elf.SymType(s.Info&0xf) != elf.STT_FUNC || elf.SymBind(s.Info>>4) != elf.STB_GLOBAL {
|
|
|
|
|
t.Errorf("add: info=0x%02x, want STT_FUNC|STB_GLOBAL", s.Info)
|
|
|
|
|
}
|
|
|
|
|
case "getanswer":
|
|
|
|
|
foundGetanswer = true
|
|
|
|
|
if elf.SymType(s.Info&0xf) != elf.STT_FUNC || elf.SymBind(s.Info>>4) != elf.STB_GLOBAL {
|
|
|
|
|
t.Errorf("getanswer: info=0x%02x, want STT_FUNC|STB_GLOBAL", s.Info)
|
|
|
|
|
}
|
|
|
|
|
case "answer":
|
|
|
|
|
foundAnswer = true
|
|
|
|
|
if elf.SymType(s.Info&0xf) != elf.STT_OBJECT || elf.SymBind(s.Info>>4) != elf.STB_LOCAL {
|
|
|
|
|
t.Errorf("answer: info=0x%02x, want STT_OBJECT|STB_LOCAL", s.Info)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !foundAdd {
|
|
|
|
|
t.Error("symbol 'add' not found")
|
|
|
|
|
}
|
|
|
|
|
if !foundGetanswer {
|
|
|
|
|
t.Error("symbol 'getanswer' not found")
|
|
|
|
|
}
|
|
|
|
|
if !foundAnswer {
|
|
|
|
|
t.Error("symbol 'answer' not found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that .rela.text exists (getanswer has SB reference).
|
|
|
|
|
relaText := ef.Section(".rela.text")
|
|
|
|
|
if relaText == nil {
|
2026-09-19 23:49:13 +02:00
|
|
|
t.Fatal("missing .rela.text section")
|
2026-08-20 14:07:12 +02:00
|
|
|
}
|
2026-09-19 23:49:13 +02:00
|
|
|
|
|
|
|
|
// 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{}
|
2026-08-20 14:07:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestELFAARCH64ObjectNoRelocations checks the ELF output when there are no
|
|
|
|
|
// static-symbol references (no .rela.text section).
|
|
|
|
|
func TestELFAARCH64ObjectNoRelocations(t *testing.T) {
|
|
|
|
|
f, errs := parser.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
|
|
|
|
|
`)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
}
|
|
|
|
|
img, err := AssembleFileARM64(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("AssembleFileARM64: %v", err)
|
|
|
|
|
}
|
|
|
|
|
obj, err := img.ELFAARCH64Object()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ELFAARCH64Object: %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 when there are no relocations")
|
|
|
|
|
}
|
|
|
|
|
}
|