diff --git a/asm/elfarm64.go b/asm/elfarm64.go index 0cfdc55..6be91af 100644 --- a/asm/elfarm64.go +++ b/asm/elfarm64.go @@ -18,12 +18,16 @@ const ( rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD page offset) rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction) rArm64Ldst64Lo12NC = 286 // R_AARCH64_LDST64_ABS_LO12_NC (64-bit LDR/STR page offset) + // R_AARCH64_ABS32 (debug/elf 258): the absolute 32-bit address of a + // symbol, the R_ADDR shape a 4-byte DATA field carries. ABS64 (257) + // lives with the DWARF fixup constants as rAARCH64Abs64. + rArm64Abs32 = 258 ) // ELFAARCH64Object returns the image as an ELF64 relocatable object file for // AArch64 (EM_AARCH64, 64-bit, little-endian). The structure mirrors the -// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an -// optional .rela.text. +// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an +// optional .rela.text and an optional .rela.data. func (img *Image) ELFAARCH64Object() ([]byte, error) { le := binary.LittleEndian @@ -133,6 +137,50 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { } } + // The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8, + // $other(SB)") become .rela.data entries: an absolute relocation of the + // DATA line's width at the field's data-section offset, S + A with no + // PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields + // cannot hold an address, so they are refused rather than truncated. + var dataRelas []elfRela + for _, d := range img.DataSyms { + for _, r := range d.Relocs { + idx, ok := symIdx[r.Name] + if !ok { + return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name) + } + var typ uint32 + switch r.Siz { + case 8: + typ = rAARCH64Abs64 + case 4: + typ = rArm64Abs32 + default: + return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz) + } + dataRelas = append(dataRelas, elfRela{ + off: uint64(d.Offset + r.Off), + sym: idx, + typ: typ, + addend: r.Addend, + }) + } + } + + // Section presence: .rela.text only when there are code relocations, + // .rela.data only when a DATA line holds a symbol value. + hasRela := len(relas) > 0 + hasDataRela := len(dataRelas) > 0 + nSections := 6 + if hasRela { + nSections++ + } + if hasDataRela { + nSections++ + } + secSymtab, secStrtab := 3, 4 + secShstr := nSections - 1 + // String tables. stNames := newElfStrtab() for _, s := range syms { @@ -142,18 +190,13 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + if hasDataRela { + stSections.add(".rela.data") + } for _, n := range dwarfSectionNames { stSections.add(n) } - hasRela := len(relas) > 0 - nSections := 6 - if hasRela { - nSections = 7 - } - secSymtab, secStrtab := 3, 4 - secShstr := nSections - 1 - // Layout. var out []byte out = append(out, make([]byte, 64)...) @@ -188,7 +231,7 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { strtabOff := len(out) out = append(out, stNames.bytes()...) - var relaOff int + var relaOff, relaDataOff int if hasRela { align(8) relaOff = len(out) @@ -200,6 +243,17 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { out = append(out, b[:]...) } } + if hasDataRela { + align(8) + relaDataOff = len(out) + for _, r := range dataRelas { + var b [24]byte + le.PutUint64(b[0:], r.off) + le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ)) + le.PutUint64(b[16:], uint64(r.addend)) + out = append(out, b[:]...) + } + } shstrOff := len(out) out = append(out, stSections.bytes()...) @@ -257,6 +311,9 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { if hasRela { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } + if hasDataRela { + putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24) + } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) // DWARF section headers; their indices follow the write order. if dw != nil { diff --git a/asm/elfarm64_test.go b/asm/elfarm64_test.go index 96a8607..0230dc1 100644 --- a/asm/elfarm64_test.go +++ b/asm/elfarm64_test.go @@ -197,3 +197,118 @@ TEXT ·add(SB), NOSPLIT, $0-24 t.Error("unexpected .rela.text section when there are no relocations") } } + +// TestELFAARCH64ObjectDataRelocation checks that a symbol-valued DATA field +// ("DATA s+0(SB)/8, $other(SB)") reaches the AArch64 ELF object as a +// .rela.data entry: an R_AARCH64_ABS64 (ABS32 for a width-4 field) at the +// field's offset within .data, against the named symbol, external targets +// included. +func TestELFAARCH64ObjectDataRelocation(t *testing.T) { + f, errs := parser.Parse("t_arm64.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 := AssembleFileARM64(f) + if err != nil { + t.Fatalf("AssembleFileARM64: %v", err) + } + obj, err := img.ELFAARCH64Object() + if err != nil { + t.Fatalf("ELFAARCH64Object: %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_AARCH64_ABS64), addend: 5, target: "Keep"}, + {off: base + 8, typ: uint32(elf.R_AARCH64_ABS64), addend: 0, target: "holder"}, + {off: base + 16, typ: uint32(elf.R_AARCH64_ABS64), addend: 0, target: "extvar"}, + {off: base + 24, typ: uint32(elf.R_AARCH64_ABS32), 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) + } + } +} diff --git a/asm/elfloong64.go b/asm/elfloong64.go index 3df47d3..78064ff 100644 --- a/asm/elfloong64.go +++ b/asm/elfloong64.go @@ -23,12 +23,16 @@ const ( rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i) rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st) rLarchB26 = 66 // R_LARCH_B26 (b/bl, matches the Go linker's mapping) + // R_LARCH_32 (debug/elf 1): the absolute 32-bit address of a symbol, + // the R_ADDR shape a 4-byte DATA field carries. R_LARCH_64 (2) lives + // with the DWARF fixup constants as rLarchAbs64. + rLarchAbs32 = 1 ) // ELFLOONG64Object returns the image as an ELF64 relocatable object file for // LoongArch (EM_LOONGARCH, 64-bit, little-endian). The structure mirrors the -// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an -// optional .rela.text. +// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an +// optional .rela.text and an optional .rela.data. func (img *Image) ELFLOONG64Object() ([]byte, error) { le := binary.LittleEndian @@ -117,6 +121,50 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { } } + // The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8, + // $other(SB)") become .rela.data entries: an absolute relocation of the + // DATA line's width at the field's data-section offset, S + A with no + // PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields + // cannot hold an address, so they are refused rather than truncated. + var dataRelas []elfRela + for _, d := range img.DataSyms { + for _, r := range d.Relocs { + idx, ok := symIdx[r.Name] + if !ok { + return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name) + } + var typ uint32 + switch r.Siz { + case 8: + typ = rLarchAbs64 + case 4: + typ = rLarchAbs32 + default: + return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz) + } + dataRelas = append(dataRelas, elfRela{ + off: uint64(d.Offset + r.Off), + sym: idx, + typ: typ, + addend: r.Addend, + }) + } + } + + // Section presence: .rela.text only when there are code relocations, + // .rela.data only when a DATA line holds a symbol value. + hasRela := len(relas) > 0 + hasDataRela := len(dataRelas) > 0 + nSections := 6 + if hasRela { + nSections++ + } + if hasDataRela { + nSections++ + } + secSymtab, secStrtab := 3, 4 + secShstr := nSections - 1 + // String tables. stNames := newElfStrtab() for _, s := range syms { @@ -126,18 +174,13 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + if hasDataRela { + stSections.add(".rela.data") + } for _, n := range dwarfSectionNames { stSections.add(n) } - hasRela := len(relas) > 0 - nSections := 6 - if hasRela { - nSections = 7 - } - secSymtab, secStrtab := 3, 4 - secShstr := nSections - 1 - // Layout. var out []byte out = append(out, make([]byte, 64)...) @@ -172,7 +215,7 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { strtabOff := len(out) out = append(out, stNames.bytes()...) - var relaOff int + var relaOff, relaDataOff int if hasRela { align(8) relaOff = len(out) @@ -184,6 +227,17 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { out = append(out, b[:]...) } } + if hasDataRela { + align(8) + relaDataOff = len(out) + for _, r := range dataRelas { + var b [24]byte + le.PutUint64(b[0:], r.off) + le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ)) + le.PutUint64(b[16:], uint64(r.addend)) + out = append(out, b[:]...) + } + } shstrOff := len(out) out = append(out, stSections.bytes()...) @@ -239,6 +293,9 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { if hasRela { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } + if hasDataRela { + putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24) + } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) // DWARF section headers; their indices follow the write order. if dw != nil { diff --git a/asm/elfloong64_test.go b/asm/elfloong64_test.go index 4c65e1d..4017860 100644 --- a/asm/elfloong64_test.go +++ b/asm/elfloong64_test.go @@ -245,3 +245,118 @@ func TestELFLOONG64BranchRelocation(t *testing.T) { } } } + +// TestELFLOONG64ObjectDataRelocation checks that a symbol-valued DATA field +// ("DATA s+0(SB)/8, $other(SB)") reaches the LoongArch ELF object as a +// .rela.data entry: an R_LARCH_64 (R_LARCH_32 for a width-4 field) at the +// field's offset within .data, against the named symbol, external targets +// included. +func TestELFLOONG64ObjectDataRelocation(t *testing.T) { + f, errs := parser.Parse("t_loong64.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 := AssembleFileLOONG64(f) + if err != nil { + t.Fatalf("AssembleFileLOONG64: %v", err) + } + obj, err := img.ELFLOONG64Object() + if err != nil { + t.Fatalf("ELFLOONG64Object: %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_LARCH_64), addend: 5, target: "Keep"}, + {off: base + 8, typ: uint32(elf.R_LARCH_64), addend: 0, target: "holder"}, + {off: base + 16, typ: uint32(elf.R_LARCH_64), addend: 0, target: "extvar"}, + {off: base + 24, typ: uint32(elf.R_LARCH_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) + } + } +} diff --git a/asm/elfriscv.go b/asm/elfriscv.go index b3c5abc..2126922 100644 --- a/asm/elfriscv.go +++ b/asm/elfriscv.go @@ -24,11 +24,16 @@ const ( rRISCVPCRELHI20 = 23 // R_RISCV_PCREL_HI20 rRISCVPCRELLO12I = 24 // R_RISCV_PCREL_LO12_I rRISCVPCRELLO12S = 25 // R_RISCV_PCREL_LO12_S + // R_RISCV_32 (debug/elf 1): the absolute 32-bit address of a symbol, + // the R_ADDR shape a 4-byte DATA field carries. R_RISCV_64 (2) lives + // with the DWARF fixup constants as rRISCVAbs64. + rRISVCAbs32 = 1 ) // ELFRISCVObject returns the image as an ELF64 relocatable object file for // RISC-V (EM_RISCV, 64-bit, little-endian). The structure mirrors the amd64 -// ELF emission: .text, .data, .symtab, .strtab and optional .rela.text. +// ELF emission: .text, .data, .symtab, .strtab, an optional .rela.text and +// an optional .rela.data. func (img *Image) ELFRISCVObject() ([]byte, error) { le := binary.LittleEndian @@ -129,6 +134,50 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { } } + // The data symbols' symbol-valued DATA fields ("DATA s+0(SB)/8, + // $other(SB)") become .rela.data entries: an absolute relocation of the + // DATA line's width at the field's data-section offset, S + A with no + // PC term. Widths 4 and 8 have ELF relocation shapes; narrower fields + // cannot hold an address, so they are refused rather than truncated. + var dataRelas []elfRela + for _, d := range img.DataSyms { + for _, r := range d.Relocs { + idx, ok := symIdx[r.Name] + if !ok { + return nil, fmt.Errorf("data relocation references unknown symbol %q", r.Name) + } + var typ uint32 + switch r.Siz { + case 8: + typ = rRISCVAbs64 + case 4: + typ = rRISVCAbs32 + default: + return nil, fmt.Errorf("DATA %q: a symbol value of width %d has no ELF relocation", d.Name, r.Siz) + } + dataRelas = append(dataRelas, elfRela{ + off: uint64(d.Offset + r.Off), + sym: idx, + typ: typ, + addend: r.Addend, + }) + } + } + + // Section presence: .rela.text only when there are code relocations, + // .rela.data only when a DATA line holds a symbol value. + hasRela := len(relas) > 0 + hasDataRela := len(dataRelas) > 0 + nSections := 6 + if hasRela { + nSections++ + } + if hasDataRela { + nSections++ + } + secSymtab, secStrtab := 3, 4 + secShstr := nSections - 1 + // String tables. stNames := newElfStrtab() for _, s := range syms { @@ -138,18 +187,13 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } + if hasDataRela { + stSections.add(".rela.data") + } for _, n := range dwarfSectionNames { stSections.add(n) } - hasRela := len(relas) > 0 - nSections := 6 - if hasRela { - nSections = 7 - } - secSymtab, secStrtab := 3, 4 - secShstr := nSections - 1 - // Layout. var out []byte out = append(out, make([]byte, 64)...) @@ -184,7 +228,7 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { strtabOff := len(out) out = append(out, stNames.bytes()...) - var relaOff int + var relaOff, relaDataOff int if hasRela { align(8) relaOff = len(out) @@ -196,6 +240,17 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { out = append(out, b[:]...) } } + if hasDataRela { + align(8) + relaDataOff = len(out) + for _, r := range dataRelas { + var b [24]byte + le.PutUint64(b[0:], r.off) + le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ)) + le.PutUint64(b[16:], uint64(r.addend)) + out = append(out, b[:]...) + } + } shstrOff := len(out) out = append(out, stSections.bytes()...) @@ -251,6 +306,9 @@ func (img *Image) ELFRISCVObject() ([]byte, error) { if hasRela { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } + if hasDataRela { + putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24) + } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) // DWARF section headers; their indices follow the write order. if dw != nil { diff --git a/asm/elfriscv_test.go b/asm/elfriscv_test.go new file mode 100644 index 0000000..dee53e6 --- /dev/null +++ b/asm/elfriscv_test.go @@ -0,0 +1,128 @@ +// 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) + } + } +}