696 lines
22 KiB
Go
696 lines
22 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package asm
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// This file emits GOOBJ, the Go toolchain's object format, which cmd/link
|
|
// consumes directly, so gasm-assembled functions drop into a go build
|
|
// without the Go assembler. The layout follows cmd/internal/goobj: a
|
|
// toolchain preamble ("go object ...\n!\n"), the go120ld header with its
|
|
// block offsets, a string table, symbol definitions, the relocation /
|
|
// aux / data index arrays, and the three blocks themselves.
|
|
//
|
|
// The object carries what the linker requires of an assembly object: the
|
|
// functions (non-package symbols, as cmd/asm emits them), the GLOBL data,
|
|
// one FuncInfo per function, the per-function DWARF symbols (the
|
|
// .debug_line program and the subprogram DIE, which the linker's DWARF
|
|
// pass reads verbatim), and the pc-value tables (pcsp, pcfile, pcline,
|
|
// pcinline). The implicit funcdata symbols are omitted; the linker fills
|
|
// their defaults.
|
|
//
|
|
// emitGOObject is architecture-agnostic; the per-architecture GOObject*
|
|
// methods supply the toolchain preamble, the MinLC (pc-value delta unit)
|
|
// and the relocation-type mapping for code relocations.
|
|
|
|
// GOOBJ block indices (cmd/internal/goobj). These MUST match the real
|
|
// archive layout: the emitter writes the header offsets per index and the
|
|
// reader (groundtruth, goobj_resolve) parses real Go archives with them.
|
|
// blkAutolib is unused by the emitter but still defines index 0.
|
|
const (
|
|
blkAutolib = iota
|
|
blkPkgIdx
|
|
blkFile
|
|
blkSymdef
|
|
blkHashed64def
|
|
blkHasheddef
|
|
blkNonpkgdef
|
|
blkNonpkgref
|
|
blkRefFlags
|
|
blkHash64
|
|
blkHash
|
|
blkRelocIdx
|
|
blkAuxIdx
|
|
blkDataIdx
|
|
blkReloc
|
|
blkAux
|
|
blkData
|
|
blkRefName
|
|
blkEnd
|
|
)
|
|
|
|
// Symbol kinds used by assembly objects (cmd/internal/objabi).
|
|
const (
|
|
kindSTEXT = 1
|
|
kindSRODATA = 3
|
|
kindSDATA = 7
|
|
kindSDWARFFCN = 14
|
|
kindSDWARFLINES = 20
|
|
)
|
|
|
|
// Symbol flags (cmd/internal/goobj).
|
|
const (
|
|
symFlagDupok = 0x01
|
|
symFlagNoSplit = 0x10
|
|
symFlag2Link = 0x10 // asm objects flag every named symbol as linkname
|
|
symABIStatic = 0xffff
|
|
)
|
|
|
|
// Aux entry types (cmd/internal/goobj).
|
|
const (
|
|
auxFuncInfo = 1
|
|
auxDwarfInfo = 3
|
|
auxDwarfLines = 6
|
|
auxPcsp = 7
|
|
auxPcfile = 8
|
|
auxPcline = 9
|
|
auxPcinline = 10
|
|
)
|
|
|
|
// FuncInfo flags (internal/abi).
|
|
const (
|
|
funcFlagSPWrite = 2
|
|
funcFlagAsm = 4
|
|
)
|
|
|
|
// Relocation types (cmd/internal/objabi).
|
|
// R_ADDR, R_CALL, R_PCREL and R_TLS_LE are stable across Go versions.
|
|
const (
|
|
relocAddr = 1 // R_ADDR
|
|
relocCall = 7 // R_CALL
|
|
relocPCRel = 14 // R_PCREL
|
|
relocTLSLE = 15 // R_TLS_LE
|
|
)
|
|
|
|
// relocDWTXTADDRU4 returns the R_DWTXTADDR_U4 relocation type for the
|
|
// installed Go toolchain. The value shifted between Go 1.26 (103) and
|
|
// Go 1.27 (106) because new LoongArch relocations were inserted before it.
|
|
func relocDWTXTADDRU4() uint16 {
|
|
if isGo127OrLater() {
|
|
return 106
|
|
}
|
|
return 103
|
|
}
|
|
|
|
var (
|
|
goVersionOnce sync.Once
|
|
goVersionGT26 bool
|
|
)
|
|
|
|
// isGo127OrLater reports whether the installed Go toolchain is 1.27 or later.
|
|
func isGo127OrLater() bool {
|
|
goVersionOnce.Do(func() {
|
|
goBin, err := exec.LookPath("go")
|
|
if err != nil {
|
|
return
|
|
}
|
|
out, err := exec.Command(goBin, "version").Output()
|
|
if err != nil {
|
|
return
|
|
}
|
|
// "go version go1.27rc1 linux/amd64"
|
|
s := string(out)
|
|
for _, prefix := range []string{"go version go1.27", "go version go1.28", "go version go1.29", "go version go2."} {
|
|
if strings.Contains(s, prefix) {
|
|
goVersionGT26 = true
|
|
return
|
|
}
|
|
}
|
|
})
|
|
return goVersionGT26
|
|
}
|
|
|
|
// Special package indices for symbol references.
|
|
const (
|
|
pkgIdxNone = 0x7fffffff
|
|
pkgIdxSelf = 0x7ffffffb
|
|
pkgIdxBuiltin = 0x7ffffffc
|
|
)
|
|
|
|
// goobjBuiltinMorestackNoctxt is the index of runtime.morestack_noctxt in
|
|
// cmd/internal/goobj/builtinlist.go of the toolchain the object targets
|
|
// (246 since Go 1.25; the list is append-only).
|
|
const goobjBuiltinMorestackNoctxt = 246
|
|
|
|
// goobjBuiltinMorestack is the builtin reference the toolchain emits for the
|
|
// stack-guard call.
|
|
var goobjBuiltinMorestack = "runtime\u00b7morestack_noctxt"
|
|
|
|
const goobjMagic = "\x00go120ld"
|
|
|
|
// goSym is one symbol definition under construction.
|
|
type goSym struct {
|
|
name string
|
|
abi uint16
|
|
typ uint8
|
|
flag uint8
|
|
flag2 uint8
|
|
size uint32
|
|
align uint32
|
|
}
|
|
|
|
func (s goSym) append(b []byte, strOff map[string]uint32) []byte {
|
|
b = binary.LittleEndian.AppendUint32(b, uint32(len(s.name)))
|
|
b = binary.LittleEndian.AppendUint32(b, strOff[s.name])
|
|
b = binary.LittleEndian.AppendUint16(b, s.abi)
|
|
b = append(b, s.typ, s.flag, s.flag2)
|
|
b = binary.LittleEndian.AppendUint32(b, s.size)
|
|
return binary.LittleEndian.AppendUint32(b, s.align)
|
|
}
|
|
|
|
// dwarfRelocSet attaches emitter-generated relocations (the DWARF
|
|
// lines/info symbols' address references) to a definition index.
|
|
type dwarfRelocSet struct {
|
|
si int
|
|
relocs []goobjReloc
|
|
}
|
|
|
|
// GOObject returns the image as a GOOBJ object file for the given package
|
|
// path (the linker qualifies the exported symbols with it, the way cmd/asm
|
|
// does with its -p flag). srcPath names the source file recorded in the
|
|
// object's file table and line tables. The toolchain's object preamble is
|
|
// captured from the installed go tool asm, so the output links with the
|
|
// toolchain it was produced on, exactly like a real assembly object.
|
|
func (img *Image) GOObject(pkgPath, srcPath string) ([]byte, error) {
|
|
pre, err := toolchainObjectPreamble()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// amd64: MinLC 1, R_PCREL for displacements, R_CALL for calls and
|
|
// R_TLS_LE for the stack-guard TLS load.
|
|
return img.emitGOObject(pkgPath, srcPath, pre, 1, func(r Reloc) (uint16, uint8) {
|
|
switch r.Kind {
|
|
case RelCall:
|
|
return relocCall, 4
|
|
case RelTLSLE:
|
|
return relocTLSLE, 4
|
|
default:
|
|
return relocPCRel, 4
|
|
}
|
|
})
|
|
}
|
|
|
|
// emitGOObject assembles the GOOBJ payload for any architecture. pre is
|
|
// the toolchain's object preamble; minLC is the architecture's minimum
|
|
// instruction length, the unit of the pc-value table deltas; relocField
|
|
// maps a code relocation to its objabi relocation type and the width of
|
|
// the instruction field the linker writes.
|
|
func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, relocField func(Reloc) (uint16, uint8)) ([]byte, error) {
|
|
if pkgPath == "" {
|
|
return nil, fmt.Errorf("GOOBJ emission requires a package path (-p)")
|
|
}
|
|
|
|
// The non-package definitions first, the DWARF symbols reference the
|
|
// functions by these indices: per function the four pc-value tables
|
|
// and the function itself, as cmd/asm lays them out.
|
|
type npSym struct {
|
|
sym goSym
|
|
data []byte
|
|
}
|
|
var nps []npSym
|
|
type pcRefs struct{ sp, file, line, inl int }
|
|
pcIdx := make([]pcRefs, len(img.Funcs))
|
|
fnNpIdx := make([]int, len(img.Funcs))
|
|
for i, fn := range img.Funcs {
|
|
tables := []struct {
|
|
data []byte
|
|
dst *int
|
|
}{
|
|
{pcspTable(fn, minLC), &pcIdx[i].sp},
|
|
{pcValueFlat(0, fn.Size, minLC), &pcIdx[i].file},
|
|
{pcValueFlat(int32(fn.Line), fn.Size, minLC), &pcIdx[i].line},
|
|
{pcValueFlat(-1, fn.Size, minLC), &pcIdx[i].inl},
|
|
}
|
|
for _, t := range tables {
|
|
*t.dst = len(nps)
|
|
nps = append(nps, npSym{
|
|
sym: goSym{typ: kindSRODATA, size: uint32(len(t.data)), align: 1},
|
|
data: t.data,
|
|
})
|
|
}
|
|
name := fn.Name
|
|
abi := uint16(0)
|
|
if fn.Static {
|
|
abi = symABIStatic
|
|
} else {
|
|
name = pkgPath + "." + name
|
|
}
|
|
flag := uint8(0)
|
|
if fn.NoSplit {
|
|
flag |= symFlagNoSplit
|
|
}
|
|
fnNpIdx[i] = len(nps)
|
|
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
|
for _, r := range fn.Relocs {
|
|
// Only the amd64 encoder resolves file-local static symbols
|
|
// into a disp32 field at assemble time; GOOBJ must leave that
|
|
// field zero for the linker to fill. The RISC-V and LoongArch
|
|
// encoders emit zero immediates with a relocation instead, and
|
|
// their relocations cover whole AUIPC/pcalau12i pairs, so
|
|
// zeroing r.Off would erase the opcode/register bits the linker
|
|
// preserves when it patches only the immediate.
|
|
if r.Kind != RelPCRel32 && r.Kind != RelCall {
|
|
continue
|
|
}
|
|
if r.Off >= 0 && r.Off+4 <= len(code) {
|
|
code[r.Off], code[r.Off+1], code[r.Off+2], code[r.Off+3] = 0, 0, 0, 0
|
|
}
|
|
}
|
|
nps = append(nps, npSym{
|
|
sym: goSym{name: name, abi: abi, typ: kindSTEXT, flag: flag, flag2: symFlag2Link, size: uint32(fn.Size)},
|
|
data: code,
|
|
})
|
|
}
|
|
|
|
// The package definitions: the GLOBL symbols, then, per function, the
|
|
// FuncInfo and the two DWARF symbols (the .debug_line program and the
|
|
// subprogram DIE). defIdx maps a GLOBL's bare name to its definition
|
|
// index for the code relocations.
|
|
var defs []goSym
|
|
var defData [][]byte
|
|
defIdx := map[string]int{}
|
|
for _, d := range img.DataSyms {
|
|
name := d.Name
|
|
if !d.Static {
|
|
name = pkgPath + "." + name
|
|
}
|
|
typ := uint8(kindSDATA)
|
|
if d.Rodata {
|
|
typ = kindSRODATA
|
|
}
|
|
flag := uint8(0)
|
|
if d.Dupok {
|
|
flag = symFlagDupok
|
|
}
|
|
abi := uint16(0)
|
|
if d.Static {
|
|
abi = symABIStatic
|
|
}
|
|
defIdx[d.Name] = len(defs)
|
|
defs = append(defs, goSym{name: name, abi: abi, typ: typ, flag: flag, flag2: symFlag2Link, size: uint32(d.Size)})
|
|
defData = append(defData, img.Data[d.Offset:d.Offset+d.Size])
|
|
}
|
|
fnFiIdx := make([]int, len(img.Funcs))
|
|
fnLinesIdx := make([]int, len(img.Funcs))
|
|
fnDIEIdx := make([]int, len(img.Funcs))
|
|
var dwarfRelocs []dwarfRelocSet
|
|
for i, fn := range img.Funcs {
|
|
data := marshalFuncInfo(fn)
|
|
fnFiIdx[i] = len(defs)
|
|
defs = append(defs, goSym{typ: kindSDATA, size: uint32(len(data))})
|
|
defData = append(defData, data)
|
|
|
|
name := fn.Name
|
|
if !fn.Static {
|
|
name = pkgPath + "." + name
|
|
}
|
|
|
|
// The DWARF symbols: the .debug_line state-machine program and the
|
|
// subprogram DIE, both referencing the function by its non-package
|
|
// index (package definitions, like cmd/asm's).
|
|
lines, lrel := goobjDwarfLines(fn, fnNpIdx[i])
|
|
fnLinesIdx[i] = len(defs)
|
|
defs = append(defs, goSym{typ: kindSDWARFLINES, size: uint32(len(lines))})
|
|
defData = append(defData, lines)
|
|
die, drel := goobjDwarfInfo(fn, name, fnNpIdx[i])
|
|
fnDIEIdx[i] = len(defs)
|
|
defs = append(defs, goSym{typ: kindSDWARFFCN, size: uint32(len(die))})
|
|
defData = append(defData, die)
|
|
dwarfRelocs = append(dwarfRelocs,
|
|
dwarfRelocSet{si: fnLinesIdx[i], relocs: lrel},
|
|
dwarfRelocSet{si: fnDIEIdx[i], relocs: drel},
|
|
)
|
|
}
|
|
|
|
// Index the non-package TEXT definitions by short name for the internal
|
|
// call references.
|
|
textNpIdx := map[string]int{}
|
|
for i, fn := range img.Funcs {
|
|
textNpIdx[fn.Name] = fnNpIdx[i]
|
|
}
|
|
|
|
// Resolve external symbol references (cross-package). Build the
|
|
// package index table and determine each external symbol's SymIdx
|
|
// by reading the target package's export data.
|
|
var extPkgTable []string
|
|
var extPkgIdx map[string]int
|
|
var extSymIdx map[string]int
|
|
if len(img.Externals) > 0 {
|
|
// The morestack call is a builtin reference, not a resolved external.
|
|
var need []string
|
|
for _, n := range img.Externals {
|
|
if n == goobjBuiltinMorestack {
|
|
continue
|
|
}
|
|
need = append(need, n)
|
|
}
|
|
if len(need) > 0 {
|
|
var err error
|
|
extPkgTable, extPkgIdx, extSymIdx, err = resolveExternalSymbols(need)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GOOBJ emission: resolving external symbols: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relocations, per defined symbol in definition order (package defs,
|
|
// then non-package defs).
|
|
nsyms := len(defs) + len(nps)
|
|
symRelocs := make([][]byte, nsyms) // flat 23-byte records
|
|
for i, fn := range img.Funcs {
|
|
si := len(defs) + fnNpIdx[i]
|
|
for _, r := range fn.Relocs {
|
|
typ, size := relocField(r)
|
|
if r.Kind == RelTLSLE {
|
|
// The TLS load has no symbol: {0, 0} is the nil ref.
|
|
var rec [23]byte
|
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
|
rec[4] = size
|
|
binary.LittleEndian.PutUint16(rec[5:], typ)
|
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
|
binary.LittleEndian.PutUint32(rec[15:], 0)
|
|
binary.LittleEndian.PutUint32(rec[19:], 0)
|
|
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
|
continue
|
|
}
|
|
if r.External && r.Kind == RelCall && r.Name == goobjBuiltinMorestack {
|
|
// The stack-guard morestack call uses the toolchain's
|
|
// builtin reference.
|
|
var rec [23]byte
|
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
|
rec[4] = size
|
|
binary.LittleEndian.PutUint16(rec[5:], typ)
|
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
|
binary.LittleEndian.PutUint32(rec[15:], pkgIdxBuiltin)
|
|
binary.LittleEndian.PutUint32(rec[19:], goobjBuiltinMorestackNoctxt)
|
|
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
|
continue
|
|
}
|
|
if r.External {
|
|
// Split package-qualified name: "runtime·morestack" → runtime, morestack.
|
|
pkg, name := splitQualified(r.Name)
|
|
if pkg == "" {
|
|
return nil, fmt.Errorf("GOOBJ emission: external symbol %q has no package prefix", r.Name)
|
|
}
|
|
pIdx, ok := extPkgIdx[pkg]
|
|
if !ok {
|
|
return nil, fmt.Errorf("GOOBJ emission: package %q not resolved", pkg)
|
|
}
|
|
sIdx, ok := extSymIdx[pkg+"·"+name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("GOOBJ emission: symbol %s·%s not resolved", pkg, name)
|
|
}
|
|
var rec [23]byte
|
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
|
rec[4] = size // field width
|
|
binary.LittleEndian.PutUint16(rec[5:], typ)
|
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
|
binary.LittleEndian.PutUint32(rec[15:], uint32(pIdx))
|
|
binary.LittleEndian.PutUint32(rec[19:], uint32(sIdx))
|
|
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
|
continue
|
|
}
|
|
pkg := uint32(pkgIdxSelf)
|
|
di, ok := defIdx[r.Name]
|
|
if !ok {
|
|
// A call to a TEXT function of the same file references the
|
|
// non-package definition table.
|
|
ni, isText := textNpIdx[r.Name]
|
|
if !isText || r.Kind != RelCall {
|
|
return nil, fmt.Errorf("GOOBJ emission: reference to unknown symbol %q", r.Name)
|
|
}
|
|
pkg = pkgIdxNone
|
|
di = ni
|
|
}
|
|
var rec [23]byte
|
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
|
rec[4] = size // field width
|
|
binary.LittleEndian.PutUint16(rec[5:], typ)
|
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
|
binary.LittleEndian.PutUint32(rec[15:], pkg)
|
|
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
|
|
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
|
}
|
|
}
|
|
// The DWARF symbols' own relocations (the function address references).
|
|
for _, ds := range dwarfRelocs {
|
|
for _, r := range ds.relocs {
|
|
var rec [23]byte
|
|
binary.LittleEndian.PutUint32(rec[0:], uint32(r.off))
|
|
rec[4] = r.siz
|
|
binary.LittleEndian.PutUint16(rec[5:], r.typ)
|
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.add))
|
|
binary.LittleEndian.PutUint32(rec[15:], r.pkg)
|
|
binary.LittleEndian.PutUint32(rec[19:], r.sym)
|
|
symRelocs[ds.si] = append(symRelocs[ds.si], rec[:]...)
|
|
}
|
|
}
|
|
|
|
// Aux entries per function: FuncInfo, the DWARF symbols, then the four
|
|
// pc tables. References into the non-package table use pkgIdxNone.
|
|
symAux := make([][]byte, nsyms)
|
|
for i := range img.Funcs {
|
|
si := len(defs) + fnNpIdx[i]
|
|
aux := func(typ uint8, pkg, idx uint32) {
|
|
var rec [9]byte
|
|
rec[0] = typ
|
|
binary.LittleEndian.PutUint32(rec[1:], pkg)
|
|
binary.LittleEndian.PutUint32(rec[5:], idx)
|
|
symAux[si] = append(symAux[si], rec[:]...)
|
|
}
|
|
aux(auxFuncInfo, pkgIdxSelf, uint32(fnFiIdx[i]))
|
|
aux(auxDwarfInfo, pkgIdxSelf, uint32(fnDIEIdx[i]))
|
|
aux(auxDwarfLines, pkgIdxSelf, uint32(fnLinesIdx[i]))
|
|
// The pc-table references are 0-based within the non-package
|
|
// definitions; the loader adds the package-definition count itself.
|
|
aux(auxPcsp, pkgIdxNone, uint32(pcIdx[i].sp))
|
|
aux(auxPcfile, pkgIdxNone, uint32(pcIdx[i].file))
|
|
aux(auxPcline, pkgIdxNone, uint32(pcIdx[i].line))
|
|
aux(auxPcinline, pkgIdxNone, uint32(pcIdx[i].inl))
|
|
}
|
|
|
|
// The string table. Absolute offsets: it starts right after the
|
|
// 96-byte header (magic, fingerprint, flags, the 19 block offsets).
|
|
const headerSize = 8 + 8 + 4 + 4*(blkEnd+1)
|
|
var strTab []byte
|
|
strOff := map[string]uint32{}
|
|
addStr := func(s string) {
|
|
if _, ok := strOff[s]; ok {
|
|
return
|
|
}
|
|
strOff[s] = uint32(headerSize + len(strTab))
|
|
strTab = append(strTab, s...)
|
|
}
|
|
addStr("")
|
|
addStr(srcPath)
|
|
for _, s := range defs {
|
|
addStr(s.name)
|
|
}
|
|
for _, s := range nps {
|
|
addStr(s.sym.name)
|
|
}
|
|
stringRef := func(b []byte, s string) []byte {
|
|
b = binary.LittleEndian.AppendUint32(b, uint32(len(s)))
|
|
return binary.LittleEndian.AppendUint32(b, strOff[s])
|
|
}
|
|
|
|
// Serialise the block bodies.
|
|
var symdefBlk, npdefBlk []byte
|
|
for _, s := range defs {
|
|
symdefBlk = s.append(symdefBlk, strOff)
|
|
}
|
|
for _, s := range nps {
|
|
npdefBlk = s.sym.append(npdefBlk, strOff)
|
|
}
|
|
|
|
// Package index table: index 0 is the dummy invalid package.
|
|
// External packages follow, in pkgIdx order.
|
|
for _, pkg := range extPkgTable {
|
|
addStr(pkg)
|
|
}
|
|
pkgIdxBlk := stringRef(nil, "") // index 0: dummy
|
|
for _, pkg := range extPkgTable {
|
|
pkgIdxBlk = stringRef(pkgIdxBlk, pkg)
|
|
}
|
|
fileBlk := stringRef(nil, srcPath)
|
|
|
|
var relocBlk, auxBlk, dataBlk []byte
|
|
relocIdxBlk := make([]byte, 0, 4*(nsyms+1))
|
|
auxIdxBlk := make([]byte, 0, 4*(nsyms+1))
|
|
dataIdxBlk := make([]byte, 0, 4*(nsyms+1))
|
|
var nr, na, nd uint32
|
|
for si := range nsyms {
|
|
relocIdxBlk = binary.LittleEndian.AppendUint32(relocIdxBlk, nr)
|
|
auxIdxBlk = binary.LittleEndian.AppendUint32(auxIdxBlk, na)
|
|
dataIdxBlk = binary.LittleEndian.AppendUint32(dataIdxBlk, nd)
|
|
relocBlk = append(relocBlk, symRelocs[si]...)
|
|
auxBlk = append(auxBlk, symAux[si]...)
|
|
var d []byte
|
|
if si < len(defData) {
|
|
d = defData[si]
|
|
} else {
|
|
d = nps[si-len(defData)].data
|
|
}
|
|
dataBlk = append(dataBlk, d...)
|
|
nr += uint32(len(symRelocs[si])) / 23
|
|
na += uint32(len(symAux[si])) / 9
|
|
nd += uint32(len(d))
|
|
}
|
|
relocIdxBlk = binary.LittleEndian.AppendUint32(relocIdxBlk, nr)
|
|
auxIdxBlk = binary.LittleEndian.AppendUint32(auxIdxBlk, na)
|
|
dataIdxBlk = binary.LittleEndian.AppendUint32(dataIdxBlk, nd)
|
|
|
|
blocks := [blkEnd][]byte{
|
|
blkPkgIdx: pkgIdxBlk,
|
|
blkFile: fileBlk,
|
|
blkSymdef: symdefBlk,
|
|
blkNonpkgdef: npdefBlk,
|
|
blkRelocIdx: relocIdxBlk,
|
|
blkAuxIdx: auxIdxBlk,
|
|
blkDataIdx: dataIdxBlk,
|
|
blkReloc: relocBlk,
|
|
blkAux: auxBlk,
|
|
blkData: dataBlk,
|
|
}
|
|
|
|
// Assemble the payload: header (offsets filled once known), string
|
|
// table, blocks in order.
|
|
payload := make([]byte, headerSize)
|
|
copy(payload, goobjMagic)
|
|
// The fingerprint stays zero, as cmd/asm leaves it.
|
|
binary.LittleEndian.PutUint32(payload[16:], 4) // ObjFlagFromAssembly
|
|
off := uint32(headerSize + len(strTab))
|
|
for i := range blkEnd {
|
|
binary.LittleEndian.PutUint32(payload[20+4*i:], off)
|
|
off += uint32(len(blocks[i]))
|
|
}
|
|
binary.LittleEndian.PutUint32(payload[20+4*blkEnd:], off)
|
|
payload = append(payload, strTab...)
|
|
for _, blk := range blocks {
|
|
payload = append(payload, blk...)
|
|
}
|
|
|
|
out := make([]byte, 0, len(pre)+len(payload))
|
|
out = append(out, pre...)
|
|
return append(out, payload...), nil
|
|
}
|
|
|
|
// marshalFuncInfo serialises a function's goobj.FuncInfo: sizes, flags,
|
|
// start line, the one-element file table and an empty inline tree.
|
|
func marshalFuncInfo(fn FuncLayout) []byte {
|
|
flag := uint8(funcFlagAsm)
|
|
if fn.SPWrite {
|
|
flag |= funcFlagSPWrite
|
|
}
|
|
b := make([]byte, 0, 28)
|
|
b = binary.LittleEndian.AppendUint32(b, uint32(fn.Args))
|
|
b = binary.LittleEndian.AppendUint32(b, uint32(fn.Frame))
|
|
b = append(b, 0, flag, 0, 0) // FuncID normal, flags, padding
|
|
b = binary.LittleEndian.AppendUint32(b, uint32(int32(fn.Line)))
|
|
b = binary.LittleEndian.AppendUint32(b, 1) // one file
|
|
b = binary.LittleEndian.AppendUint32(b, 0) // file index 0
|
|
b = binary.LittleEndian.AppendUint32(b, 0) // no inline tree
|
|
return b
|
|
}
|
|
|
|
// pcValueFlat encodes a pc-value table holding v over the whole function.
|
|
// The pc deltas are in MinLC units (the runtime scales them by the
|
|
// architecture's minimum instruction length).
|
|
func pcValueFlat(v int32, size, minLC int) []byte {
|
|
// The table is delta-encoded from an implicit value of -1: a varint
|
|
// value delta, an unsigned pc delta to the end, and a zero terminator.
|
|
out := binary.AppendVarint(nil, int64(v)+1)
|
|
out = binary.AppendUvarint(out, uint64(size/minLC))
|
|
return append(out, 0)
|
|
}
|
|
|
|
// pcspTable encodes the stack-adjustment table: the SP delta in effect at
|
|
// every pc, from the function's prologue and epilogue boundaries.
|
|
func pcspTable(fn FuncLayout, minLC int) []byte {
|
|
if len(fn.Spadj) == 0 {
|
|
return pcValueFlat(0, fn.Size, minLC)
|
|
}
|
|
pts := make([]SpadjStep, 0, len(fn.Spadj)+1)
|
|
pts = append(pts, SpadjStep{PC: 0, Value: 0})
|
|
pts = append(pts, fn.Spadj...)
|
|
out := binary.AppendVarint(nil, int64(pts[0].Value)+1)
|
|
cur, old := pts[0].PC, pts[0].Value
|
|
for _, p := range pts[1:] {
|
|
out = binary.AppendUvarint(out, uint64((p.PC-cur)/minLC))
|
|
out = binary.AppendVarint(out, int64(p.Value-old))
|
|
cur, old = p.PC, p.Value
|
|
}
|
|
out = binary.AppendUvarint(out, uint64((fn.Size-cur)/minLC))
|
|
return append(out, 0)
|
|
}
|
|
|
|
// toolchainObjectPreamble returns the "go object ...\n!\n" header the
|
|
// installed go tool asm writes, captured by assembling a one-instruction
|
|
// probe. The linker compares this string verbatim against its own, so it
|
|
// must come from the toolchain itself, not be reconstructed.
|
|
var (
|
|
preambleOnce sync.Once
|
|
preamble []byte
|
|
preambleErr error
|
|
)
|
|
|
|
func toolchainObjectPreamble() ([]byte, error) {
|
|
preambleOnce.Do(func() {
|
|
goBin, err := exec.LookPath("go")
|
|
if err != nil {
|
|
preambleErr = fmt.Errorf("GOOBJ emission needs the Go toolchain: %w", err)
|
|
return
|
|
}
|
|
dir, err := os.MkdirTemp("", "gasm-preamble")
|
|
if err != nil {
|
|
preambleErr = err
|
|
return
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
src := filepath.Join(dir, "probe_amd64.s")
|
|
if err := os.WriteFile(src, []byte("TEXT \u00b7x(SB), $0-0\n\tRET\n"), 0o644); err != nil {
|
|
preambleErr = err
|
|
return
|
|
}
|
|
obj := filepath.Join(dir, "probe.o")
|
|
cmd := exec.Command(goBin, "tool", "asm", "-p", "probe", "-o", obj, src)
|
|
cmd.Env = append(os.Environ(), "GOARCH=amd64")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
preambleErr = fmt.Errorf("probing the assembler for the object header: %v\n%s", err, out)
|
|
return
|
|
}
|
|
data, err := os.ReadFile(obj)
|
|
if err != nil {
|
|
preambleErr = err
|
|
return
|
|
}
|
|
i := bytes.Index(data, []byte("\n!\n"))
|
|
if i < 0 || !bytes.HasPrefix(data[i+3:], []byte(goobjMagic)) {
|
|
preambleErr = fmt.Errorf("unrecognised assembler object layout")
|
|
return
|
|
}
|
|
preamble = data[:i+3]
|
|
})
|
|
return preamble, preambleErr
|
|
}
|