// Copyright (c) 2026 Petr Balvín (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) } }