128 lines
3.8 KiB
Go
128 lines
3.8 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
||
package asm
|
||
|
||
import (
|
||
"bytes"
|
||
"debug/macho"
|
||
"encoding/binary"
|
||
"testing"
|
||
)
|
||
|
||
// TestMachOObject checks the structure of the emitted MH_OBJECT: the two
|
||
// sections and their addresses, the symbol table (types, sections, values)
|
||
// and the __text relocation entries, parsed back with debug/macho. No
|
||
// Darwin toolchain is available on the test hosts, so the check is
|
||
// structural — the ELF output carries the end-to-end link-and-run proof of
|
||
// the shared symbol and relocation model.
|
||
func TestMachOObject(t *testing.T) {
|
||
img := elfTestImage(t)
|
||
obj, err := img.MachOObject()
|
||
if err != nil {
|
||
t.Fatalf("MachOObject: %v", err)
|
||
}
|
||
f, err := macho.NewFile(bytes.NewReader(obj))
|
||
if err != nil {
|
||
t.Fatalf("parse emitted object: %v", err)
|
||
}
|
||
defer f.Close()
|
||
|
||
if f.Type != macho.TypeObj {
|
||
t.Errorf("file type = %v, want MH_OBJECT", f.Type)
|
||
}
|
||
if f.Cpu != macho.CpuAmd64 {
|
||
t.Errorf("cpu = %v, want CpuAmd64", f.Cpu)
|
||
}
|
||
|
||
text := f.Section("__text")
|
||
data := f.Section("__data")
|
||
if text == nil || data == nil {
|
||
t.Fatal("missing __text or __data section")
|
||
}
|
||
if text.Addr != 0 || text.Size != uint64(len(img.Code)) {
|
||
t.Errorf("__text addr/size = %#x/%d, want 0/%d", text.Addr, text.Size, len(img.Code))
|
||
}
|
||
if data.Addr != uint64(len(img.Code)) {
|
||
t.Errorf("__data addr = %#x, want %#x", data.Addr, len(img.Code))
|
||
}
|
||
|
||
// Symbol table: locals, exported definitions, undefined externals.
|
||
syms := f.Symtab.Syms
|
||
byName := map[string]macho.Symbol{}
|
||
for _, s := range syms {
|
||
byName[s.Name] = s
|
||
}
|
||
wantSym := func(name string, typ, sect uint8, value uint64) {
|
||
t.Helper()
|
||
s, ok := byName[name]
|
||
if !ok {
|
||
t.Errorf("symbol %q not found", name)
|
||
return
|
||
}
|
||
if s.Type != typ || s.Sect != sect || s.Value != value {
|
||
t.Errorf("%s: type/sect/value = %#x/%d/%#x, want %#x/%d/%#x",
|
||
name, s.Type, s.Sect, s.Value, typ, sect, value)
|
||
}
|
||
}
|
||
const (
|
||
defined = nSect | nExt
|
||
local = nSect
|
||
undefined = nUndf | nExt
|
||
)
|
||
wantSym("addq", defined, 1, 0)
|
||
wantSym("getanswer", defined, 1, 5)
|
||
wantSym("useextern", defined, 1, 13)
|
||
answer := byName["answer"]
|
||
if answer.Type != local || answer.Sect != 2 {
|
||
t.Errorf("answer: type/sect = %#x/%d, want %#x/2", answer.Type, answer.Sect, local)
|
||
}
|
||
wantSym("extvar", undefined, 0, 0)
|
||
|
||
// Relocations: both X86_64_RELOC_SIGNED, PC-relative, 4 bytes wide.
|
||
// The local one carries its section number in Value, the external one
|
||
// its symbol number.
|
||
if len(text.Relocs) != 2 {
|
||
t.Fatalf("__text relocs = %d, want 2", len(text.Relocs))
|
||
}
|
||
var sawLocal, sawExternal bool
|
||
for _, r := range text.Relocs {
|
||
if !r.Pcrel || r.Len != 2 || r.Type != x8664RelocSigned {
|
||
t.Errorf("reloc at %#x: pcrel/len/type = %v/%d/%d", r.Addr, r.Pcrel, r.Len, r.Type)
|
||
}
|
||
switch {
|
||
case r.Extern:
|
||
if name := syms[r.Value].Name; name != "extvar" {
|
||
t.Errorf("external reloc at %#x names %q, want extvar", r.Addr, name)
|
||
}
|
||
sawExternal = true
|
||
default:
|
||
if r.Value != 2 { // __data, the second section
|
||
t.Errorf("local reloc at %#x: section %d, want 2 (__data)", r.Addr, r.Value)
|
||
}
|
||
sawLocal = true
|
||
}
|
||
}
|
||
if !sawLocal || !sawExternal {
|
||
t.Errorf("relocs seen: local=%v external=%v, want both", sawLocal, sawExternal)
|
||
}
|
||
|
||
// The __text bytes are the image code, with the external displacement
|
||
// primed to addend − 4 and the local one left resolved.
|
||
textData, err := text.Data()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
want := append([]byte(nil), img.Code...)
|
||
for _, fn := range img.Funcs {
|
||
for _, r := range fn.Relocs {
|
||
if r.Name == "extvar" {
|
||
binary.LittleEndian.PutUint32(want[fn.Offset+r.Off:], 0xfffffffc) // −4
|
||
}
|
||
}
|
||
}
|
||
if !bytes.Equal(textData, want) {
|
||
t.Errorf("__text bytes %x, want %x", textData, want)
|
||
}
|
||
}
|