// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "bytes" "debug/elf" "encoding/binary" "testing" "sourcedock.dev/petrbalvin/gasm-devkit/parser" ) // TestELFRISCVObjectDataRelocation checks that a symbol-valued DATA field // ("DATA s+0(SB)/8, $other(SB)") reaches the RISC-V ELF object as a // .rela.data entry: an R_RISCV_64 (R_RISCV_32 for a width-4 field) at the // field's offset within .data, against the named symbol, external targets // included. func TestELFRISCVObjectDataRelocation(t *testing.T) { f, errs := parser.Parse("t_riscv64.s", `#include "textflag.h" TEXT ·Keep(SB), NOSPLIT, $0-0 RET GLOBL holder(SB), NOPTR, $32 DATA holder+0(SB)/8, $·Keep+5(SB) DATA holder+8(SB)/8, $holder(SB) DATA holder+16(SB)/8, $extvar(SB) DATA holder+24(SB)/4, $Keep(SB) `) if len(errs) > 0 { t.Fatalf("parse: %v", errs) } img, err := AssembleFileRISCV(f) if err != nil { t.Fatalf("AssembleFileRISCV: %v", err) } obj, err := img.ELFRISCVObject() if err != nil { t.Fatalf("ELFRISCVObject: %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() relaData := ef.Section(".rela.data") if relaData == nil { t.Fatal("missing .rela.data section") } if relaData.Type != elf.SHT_RELA { t.Errorf(".rela.data type = %v, want SHT_RELA", relaData.Type) } 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_RISCV_64), addend: 5, target: "Keep"}, {off: base + 8, typ: uint32(elf.R_RISCV_64), addend: 0, target: "holder"}, {off: base + 16, typ: uint32(elf.R_RISCV_64), addend: 0, target: "extvar"}, {off: base + 24, typ: uint32(elf.R_RISCV_32), addend: 0, target: "Keep"}, } 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) } } }