feat(asm): emit linkable ELF and Mach-O objects with external symbols
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+109
-39
@@ -5,27 +5,58 @@ package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
)
|
||||
|
||||
// Image is an assembled file: the function bodies laid out in source order,
|
||||
// followed by the file's static data section (GLOBL/DATA). Static-symbol
|
||||
// references are encoded RIP-relative and resolved within the image, so the
|
||||
// bytes are self-consistent and executable at any base address.
|
||||
// followed by the file's static data section (GLOBL/DATA). References to
|
||||
// file-local static symbols are encoded RIP-relative and resolved within the
|
||||
// image, so the raw bytes are self-consistent and executable at any base
|
||||
// address; references to external symbols are recorded as relocations
|
||||
// (Funcs[i].Relocs, Externals) and left unresolved — the object-file
|
||||
// emitters turn them into linker relocations.
|
||||
type Image struct {
|
||||
Code []byte // concatenated function bodies
|
||||
Data []byte // static data section
|
||||
Funcs []FuncLayout // function positions, in source order
|
||||
Symbols map[string]int // static symbol → byte offset within the image
|
||||
Code []byte // concatenated function bodies
|
||||
Data []byte // static data section
|
||||
Funcs []FuncLayout // function positions, in source order
|
||||
Symbols map[string]int // static symbol → byte offset within the image
|
||||
DataSyms []DataSymbol // GLOBL symbols, in layout order
|
||||
Externals []string // referenced but undefined symbols, sorted
|
||||
}
|
||||
|
||||
// FuncLayout describes one assembled function within an Image.
|
||||
type FuncLayout struct {
|
||||
Name string
|
||||
Offset int // start offset within the image (== offset within Code)
|
||||
Pkg string // explicit package prefix ("" = the current package)
|
||||
Static bool // the <> marker: file-local, not exported
|
||||
Offset int // start offset within the image (== offset within Code)
|
||||
Size int
|
||||
Labels map[string]int // local labels, function-relative
|
||||
Relocs []Reloc // static-symbol references, in emission order
|
||||
}
|
||||
|
||||
// Reloc is one static-symbol reference within a function body: the disp32
|
||||
// field at Off (function-relative) must reach the symbol plus Addend,
|
||||
// measured from After, the address just past the instruction. An External
|
||||
// relocation names a symbol no GLOBL in the file defines; the object-file
|
||||
// emitters carry it into the output's relocation table.
|
||||
type Reloc struct {
|
||||
Off int
|
||||
After int
|
||||
Name string
|
||||
Addend int64
|
||||
External bool
|
||||
}
|
||||
|
||||
// DataSymbol describes one GLOBL symbol laid out in the data section.
|
||||
type DataSymbol struct {
|
||||
Name string
|
||||
Pkg string // explicit package prefix ("" = the current package)
|
||||
Offset int // byte offset within Data
|
||||
Size int
|
||||
Static bool // the <> marker: file-local, not exported
|
||||
}
|
||||
|
||||
// Bytes returns the whole image: code, then data.
|
||||
@@ -37,19 +68,21 @@ func (img *Image) Bytes() []byte {
|
||||
|
||||
// AssembleFile assembles every TEXT function of a parsed file and lays out
|
||||
// its static symbols (GLOBL/DATA) in a data section behind the code. Each
|
||||
// static-symbol reference becomes a RIP-relative load whose displacement is
|
||||
// resolved against that layout. External (non-file-local) symbol references
|
||||
// are rejected: they need object-file emission.
|
||||
// reference to a file-local static symbol becomes a RIP-relative load whose
|
||||
// displacement is resolved against that layout; a reference to a symbol no
|
||||
// GLOBL defines is recorded as an external relocation (Externals) with its
|
||||
// displacement left zero — the object-file emitters resolve it at link
|
||||
// time, while the raw image (Bytes) cannot represent it.
|
||||
func AssembleFile(f *ast.File) (*Image, error) {
|
||||
syms, order, err := collectData(f)
|
||||
dataSyms, err := collectData(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
known := make(map[string]bool, len(syms))
|
||||
for name := range syms {
|
||||
known[name] = true
|
||||
known := make(map[string]bool, len(dataSyms))
|
||||
for _, d := range dataSyms {
|
||||
known[d.name] = true
|
||||
}
|
||||
link := &linkInfo{symbols: known}
|
||||
link := &linkInfo{symbols: known, allowExternal: true}
|
||||
|
||||
img := &Image{Symbols: map[string]int{}}
|
||||
type asmFunc struct {
|
||||
@@ -68,6 +101,8 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
||||
}
|
||||
img.Funcs = append(img.Funcs, FuncLayout{
|
||||
Name: t.Name.Name,
|
||||
Pkg: t.Name.Pkg,
|
||||
Static: t.Name.Static,
|
||||
Offset: len(img.Code),
|
||||
Size: len(code),
|
||||
Labels: labels,
|
||||
@@ -78,34 +113,63 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
||||
|
||||
// Lay out the data section behind the code, each symbol 16-aligned.
|
||||
dataStart := len(img.Code)
|
||||
for _, name := range order {
|
||||
for _, d := range dataSyms {
|
||||
if pos := dataStart + len(img.Data); pos != align16(pos) {
|
||||
img.Data = append(img.Data, make([]byte, align16(pos)-pos)...)
|
||||
}
|
||||
img.Symbols[name] = dataStart + len(img.Data)
|
||||
img.Data = append(img.Data, syms[name]...)
|
||||
img.Symbols[d.name] = dataStart + len(img.Data)
|
||||
img.DataSyms = append(img.DataSyms, DataSymbol{
|
||||
Name: d.name,
|
||||
Pkg: d.pkg,
|
||||
Offset: len(img.Data),
|
||||
Size: len(d.buf),
|
||||
Static: d.static,
|
||||
})
|
||||
img.Data = append(img.Data, d.buf...)
|
||||
}
|
||||
|
||||
// Resolve the RIP-relative displacements now that every address is known.
|
||||
// Resolve the RIP-relative displacements of file-local references now
|
||||
// that every address is known, and record every reference (resolved or
|
||||
// external) for the object-file emitters.
|
||||
externals := map[string]bool{}
|
||||
for i, fn := range funcs {
|
||||
base := img.Funcs[i].Offset
|
||||
code := img.Code[base : base+img.Funcs[i].Size]
|
||||
for _, p := range fn.patches {
|
||||
rel := int64(img.Symbols[p.name]) + p.addend - int64(base+p.after)
|
||||
if rel < -1<<31 || rel >= 1<<31 {
|
||||
return nil, fmt.Errorf("%s: displacement to %q out of rel32 range", fn.name, p.name)
|
||||
reloc := Reloc{Off: p.off, After: p.after, Name: p.name, Addend: p.addend}
|
||||
if imgOff, ok := img.Symbols[p.name]; ok {
|
||||
rel := int64(imgOff) + p.addend - int64(base+p.after)
|
||||
if rel < -1<<31 || rel >= 1<<31 {
|
||||
return nil, fmt.Errorf("%s: displacement to %q out of rel32 range", fn.name, p.name)
|
||||
}
|
||||
copy(code[p.off:p.off+4], le32(rel))
|
||||
} else {
|
||||
reloc.External = true
|
||||
externals[p.name] = true
|
||||
}
|
||||
copy(code[p.off:p.off+4], le32(rel))
|
||||
img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc)
|
||||
}
|
||||
}
|
||||
for name := range externals {
|
||||
img.Externals = append(img.Externals, name)
|
||||
}
|
||||
sort.Strings(img.Externals)
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// dataSym is one GLOBL symbol and its DATA initialiser.
|
||||
type dataSym struct {
|
||||
name string
|
||||
pkg string
|
||||
buf []byte
|
||||
static bool
|
||||
}
|
||||
|
||||
// collectData gathers the file's static symbols (GLOBL) and their initial
|
||||
// contents (DATA) into byte buffers, in declaration order.
|
||||
func collectData(f *ast.File) (map[string][]byte, []string, error) {
|
||||
syms := map[string][]byte{}
|
||||
var order []string
|
||||
func collectData(f *ast.File) ([]dataSym, error) {
|
||||
index := map[string]int{}
|
||||
var syms []dataSym
|
||||
for _, d := range f.Decls {
|
||||
switch dd := d.(type) {
|
||||
case *ast.Globl:
|
||||
@@ -113,47 +177,53 @@ func collectData(f *ast.File) (map[string][]byte, []string, error) {
|
||||
continue
|
||||
}
|
||||
name := dd.Name.Name
|
||||
if _, dup := syms[name]; dup {
|
||||
return nil, nil, fmt.Errorf("duplicate GLOBL %q", name)
|
||||
if _, dup := index[name]; dup {
|
||||
return nil, fmt.Errorf("duplicate GLOBL %q", name)
|
||||
}
|
||||
size := 0
|
||||
if dd.Size != nil && dd.Size.Imm.HasVal {
|
||||
size = int(dd.Size.Imm.Val)
|
||||
}
|
||||
syms[name] = make([]byte, size)
|
||||
order = append(order, name)
|
||||
index[name] = len(syms)
|
||||
syms = append(syms, dataSym{
|
||||
name: name,
|
||||
pkg: dd.Name.Pkg,
|
||||
buf: make([]byte, size),
|
||||
static: dd.Name.Static,
|
||||
})
|
||||
|
||||
case *ast.Data:
|
||||
if dd.Name == nil || dd.Name.Pseudo != "SB" {
|
||||
continue
|
||||
}
|
||||
buf, ok := syms[dd.Name.Name]
|
||||
i, ok := index[dd.Name.Name]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
|
||||
return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
|
||||
}
|
||||
if dd.Value == nil || !dd.Value.Imm.HasVal {
|
||||
return nil, nil, fmt.Errorf("DATA %q: value must be an integer immediate", dd.Name.Name)
|
||||
return nil, fmt.Errorf("DATA %q: value must be an integer immediate", dd.Name.Name)
|
||||
}
|
||||
w := dd.Width
|
||||
switch w {
|
||||
case 1, 2, 4, 8:
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
||||
return nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
||||
}
|
||||
off := dd.Name.Offset
|
||||
buf := syms[i].buf
|
||||
if off < 0 || off+int64(w) > int64(len(buf)) {
|
||||
return nil, nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf))
|
||||
return nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf))
|
||||
}
|
||||
v := dd.Value.Imm.Val
|
||||
if dd.Value.Imm.Neg {
|
||||
v = -v
|
||||
}
|
||||
for i := 0; i < w; i++ {
|
||||
buf[off+int64(i)] = byte(v >> (8 * i))
|
||||
for j := 0; j < w; j++ {
|
||||
buf[off+int64(j)] = byte(v >> (8 * j))
|
||||
}
|
||||
}
|
||||
}
|
||||
return syms, order, nil
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
// align16 rounds n up to the next multiple of 16.
|
||||
|
||||
Reference in New Issue
Block a user