2026-07-16 20:52:20 +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"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// This file emits ELF64 relocatable objects (ET_REL) from an assembled
|
|
|
|
|
|
// Image: a .text section holding the function bodies, a .data section
|
|
|
|
|
|
// holding the GLOBL initialisers, a symbol table with one symbol per TEXT
|
|
|
|
|
|
// and GLOBL (file-local <> symbols are STB_LOCAL, the rest STB_GLOBAL), and
|
|
|
|
|
|
// a .rela.text relocation table — one R_X86_64_PC32 entry per static-symbol
|
|
|
|
|
|
// reference, internal references resolving against the local data symbols
|
|
|
|
|
|
// and external ones against undefined globals. The output links with the
|
|
|
|
|
|
// system toolchain (cc/ld) the way a hand-assembled .o would.
|
|
|
|
|
|
|
|
|
|
|
|
// ELF constants (ELF64, little-endian, System V).
|
|
|
|
|
|
const (
|
|
|
|
|
|
elfClass64 = 2
|
|
|
|
|
|
elfDataLSB = 1
|
|
|
|
|
|
elfVersion = 1
|
|
|
|
|
|
|
|
|
|
|
|
etREL = 1 // relocatable object
|
|
|
|
|
|
emX8664 = 62
|
|
|
|
|
|
|
|
|
|
|
|
shtNull = 0
|
|
|
|
|
|
shtProgbits = 1
|
|
|
|
|
|
shtSymtab = 2
|
|
|
|
|
|
shtStrtab = 3
|
|
|
|
|
|
shtRela = 4
|
|
|
|
|
|
|
|
|
|
|
|
shfWrite = 1
|
|
|
|
|
|
shfAlloc = 2
|
|
|
|
|
|
shfExecInstr = 4
|
|
|
|
|
|
|
|
|
|
|
|
stbLocal = 0
|
|
|
|
|
|
stbGlobal = 1
|
|
|
|
|
|
|
|
|
|
|
|
sttNotype = 0
|
|
|
|
|
|
sttObject = 1
|
|
|
|
|
|
sttFunc = 2
|
|
|
|
|
|
sttSection = 3
|
|
|
|
|
|
stInfoShift = 4
|
|
|
|
|
|
|
|
|
|
|
|
rX8664PC32 = 2
|
2026-08-21 00:14:02 +02:00
|
|
|
|
rX8664_64 = 1 // R_X86_64_64 (absolute 64-bit)
|
2026-07-16 20:52:20 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// elfSym is one symbol-table entry in construction.
|
|
|
|
|
|
type elfSym struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
info byte
|
|
|
|
|
|
shndx uint16
|
|
|
|
|
|
value uint64
|
|
|
|
|
|
size uint64
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ELFObject returns the image as an ELF64 relocatable object file, ready for
|
|
|
|
|
|
// the system linker. Symbol names are the TEXT and GLOBL identifiers as
|
|
|
|
|
|
// written (the middle dot stripped); a package prefix, when present, is
|
|
|
|
|
|
// joined with a dot. Every static-symbol reference becomes an
|
|
|
|
|
|
// R_X86_64_PC32 relocation, so the code is position-independent and links
|
|
|
|
|
|
// at any address.
|
|
|
|
|
|
func (img *Image) ELFObject() ([]byte, error) {
|
|
|
|
|
|
le := binary.LittleEndian
|
|
|
|
|
|
|
|
|
|
|
|
// Section indices: 0 NULL, 1 .text, 2 .data; the tables follow.
|
|
|
|
|
|
const (
|
|
|
|
|
|
secText = 1
|
|
|
|
|
|
secData = 2
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Build the symbol table: the null entry and the two section symbols
|
|
|
|
|
|
// come first, then the local symbols (static TEXT and GLOBL), then the
|
|
|
|
|
|
// globals (exported TEXT and GLOBL, and the undefined externals) — ELF
|
|
|
|
|
|
// requires every local to precede every global, and sh_info records the
|
|
|
|
|
|
// boundary. symIdx maps a symbol name to its index for the relocations.
|
|
|
|
|
|
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{
|
|
|
|
|
|
{}, // the mandatory null entry
|
|
|
|
|
|
{name: ".text", info: sttSection, shndx: secText},
|
|
|
|
|
|
{name: ".data", info: sttSection, shndx: secData},
|
|
|
|
|
|
}
|
|
|
|
|
|
syms = append(syms, locals...)
|
|
|
|
|
|
shInfo := len(syms) // first global symbol
|
|
|
|
|
|
syms = append(syms, globals...)
|
|
|
|
|
|
symIdx := map[string]int{}
|
|
|
|
|
|
for i, s := range syms {
|
|
|
|
|
|
symIdx[s.name] = i
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Build the relocations.
|
|
|
|
|
|
type elfRela struct {
|
|
|
|
|
|
off uint64
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
relas = append(relas, elfRela{
|
|
|
|
|
|
off: uint64(fn.Offset + r.Off),
|
|
|
|
|
|
sym: idx,
|
|
|
|
|
|
// R_X86_64_PC32 computes S + A − P with P the patch site; the
|
|
|
|
|
|
// assembler measures the symbol from the instruction end,
|
|
|
|
|
|
// After − Off bytes past the field, so the addend carries
|
|
|
|
|
|
// that distance with a negative sign.
|
|
|
|
|
|
addend: r.Addend - int64(r.After-r.Off),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Serialise the 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-08-21 00:14:02 +02:00
|
|
|
|
for _, n := range dwarfSectionNames {
|
|
|
|
|
|
stSections.add(n)
|
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
|
|
|
|
|
|
|
// Section presence: .rela.text only when there are relocations.
|
|
|
|
|
|
hasRela := len(relas) > 0
|
|
|
|
|
|
nSections := 6 // NULL, .text, .data, .symtab, .strtab, .shstrtab
|
|
|
|
|
|
if hasRela {
|
|
|
|
|
|
nSections = 7
|
|
|
|
|
|
}
|
|
|
|
|
|
secSymtab, secStrtab := 3, 4
|
|
|
|
|
|
secShstr := nSections - 1
|
|
|
|
|
|
|
|
|
|
|
|
// Lay the file out: header, section data, section headers.
|
|
|
|
|
|
var out []byte
|
|
|
|
|
|
out = append(out, make([]byte, 64)...) // ELF header, filled last
|
|
|
|
|
|
|
|
|
|
|
|
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 // st_other
|
|
|
|
|
|
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()...)
|
|
|
|
|
|
|
|
|
|
|
|
var relaOff int
|
|
|
|
|
|
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|rX8664PC32)
|
|
|
|
|
|
le.PutUint64(b[16:], uint64(r.addend))
|
|
|
|
|
|
out = append(out, b[:]...)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
shstrOff := len(out)
|
|
|
|
|
|
out = append(out, stSections.bytes()...)
|
|
|
|
|
|
|
2026-08-21 00:14:02 +02:00
|
|
|
|
// DWARF debug sections (no relocations — the linker resolves DWARF fixups).
|
|
|
|
|
|
dwAlign := func(n int) {
|
|
|
|
|
|
for len(out)%n != 0 {
|
|
|
|
|
|
out = append(out, 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign)
|
|
|
|
|
|
if dw != nil {
|
|
|
|
|
|
nSections += 4 // .debug_abbrev, .debug_info, .debug_line, .debug_line_str
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
|
align(8)
|
|
|
|
|
|
shoff := len(out)
|
|
|
|
|
|
|
|
|
|
|
|
// Section headers.
|
|
|
|
|
|
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) // sh_addr
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0)
|
|
|
|
|
|
|
2026-08-21 00:14:02 +02:00
|
|
|
|
// DWARF section headers.
|
|
|
|
|
|
if dw != nil {
|
|
|
|
|
|
putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0)
|
|
|
|
|
|
putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0)
|
|
|
|
|
|
putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0)
|
|
|
|
|
|
putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0)
|
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-08-21 00:14:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
|
// The 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:], emX8664)
|
|
|
|
|
|
le.PutUint32(hdr[20:], elfVersion)
|
|
|
|
|
|
le.PutUint64(hdr[24:], 0) // e_entry
|
|
|
|
|
|
le.PutUint64(hdr[32:], 0) // e_phoff
|
|
|
|
|
|
le.PutUint64(hdr[40:], uint64(shoff)) // e_shoff
|
|
|
|
|
|
le.PutUint32(hdr[48:], 0) // e_flags
|
|
|
|
|
|
le.PutUint16(hdr[52:], 64) // e_ehsize
|
|
|
|
|
|
le.PutUint16(hdr[54:], 0) // e_phentsize
|
|
|
|
|
|
le.PutUint16(hdr[56:], 0) // e_phnum
|
|
|
|
|
|
le.PutUint16(hdr[58:], 64) // e_shentsize
|
|
|
|
|
|
le.PutUint16(hdr[60:], uint16(nSections))
|
|
|
|
|
|
le.PutUint16(hdr[62:], uint16(secShstr))
|
|
|
|
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// objectName renders a symbol's object-file name: the identifier as written,
|
|
|
|
|
|
// with an explicit package prefix joined by a dot.
|
|
|
|
|
|
func objectName(pkg, name string) string {
|
|
|
|
|
|
if pkg == "" {
|
|
|
|
|
|
return name
|
|
|
|
|
|
}
|
|
|
|
|
|
return pkg + "." + name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// elfStrtab is an ELF string table under construction.
|
|
|
|
|
|
type elfStrtab struct {
|
|
|
|
|
|
buf []byte
|
|
|
|
|
|
off map[string]int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func newElfStrtab() *elfStrtab {
|
|
|
|
|
|
return &elfStrtab{buf: []byte{0}, off: map[string]int{"": 0}}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *elfStrtab) add(name string) {
|
|
|
|
|
|
if _, ok := s.off[name]; ok {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
s.off[name] = len(s.buf)
|
|
|
|
|
|
s.buf = append(s.buf, name...)
|
|
|
|
|
|
s.buf = append(s.buf, 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *elfStrtab) at(name string) int { return s.off[name] }
|
|
|
|
|
|
|
|
|
|
|
|
func (s *elfStrtab) bytes() []byte { return s.buf }
|