2026-08-13 11:24:44 +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"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestELFLOONG64Object checks the structure of the emitted LoongArch 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 TestELFLOONG64Object(t *testing.T) {
|
|
|
|
|
f, errs := parser.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
|
|
|
|
|
|
|
|
|
|
TEXT ·getanswer(SB), NOSPLIT, $0-8
|
|
|
|
|
MOVV answer<>(SB), R4
|
|
|
|
|
MOVV 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 := AssembleFileLOONG64(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("AssembleFileLOONG64: %v", err)
|
|
|
|
|
}
|
|
|
|
|
obj, err := img.ELFLOONG64Object()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ELFLOONG64Object: %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_LOONGARCH {
|
|
|
|
|
t.Errorf("type/machine = %v/%v, want ET_REL/EM_LOONGARCH", 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.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")
|
|
|
|
|
}
|
|
|
|
|
dataData, err := data.Data()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
syms, err := ef.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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ef.Sections[1].Name != ".text" || ef.Sections[2].Name != ".data" {
|
|
|
|
|
t.Fatalf("section layout = %s, %s; want .text, .data", ef.Sections[1].Name, ef.Sections[2].Name)
|
|
|
|
|
}
|
|
|
|
|
textIdx := elf.SectionIndex(1)
|
|
|
|
|
dataIdx := elf.SectionIndex(2)
|
|
|
|
|
wantSym("add", elf.STB_GLOBAL, elf.STT_FUNC, textIdx, 20)
|
|
|
|
|
wantSym("getanswer", elf.STB_GLOBAL, elf.STT_FUNC, textIdx, 16)
|
|
|
|
|
wantSym("answer", elf.STB_LOCAL, elf.STT_OBJECT, dataIdx, 8)
|
|
|
|
|
|
|
|
|
|
// The data section carries 16-byte alignment padding; the answer
|
|
|
|
|
// symbol sits at its padded offset.
|
|
|
|
|
ans := byName["answer"]
|
|
|
|
|
if ans.Value+8 > uint64(len(dataData)) {
|
|
|
|
|
t.Fatalf("answer value %d outside .data (%d bytes)", ans.Value, len(dataData))
|
|
|
|
|
}
|
|
|
|
|
if got := dataData[ans.Value : ans.Value+8]; !bytes.Equal(got, []byte{42, 0, 0, 0, 0, 0, 0, 0}) {
|
|
|
|
|
t.Errorf("answer data = % x, want $42", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Relocations: the static-symbol load is a pcalau12i+ld.d pair, so one
|
|
|
|
|
// R_LARCH_PCALA_HI20 and one R_LARCH_PCALA_LO12, both against the local
|
|
|
|
|
// data symbol. debug/elf does not surface rela entries, so read the
|
|
|
|
|
// section directly.
|
|
|
|
|
relaSec := ef.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))
|
|
|
|
|
}
|
|
|
|
|
le := binary.LittleEndian
|
2026-08-29 15:40:31 +02:00
|
|
|
for i := range 2 {
|
2026-08-13 11:24:44 +02:00
|
|
|
e := raw[i*24 : (i+1)*24]
|
|
|
|
|
off := le.Uint64(e[0:])
|
|
|
|
|
info := le.Uint64(e[8:])
|
|
|
|
|
typ := info & 0xffffffff
|
|
|
|
|
sym := int(info >> 32)
|
|
|
|
|
if i == 0 && (typ != uint64(elf.R_LARCH_PCALA_HI20) || off != 20) {
|
|
|
|
|
t.Errorf("reloc %d: type %d off %d, want R_LARCH_PCALA_HI20 at 20", i, typ, off)
|
|
|
|
|
}
|
|
|
|
|
if i == 1 && (typ != uint64(elf.R_LARCH_PCALA_LO12) || off != 24) {
|
|
|
|
|
t.Errorf("reloc %d: type %d off %d, want R_LARCH_PCALA_LO12 at 24", i, typ, off)
|
|
|
|
|
}
|
|
|
|
|
if sym != 3 { // NULL, .text, .data, then the first local: answer
|
|
|
|
|
t.Errorf("reloc %d: symbol index %d, want 3 (answer)", i, sym)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestELFLOONG64ObjectNoRelocations checks a file with no static-symbol
|
|
|
|
|
// references emits a valid object without a .rela.text section.
|
|
|
|
|
func TestELFLOONG64ObjectNoRelocations(t *testing.T) {
|
|
|
|
|
f, errs := parser.Parse("n_loong64.s", `
|
|
|
|
|
#include "textflag.h"
|
|
|
|
|
TEXT ·nop(SB), NOSPLIT, $0
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
}
|
|
|
|
|
img, err := AssembleFileLOONG64(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("AssembleFileLOONG64: %v", err)
|
|
|
|
|
}
|
|
|
|
|
obj, err := img.ELFLOONG64Object()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ELFLOONG64Object: %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")
|
|
|
|
|
}
|
|
|
|
|
}
|