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 (
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"fmt"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// LoongArch ELF64 relocatable object emission.
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
emLOONGARCH = 258 // EM_LOONGARCH
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
// EF_LOONGARCH_ABI_DOUBLE_FLOAT | EF_LOONGARCH_OBJABI_V1: the flags the
|
|
|
|
|
// Go toolchain writes (cmd/link/internal/ld/elf.go: Flags = 0x43 for
|
|
|
|
|
// Loong64). System linkers refuse to merge ET_REL objects whose float
|
|
|
|
|
// ABI differs, so 0 (soft-float) would make the object unlinkable.
|
|
|
|
|
efLarchAbiDoubleObjV1 = 0x43
|
|
|
|
|
|
2026-08-13 11:24:44 +02:00
|
|
|
// LoongArch relocation types (the ELF psABI).
|
|
|
|
|
rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i)
|
|
|
|
|
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
2026-09-14 22:38:58 +02:00
|
|
|
rLarchB26 = 66 // R_LARCH_B26 (b/bl, matches the Go linker's mapping)
|
2026-09-20 21:17:20 +02:00
|
|
|
// 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
|
2026-08-13 11:24:44 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ELFLOONG64Object returns the image as an ELF64 relocatable object file for
|
|
|
|
|
// LoongArch (EM_LOONGARCH, 64-bit, little-endian). The structure mirrors the
|
2026-09-20 21:17:20 +02:00
|
|
|
// amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab, an
|
|
|
|
|
// optional .rela.text and an optional .rela.data.
|
2026-08-13 11:24:44 +02:00
|
|
|
func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
|
|
|
|
le := binary.LittleEndian
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
secText = 1
|
|
|
|
|
secData = 2
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Build symbol table.
|
|
|
|
|
var locals, globals []elfSym
|
|
|
|
|
for _, fn := range img.Funcs {
|
|
|
|
|
s := elfSym{
|
|
|
|
|
name: objectName(fn.Pkg, fn.Name),
|
|
|
|
|
info: sttFunc,
|
|
|
|
|
shndx: secText,
|
|
|
|
|
value: uint64(fn.Offset),
|
|
|
|
|
size: uint64(fn.Size),
|
|
|
|
|
}
|
|
|
|
|
if fn.Static {
|
|
|
|
|
locals = append(locals, s)
|
|
|
|
|
} else {
|
|
|
|
|
s.info |= stbGlobal << stInfoShift
|
|
|
|
|
globals = append(globals, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, d := range img.DataSyms {
|
|
|
|
|
s := elfSym{
|
|
|
|
|
name: objectName(d.Pkg, d.Name),
|
|
|
|
|
info: sttObject,
|
|
|
|
|
shndx: secData,
|
|
|
|
|
value: uint64(d.Offset),
|
|
|
|
|
size: uint64(d.Size),
|
|
|
|
|
}
|
|
|
|
|
if d.Static {
|
|
|
|
|
locals = append(locals, s)
|
|
|
|
|
} else {
|
|
|
|
|
s.info |= stbGlobal << stInfoShift
|
|
|
|
|
globals = append(globals, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, name := range img.Externals {
|
|
|
|
|
globals = append(globals, elfSym{name: name, info: stbGlobal << stInfoShift})
|
|
|
|
|
}
|
|
|
|
|
syms := []elfSym{
|
|
|
|
|
{},
|
|
|
|
|
{name: ".text", info: sttSection, shndx: secText},
|
|
|
|
|
{name: ".data", info: sttSection, shndx: secData},
|
|
|
|
|
}
|
|
|
|
|
syms = append(syms, locals...)
|
|
|
|
|
shInfo := len(syms)
|
|
|
|
|
syms = append(syms, globals...)
|
|
|
|
|
symIdx := map[string]int{}
|
|
|
|
|
for i, s := range syms {
|
|
|
|
|
symIdx[s.name] = i
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build relocations. Each SB reference is a pcalau12i pair:
|
|
|
|
|
// pcalau12i rd, 0 → R_LARCH_PCALA_HI20
|
|
|
|
|
// addi.d/ld/st → R_LARCH_PCALA_LO12
|
|
|
|
|
type elfRela struct {
|
|
|
|
|
off uint64
|
|
|
|
|
typ uint32
|
|
|
|
|
sym int
|
|
|
|
|
addend int64
|
|
|
|
|
}
|
|
|
|
|
var relas []elfRela
|
|
|
|
|
for _, fn := range img.Funcs {
|
|
|
|
|
for _, r := range fn.Relocs {
|
|
|
|
|
idx, ok := symIdx[r.Name]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
|
|
|
|
}
|
|
|
|
|
typ := uint32(rLarchPCALAHI20)
|
2026-09-14 22:38:58 +02:00
|
|
|
switch r.Kind {
|
|
|
|
|
case RelLoong64AddrLo:
|
2026-08-13 11:24:44 +02:00
|
|
|
typ = rLarchPCALALO12
|
2026-09-14 22:38:58 +02:00
|
|
|
case RelLoong64Branch:
|
|
|
|
|
typ = rLarchB26
|
2026-08-13 11:24:44 +02:00
|
|
|
}
|
|
|
|
|
relas = append(relas, elfRela{
|
|
|
|
|
off: uint64(fn.Offset + r.Off),
|
|
|
|
|
typ: typ,
|
|
|
|
|
sym: idx,
|
2026-09-14 18:22:00 +02:00
|
|
|
addend: r.Addend,
|
2026-08-13 11:24:44 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-20 21:17:20 +02:00
|
|
|
// 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
|
|
|
|
|
|
2026-08-13 11:24:44 +02:00
|
|
|
// String tables.
|
|
|
|
|
stNames := newElfStrtab()
|
|
|
|
|
for _, s := range syms {
|
|
|
|
|
stNames.add(s.name)
|
|
|
|
|
}
|
|
|
|
|
stSections := newElfStrtab()
|
|
|
|
|
for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} {
|
|
|
|
|
stSections.add(n)
|
|
|
|
|
}
|
2026-09-20 21:17:20 +02:00
|
|
|
if hasDataRela {
|
|
|
|
|
stSections.add(".rela.data")
|
|
|
|
|
}
|
2026-08-21 00:14:02 +02:00
|
|
|
for _, n := range dwarfSectionNames {
|
|
|
|
|
stSections.add(n)
|
|
|
|
|
}
|
2026-08-13 11:24:44 +02:00
|
|
|
|
|
|
|
|
// Layout.
|
|
|
|
|
var out []byte
|
|
|
|
|
out = append(out, make([]byte, 64)...)
|
|
|
|
|
|
|
|
|
|
align := func(n int) {
|
|
|
|
|
for len(out)%n != 0 {
|
|
|
|
|
out = append(out, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
align(16)
|
|
|
|
|
textOff := len(out)
|
|
|
|
|
out = append(out, img.Code...)
|
|
|
|
|
|
|
|
|
|
align(16)
|
|
|
|
|
dataOff := len(out)
|
|
|
|
|
out = append(out, img.Data...)
|
|
|
|
|
|
|
|
|
|
align(8)
|
|
|
|
|
symtabOff := len(out)
|
|
|
|
|
for _, s := range syms {
|
|
|
|
|
var b [24]byte
|
|
|
|
|
le.PutUint32(b[0:], uint32(stNames.at(s.name)))
|
|
|
|
|
b[4] = s.info
|
|
|
|
|
b[5] = 0
|
|
|
|
|
le.PutUint16(b[6:], s.shndx)
|
|
|
|
|
le.PutUint64(b[8:], s.value)
|
|
|
|
|
le.PutUint64(b[16:], s.size)
|
|
|
|
|
out = append(out, b[:]...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strtabOff := len(out)
|
|
|
|
|
out = append(out, stNames.bytes()...)
|
|
|
|
|
|
2026-09-20 21:17:20 +02:00
|
|
|
var relaOff, relaDataOff int
|
2026-08-13 11:24:44 +02:00
|
|
|
if hasRela {
|
|
|
|
|
align(8)
|
|
|
|
|
relaOff = len(out)
|
|
|
|
|
for _, r := range relas {
|
|
|
|
|
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[:]...)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-20 21:17:20 +02:00
|
|
|
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[:]...)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-13 11:24:44 +02:00
|
|
|
|
|
|
|
|
shstrOff := len(out)
|
|
|
|
|
out = append(out, stSections.bytes()...)
|
|
|
|
|
|
2026-08-21 00:14:02 +02:00
|
|
|
dwAlign := func(n int) {
|
|
|
|
|
for len(out)%n != 0 {
|
|
|
|
|
out = append(out, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 23:49:13 +02:00
|
|
|
dw := appendDWARFSections(&out, img, dwarfSourceName(img), symIdx, dwAlign, cfiLOONG64)
|
|
|
|
|
dwarfStart := 0 // section index of .debug_abbrev, set when DWARF is present
|
2026-08-21 00:14:02 +02:00
|
|
|
if dw != nil {
|
2026-09-19 23:49:13 +02:00
|
|
|
// Five DWARF sections: .debug_abbrev, .debug_info, .debug_line,
|
|
|
|
|
// .debug_line_str and .debug_frame (the CIE is unconditional, so
|
|
|
|
|
// the frame section is always present), plus the relocation
|
|
|
|
|
// sections below when they carry entries.
|
|
|
|
|
dwarfStart = nSections
|
|
|
|
|
nSections += 5
|
|
|
|
|
appendDWARFRelas(&out, dw, rLarchAbs64, dwAlign)
|
|
|
|
|
if dw.infoRelaCount > 0 {
|
|
|
|
|
nSections++
|
|
|
|
|
}
|
|
|
|
|
if dw.lineRelaCount > 0 {
|
|
|
|
|
nSections++
|
|
|
|
|
}
|
|
|
|
|
if dw.frameRelaCount > 0 {
|
|
|
|
|
nSections++
|
|
|
|
|
}
|
2026-08-21 00:14:02 +02:00
|
|
|
}
|
|
|
|
|
|
2026-08-13 11:24:44 +02:00
|
|
|
align(8)
|
|
|
|
|
shoff := len(out)
|
|
|
|
|
|
|
|
|
|
putSh := func(name string, typ int, flags uint64, off, size int, link, info int, alignV, entsize uint64) {
|
|
|
|
|
var b [64]byte
|
|
|
|
|
le.PutUint32(b[0:], uint32(stSections.at(name)))
|
|
|
|
|
le.PutUint32(b[4:], uint32(typ))
|
|
|
|
|
le.PutUint64(b[8:], flags)
|
|
|
|
|
le.PutUint64(b[16:], 0)
|
|
|
|
|
le.PutUint64(b[24:], uint64(off))
|
|
|
|
|
le.PutUint64(b[32:], uint64(size))
|
|
|
|
|
le.PutUint32(b[40:], uint32(link))
|
|
|
|
|
le.PutUint32(b[44:], uint32(info))
|
|
|
|
|
le.PutUint64(b[48:], alignV)
|
|
|
|
|
le.PutUint64(b[56:], entsize)
|
|
|
|
|
out = append(out, b[:]...)
|
|
|
|
|
}
|
|
|
|
|
putSh("", shtNull, 0, 0, 0, 0, 0, 0, 0)
|
|
|
|
|
putSh(".text", shtProgbits, shfAlloc|shfExecInstr, textOff, len(img.Code), 0, 0, 16, 0)
|
|
|
|
|
putSh(".data", shtProgbits, shfAlloc|shfWrite, dataOff, len(img.Data), 0, 0, 16, 0)
|
|
|
|
|
putSh(".symtab", shtSymtab, 0, symtabOff, 24*len(syms), secStrtab, shInfo, 8, 24)
|
|
|
|
|
putSh(".strtab", shtStrtab, 0, strtabOff, len(stNames.bytes()), 0, 0, 1, 0)
|
|
|
|
|
if hasRela {
|
|
|
|
|
putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24)
|
|
|
|
|
}
|
2026-09-20 21:17:20 +02:00
|
|
|
if hasDataRela {
|
|
|
|
|
putSh(".rela.data", shtRela, 0, relaDataOff, 24*len(dataRelas), secSymtab, secData, 8, 24)
|
|
|
|
|
}
|
2026-08-13 11:24:44 +02:00
|
|
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
2026-09-19 23:49:13 +02:00
|
|
|
// DWARF section headers; their indices follow the write order.
|
2026-08-21 00:14:02 +02:00
|
|
|
if dw != nil {
|
2026-09-19 23:49:13 +02:00
|
|
|
// secIdx is a running section index: each putSh below emits the
|
|
|
|
|
// next header, and the sh_info of a .rela section names the index
|
|
|
|
|
// of the section it relocates.
|
|
|
|
|
secIdx := dwarfStart
|
2026-08-21 00:14:02 +02:00
|
|
|
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
2026-09-19 23:49:13 +02:00
|
|
|
secIdx++
|
2026-08-21 00:14:02 +02:00
|
|
|
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
2026-09-19 23:49:13 +02:00
|
|
|
secInfoIdx := secIdx
|
|
|
|
|
secIdx++
|
|
|
|
|
if dw.infoRelaCount > 0 {
|
|
|
|
|
putSh(".rela.debug_info", shtRela, 0, dw.infoRelaOff, 24*dw.infoRelaCount, secSymtab, secInfoIdx, 8, 24)
|
|
|
|
|
secIdx++
|
|
|
|
|
}
|
2026-08-21 00:14:02 +02:00
|
|
|
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
2026-09-19 23:49:13 +02:00
|
|
|
secLineIdx := secIdx
|
|
|
|
|
secIdx++
|
|
|
|
|
if dw.lineRelaCount > 0 {
|
|
|
|
|
putSh(".rela.debug_line", shtRela, 0, dw.lineRelaOff, 24*dw.lineRelaCount, secSymtab, secLineIdx, 8, 24)
|
|
|
|
|
secIdx++
|
|
|
|
|
}
|
2026-08-21 00:14:02 +02:00
|
|
|
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
2026-09-19 23:49:13 +02:00
|
|
|
secIdx++
|
2026-08-21 01:06:21 +02:00
|
|
|
if dw.frameSize > 0 {
|
|
|
|
|
putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0)
|
2026-09-19 23:49:13 +02:00
|
|
|
secFrameIdx := secIdx
|
|
|
|
|
secIdx++
|
|
|
|
|
if dw.frameRelaCount > 0 {
|
|
|
|
|
putSh(".rela.debug_frame", shtRela, 0, dw.frameRelaOff, 24*dw.frameRelaCount, secSymtab, secFrameIdx, 8, 24)
|
|
|
|
|
}
|
2026-08-21 01:06:21 +02:00
|
|
|
}
|
2026-08-21 00:14:02 +02:00
|
|
|
}
|
2026-08-13 11:24:44 +02:00
|
|
|
|
|
|
|
|
// ELF header.
|
|
|
|
|
hdr := out[:64]
|
|
|
|
|
copy(hdr[0:], []byte{0x7f, 'E', 'L', 'F', elfClass64, elfDataLSB, elfVersion, 0})
|
|
|
|
|
le.PutUint16(hdr[16:], etREL)
|
|
|
|
|
le.PutUint16(hdr[18:], emLOONGARCH)
|
|
|
|
|
le.PutUint32(hdr[20:], elfVersion)
|
|
|
|
|
le.PutUint64(hdr[24:], 0)
|
|
|
|
|
le.PutUint64(hdr[32:], 0)
|
|
|
|
|
le.PutUint64(hdr[40:], uint64(shoff))
|
2026-09-19 23:49:13 +02:00
|
|
|
le.PutUint32(hdr[48:], efLarchAbiDoubleObjV1)
|
2026-08-13 11:24:44 +02:00
|
|
|
le.PutUint16(hdr[52:], 64)
|
|
|
|
|
le.PutUint16(hdr[54:], 0)
|
|
|
|
|
le.PutUint16(hdr[56:], 0)
|
|
|
|
|
le.PutUint16(hdr[58:], 64)
|
|
|
|
|
le.PutUint16(hdr[60:], uint16(nSections))
|
|
|
|
|
le.PutUint16(hdr[62:], uint16(secShstr))
|
|
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|