]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: Mach-O (OS X) file formatter
authorRuss Cox <rsc@golang.org>
Fri, 10 Jan 2014 00:29:29 +0000 (19:29 -0500)
committerRuss Cox <rsc@golang.org>
Fri, 10 Jan 2014 00:29:29 +0000 (19:29 -0500)
See CL 48870044 for basic structure.

R=iant
CC=golang-codereviews
https://golang.org/cl/48910043

src/cmd/link/macho.go [new file with mode: 0644]
src/cmd/link/macho_test.go [new file with mode: 0644]
src/cmd/link/testdata/macho.amd64.exit9 [new file with mode: 0755]
src/cmd/link/testdata/macho.amd64.hello [new file with mode: 0755]
src/cmd/link/testdata/macho.amd64.helloro [new file with mode: 0755]

diff --git a/src/cmd/link/macho.go b/src/cmd/link/macho.go
new file mode 100644 (file)
index 0000000..9418bea
--- /dev/null
@@ -0,0 +1,385 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Mach-O (Darwin) object file writing.
+
+package main
+
+import (
+       "debug/macho"
+       "encoding/binary"
+       "io"
+       "strings"
+)
+
+// machoFormat is the implementation of formatter.
+type machoFormat struct{}
+
+// machoHeader and friends are data structures
+// corresponding to the Mach-O file header
+// to be written to disk.
+
+const (
+       macho64Bit     = 1 << 24
+       machoSubCPU386 = 3
+)
+
+// machoArch describes a Mach-O target architecture.
+type machoArch struct {
+       CPU    uint32
+       SubCPU uint32
+}
+
+// machoHeader is the Mach-O file header.
+type machoHeader struct {
+       machoArch
+       FileType uint32
+       Loads    []*machoLoad
+       Segments []*machoSegment
+       p        *Prog // for reporting errors
+}
+
+// machoLoad is a Mach-O load command.
+type machoLoad struct {
+       Type uint32
+       Data []uint32
+}
+
+// machoSegment is a Mach-O segment.
+type machoSegment struct {
+       Name       string
+       VirtAddr   Addr
+       VirtSize   Addr
+       FileOffset Addr
+       FileSize   Addr
+       Prot1      uint32
+       Prot2      uint32
+       Flags      uint32
+       Sections   []*machoSection
+}
+
+// machoSection is a Mach-O section, inside a segment.
+type machoSection struct {
+       Name    string
+       Segment string
+       Addr    Addr
+       Size    Addr
+       Offset  uint32
+       Align   uint32
+       Reloc   uint32
+       Nreloc  uint32
+       Flags   uint32
+       Res1    uint32
+       Res2    uint32
+}
+
+// layout positions the segments and sections in p
+// to make room for the Mach-O file header.
+// That is, it edits their VirtAddr fields to adjust for the presence
+// of the Mach-O header at the beginning of the address space.
+func (machoFormat) headerSize(p *Prog) (virt, file Addr) {
+       var h machoHeader
+       h.init(p)
+       size := Addr(h.size())
+       size = round(size, 4096)
+       p.HeaderSize = size
+       return size, size
+}
+
+// write writes p to w as a Mach-O executable.
+// layout(p) must have already been called,
+// and the number, sizes, and addresses of the segments
+// and sections must not have been modified since the call.
+func (machoFormat) write(w io.Writer, p *Prog) {
+       var h machoHeader
+       h.init(p)
+       off := Addr(0)
+       enc := h.encode()
+       w.Write(enc)
+       off += Addr(len(enc))
+       for _, seg := range p.Segments {
+               if seg.FileOffset < off {
+                       h.p.errorf("mach-o error: invalid file offset")
+               }
+               w.Write(make([]byte, int(seg.FileOffset-off)))
+               if seg.FileSize != Addr(len(seg.Data)) {
+                       h.p.errorf("mach-o error: invalid file size")
+               }
+               w.Write(seg.Data)
+               off = seg.FileOffset + Addr(len(seg.Data))
+       }
+}
+
+// Conversion of Prog to macho data structures.
+
+// machoArches maps from GOARCH to machoArch.
+var machoArches = map[string]machoArch{
+       "amd64": {
+               CPU:    uint32(macho.CpuAmd64),
+               SubCPU: uint32(machoSubCPU386),
+       },
+}
+
+// init initializes the header h to describe p.
+func (h *machoHeader) init(p *Prog) {
+       h.p = p
+       h.Segments = nil
+       h.Loads = nil
+       var ok bool
+       h.machoArch, ok = machoArches[p.GOARCH]
+       if !ok {
+               p.errorf("mach-o: unknown target GOARCH %q", p.GOARCH)
+               return
+       }
+       h.FileType = uint32(macho.TypeExec)
+
+       mseg := h.addSegment(p, "__PAGEZERO", nil)
+       mseg.VirtSize = p.UnmappedSize
+
+       for _, seg := range p.Segments {
+               h.addSegment(p, "__"+strings.ToUpper(seg.Name), seg)
+       }
+
+       var data []uint32
+       switch h.CPU {
+       default:
+               p.errorf("mach-o: unknown cpu %#x for GOARCH %q", h.CPU, p.GOARCH)
+       case uint32(macho.CpuAmd64):
+               data = make([]uint32, 2+42)
+               data[0] = 4                  // thread type
+               data[1] = 42                 // word count
+               data[2+32] = uint32(p.Entry) // RIP register, in two parts
+               data[2+32+1] = uint32(p.Entry >> 32)
+       }
+
+       h.Loads = append(h.Loads, &machoLoad{
+               Type: uint32(macho.LoadCmdUnixThread),
+               Data: data,
+       })
+}
+
+// addSegment adds to h a Mach-O segment like seg with the given name.
+func (h *machoHeader) addSegment(p *Prog, name string, seg *Segment) *machoSegment {
+       mseg := &machoSegment{
+               Name: name,
+       }
+       h.Segments = append(h.Segments, mseg)
+       if seg == nil {
+               return mseg
+       }
+
+       mseg.VirtAddr = seg.VirtAddr
+       mseg.VirtSize = seg.VirtSize
+       mseg.FileOffset = round(seg.FileOffset, 4096)
+       mseg.FileSize = seg.FileSize
+
+       if name == "__TEXT" {
+               // Initially RWX, then just RX
+               mseg.Prot1 = 7
+               mseg.Prot2 = 5
+
+               // Text segment maps Mach-O header, needed by dynamic linker.
+               mseg.VirtAddr -= p.HeaderSize
+               mseg.VirtSize += p.HeaderSize
+               mseg.FileOffset -= p.HeaderSize
+               mseg.FileSize += p.HeaderSize
+       } else {
+               // RW
+               mseg.Prot1 = 3
+               mseg.Prot2 = 3
+       }
+
+       for _, sect := range seg.Sections {
+               h.addSection(mseg, seg, sect)
+       }
+       return mseg
+}
+
+// addSection adds to mseg a Mach-O section like sect, inside seg, with the given name.
+func (h *machoHeader) addSection(mseg *machoSegment, seg *Segment, sect *Section) {
+       msect := &machoSection{
+               Name:    "__" + sect.Name,
+               Segment: mseg.Name,
+               // Reloc: sect.RelocOffset,
+               // NumReloc: sect.RelocLen / 8,
+               Addr: sect.VirtAddr,
+               Size: sect.Size,
+       }
+       mseg.Sections = append(mseg.Sections, msect)
+
+       for 1<<msect.Align < sect.Align {
+               msect.Align++
+       }
+
+       if off := sect.VirtAddr - seg.VirtAddr; off < seg.FileSize {
+               // Data in file.
+               if sect.Size > seg.FileSize-off {
+                       h.p.errorf("mach-o error: section crosses file boundary")
+               }
+               msect.Offset = uint32(seg.FileOffset + off)
+       } else {
+               // Zero filled.
+               msect.Flags |= 1
+       }
+
+       if sect.Name == "text" {
+               msect.Flags |= 0x400 // contains executable instructions
+       }
+}
+
+// A machoWriter helps write Mach-O headers.
+// It is basically a buffer with some helper routines for writing integers.
+type machoWriter struct {
+       dst   []byte
+       tmp   [8]byte
+       order binary.ByteOrder
+       is64  bool
+       p     *Prog
+}
+
+// if64 returns x if w is writing a 64-bit object file; otherwise it returns y.
+func (w *machoWriter) if64(x, y interface{}) interface{} {
+       if w.is64 {
+               return x
+       }
+       return y
+}
+
+// encode encodes each of the given arguments into the writer.
+// It encodes uint32, []uint32, uint64, and []uint64 by writing each value
+// in turn in the correct byte order for the output file.
+// It encodes an Addr as a uint64 if writing a 64-bit output file, or else as a uint32.
+// It encodes []byte and string by writing the raw bytes (no length prefix).
+// It skips nil values in the args list.
+func (w *machoWriter) encode(args ...interface{}) {
+       for _, arg := range args {
+               switch arg := arg.(type) {
+               default:
+                       w.p.errorf("mach-o error: cannot encode %T", arg)
+               case nil:
+                       // skip
+               case []byte:
+                       w.dst = append(w.dst, arg...)
+               case string:
+                       w.dst = append(w.dst, arg...)
+               case uint32:
+                       w.order.PutUint32(w.tmp[:], arg)
+                       w.dst = append(w.dst, w.tmp[:4]...)
+               case []uint32:
+                       for _, x := range arg {
+                               w.order.PutUint32(w.tmp[:], x)
+                               w.dst = append(w.dst, w.tmp[:4]...)
+                       }
+               case uint64:
+                       w.order.PutUint64(w.tmp[:], arg)
+                       w.dst = append(w.dst, w.tmp[:8]...)
+               case Addr:
+                       if w.is64 {
+                               w.order.PutUint64(w.tmp[:], uint64(arg))
+                               w.dst = append(w.dst, w.tmp[:8]...)
+                       } else {
+                               if Addr(uint32(arg)) != arg {
+                                       w.p.errorf("mach-o error: truncating address %#x to uint32", arg)
+                               }
+                               w.order.PutUint32(w.tmp[:], uint32(arg))
+                               w.dst = append(w.dst, w.tmp[:4]...)
+                       }
+               }
+       }
+}
+
+// segmentSize returns the size of the encoding of seg in bytes.
+func (w *machoWriter) segmentSize(seg *machoSegment) int {
+       if w.is64 {
+               return 18*4 + 20*4*len(seg.Sections)
+       }
+       return 14*4 + 22*4*len(seg.Sections)
+}
+
+// zeroPad returns the string s truncated or padded with NULs to n bytes.
+func zeroPad(s string, n int) string {
+       if len(s) >= n {
+               return s[:n]
+       }
+       return s + strings.Repeat("\x00", n-len(s))
+}
+
+// size returns the encoded size of the header.
+func (h *machoHeader) size() int {
+       // Could write separate code, but encoding is cheap; encode and throw it away.
+       return len(h.encode())
+}
+
+// encode returns the Mach-O encoding of the header.
+func (h *machoHeader) encode() []byte {
+       w := &machoWriter{p: h.p}
+       w.is64 = h.CPU&macho64Bit != 0
+       switch h.SubCPU {
+       default:
+               h.p.errorf("mach-o error: unknown CPU")
+       case machoSubCPU386:
+               w.order = binary.LittleEndian
+       }
+
+       loadSize := 0
+       for _, seg := range h.Segments {
+               loadSize += w.segmentSize(seg)
+       }
+       for _, l := range h.Loads {
+               loadSize += 4 * (2 + len(l.Data))
+       }
+
+       w.encode(
+               w.if64(macho.Magic64, macho.Magic32),
+               uint32(h.CPU),
+               uint32(h.SubCPU),
+               uint32(h.FileType),
+               uint32(len(h.Loads)+len(h.Segments)),
+               uint32(loadSize),
+               uint32(1),
+               w.if64(uint32(0), nil),
+       )
+
+       for _, seg := range h.Segments {
+               w.encode(
+                       w.if64(uint32(macho.LoadCmdSegment64), uint32(macho.LoadCmdSegment)),
+                       uint32(w.segmentSize(seg)),
+                       zeroPad(seg.Name, 16),
+                       seg.VirtAddr,
+                       seg.VirtSize,
+                       seg.FileOffset,
+                       seg.FileSize,
+                       seg.Prot1,
+                       seg.Prot2,
+                       uint32(len(seg.Sections)),
+                       seg.Flags,
+               )
+               for _, sect := range seg.Sections {
+                       w.encode(
+                               zeroPad(sect.Name, 16),
+                               zeroPad(seg.Name, 16),
+                               sect.Addr,
+                               sect.Size,
+                               sect.Offset,
+                               sect.Align,
+                               sect.Reloc,
+                               sect.Nreloc,
+                               sect.Flags,
+                               sect.Res1,
+                               sect.Res2,
+                               w.if64(uint32(0), nil),
+                       )
+               }
+       }
+
+       for _, load := range h.Loads {
+               w.encode(
+                       load.Type,
+                       uint32(4*(2+len(load.Data))),
+                       load.Data,
+               )
+       }
+
+       return w.dst
+}
diff --git a/src/cmd/link/macho_test.go b/src/cmd/link/macho_test.go
new file mode 100644 (file)
index 0000000..4f19fa4
--- /dev/null
@@ -0,0 +1,403 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "bytes"
+       "debug/macho"
+       "encoding/binary"
+       "fmt"
+       "io/ioutil"
+       "strings"
+       "testing"
+)
+
+// Test macho writing by checking that each generated prog can be written
+// and then read back using debug/macho to get the same prog.
+// Also check against golden testdata file.
+var machoWriteTests = []struct {
+       name   string
+       golden bool
+       prog   *Prog
+}{
+       // amd64 exit 9
+       {
+               name:   "exit9",
+               golden: true,
+               prog: &Prog{
+                       GOARCH:       "amd64",
+                       UnmappedSize: 0x1000,
+                       Entry:        0x1000,
+                       Segments: []*Segment{
+                               {
+                                       Name:       "text",
+                                       VirtAddr:   0x1000,
+                                       VirtSize:   13,
+                                       FileOffset: 0,
+                                       FileSize:   13,
+                                       Data: []byte{
+                                               0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
+                                               0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI
+                                               0x0f, 0x05, // SYSCALL
+                                               0xf4, // HLT
+                                       },
+                                       Sections: []*Section{
+                                               {
+                                                       Name:     "text",
+                                                       VirtAddr: 0x1000,
+                                                       Size:     13,
+                                                       Align:    64,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       },
+
+       // amd64 write hello world & exit 9
+       {
+               name:   "hello",
+               golden: true,
+               prog: &Prog{
+                       GOARCH:       "amd64",
+                       UnmappedSize: 0x1000,
+                       Entry:        0x1000,
+                       Segments: []*Segment{
+                               {
+                                       Name:       "text",
+                                       VirtAddr:   0x1000,
+                                       VirtSize:   35,
+                                       FileOffset: 0,
+                                       FileSize:   35,
+                                       Data: []byte{
+                                               0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
+                                               0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI
+                                               0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI
+                                               0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX
+                                               0x0f, 0x05, // SYSCALL
+                                               0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
+                                               0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI
+                                               0x0f, 0x05, // SYSCALL
+                                               0xf4, // HLT
+                                       },
+                                       Sections: []*Section{
+                                               {
+                                                       Name:     "text",
+                                                       VirtAddr: 0x1000,
+                                                       Size:     35,
+                                                       Align:    64,
+                                               },
+                                       },
+                               },
+                               {
+                                       Name:       "data",
+                                       VirtAddr:   0x2000,
+                                       VirtSize:   12,
+                                       FileOffset: 0x1000,
+                                       FileSize:   12,
+                                       Data:       []byte("hello world\n"),
+                                       Sections: []*Section{
+                                               {
+                                                       Name:     "data",
+                                                       VirtAddr: 0x2000,
+                                                       Size:     12,
+                                                       Align:    64,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       },
+
+       // amd64 write hello world from rodata & exit 0
+       {
+               name:   "helloro",
+               golden: true,
+               prog: &Prog{
+                       GOARCH:       "amd64",
+                       UnmappedSize: 0x1000,
+                       Entry:        0x1000,
+                       Segments: []*Segment{
+                               {
+                                       Name:       "text",
+                                       VirtAddr:   0x1000,
+                                       VirtSize:   0x100c,
+                                       FileOffset: 0,
+                                       FileSize:   0x100c,
+                                       Data: concat(
+                                               []byte{
+                                                       0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
+                                                       0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI
+                                                       0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI
+                                                       0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX
+                                                       0x0f, 0x05, // SYSCALL
+                                                       0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
+                                                       0xbf, 0x00, 0x00, 0x00, 0x00, // MOVL $0, DI
+                                                       0x0f, 0x05, // SYSCALL
+                                                       0xf4, // HLT
+                                               },
+                                               make([]byte, 0x1000-35),
+                                               []byte("hello world\n"),
+                                       ),
+                                       Sections: []*Section{
+                                               {
+                                                       Name:     "text",
+                                                       VirtAddr: 0x1000,
+                                                       Size:     35,
+                                                       Align:    64,
+                                               },
+                                               {
+                                                       Name:     "rodata",
+                                                       VirtAddr: 0x2000,
+                                                       Size:     12,
+                                                       Align:    64,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       },
+}
+
+func concat(xs ...[]byte) []byte {
+       var out []byte
+       for _, x := range xs {
+               out = append(out, x...)
+       }
+       return out
+}
+
+func TestMachoWrite(t *testing.T) {
+       for _, tt := range machoWriteTests {
+               name := tt.prog.GOARCH + "." + tt.name
+               prog := cloneProg(tt.prog)
+               var f machoFormat
+               vsize, fsize := f.headerSize(prog)
+               shiftProg(prog, vsize, fsize)
+               var buf bytes.Buffer
+               f.write(&buf, prog)
+               if false { // enable to debug
+                       ioutil.WriteFile("a.out", buf.Bytes(), 0777)
+               }
+               read, err := machoRead(machoArches[tt.prog.GOARCH], buf.Bytes())
+               if err != nil {
+                       t.Errorf("%s: reading mach-o output:\n\t%v", name, err)
+                       continue
+               }
+               diffs := diffProg(read, prog)
+               if diffs != nil {
+                       t.Errorf("%s: mismatched prog:\n\t%s", name, strings.Join(diffs, "\n\t"))
+                       continue
+               }
+               if !tt.golden {
+                       continue
+               }
+               checkGolden(t, buf.Bytes(), "testdata/macho."+name)
+       }
+}
+
+// machoRead reads the mach-o file in data and returns a corresponding prog.
+func machoRead(arch machoArch, data []byte) (*Prog, error) {
+       f, err := macho.NewFile(bytes.NewReader(data))
+       if err != nil {
+               return nil, err
+       }
+
+       var errors []string
+       errorf := func(format string, args ...interface{}) {
+               errors = append(errors, fmt.Sprintf(format, args...))
+       }
+
+       magic := uint32(0xFEEDFACE)
+       if arch.CPU&macho64Bit != 0 {
+               magic |= 1
+       }
+       if f.Magic != magic {
+               errorf("header: Magic = %#x, want %#x", f.Magic, magic)
+       }
+       if f.Cpu != macho.CpuAmd64 {
+               errorf("header: CPU = %#x, want %#x", f.Cpu, macho.CpuAmd64)
+       }
+       if f.SubCpu != 3 {
+               errorf("header: SubCPU = %#x, want %#x", f.SubCpu, 3)
+       }
+       if f.Type != 2 {
+               errorf("header: FileType = %d, want %d", f.Type, 2)
+       }
+       if f.Flags != 1 {
+               errorf("header: Flags = %d, want %d", f.Flags, 1)
+       }
+
+       msects := f.Sections
+       var limit uint64
+       prog := new(Prog)
+       for _, load := range f.Loads {
+               switch load := load.(type) {
+               default:
+                       errorf("unexpected macho load %T %x", load, load.Raw())
+
+               case macho.LoadBytes:
+                       if len(load) < 8 || len(load)%4 != 0 {
+                               errorf("unexpected load length %d", len(load))
+                               continue
+                       }
+                       cmd := f.ByteOrder.Uint32(load)
+                       switch macho.LoadCmd(cmd) {
+                       default:
+                               errorf("unexpected macho load cmd %s", macho.LoadCmd(cmd))
+                       case macho.LoadCmdUnixThread:
+                               data := make([]uint32, len(load[8:])/4)
+                               binary.Read(bytes.NewReader(load[8:]), f.ByteOrder, data)
+                               if len(data) != 44 {
+                                       errorf("macho thread len(data) = %d, want 42", len(data))
+                                       continue
+                               }
+                               if data[0] != 4 {
+                                       errorf("macho thread type = %d, want 4", data[0])
+                               }
+                               if data[1] != uint32(len(data))-2 {
+                                       errorf("macho thread desc len = %d, want %d", data[1], uint32(len(data))-2)
+                                       continue
+                               }
+                               for i, val := range data[2:] {
+                                       switch i {
+                                       default:
+                                               if val != 0 {
+                                                       errorf("macho thread data[%d] = %#x, want 0", i, val)
+                                               }
+                                       case 32:
+                                               prog.Entry = Addr(val)
+                                       case 33:
+                                               prog.Entry |= Addr(val) << 32
+                                       }
+                               }
+                       }
+
+               case *macho.Segment:
+                       if load.Addr < limit {
+                               errorf("segments out of order: %q at %#x after %#x", load.Name, load.Addr, limit)
+                       }
+                       limit = load.Addr + load.Memsz
+                       if load.Name == "__PAGEZERO" || load.Addr == 0 && load.Filesz == 0 {
+                               if load.Name != "__PAGEZERO" {
+                                       errorf("segment with Addr=0, Filesz=0 is named %q, want %q", load.Name, "__PAGEZERO")
+                               } else if load.Addr != 0 || load.Filesz != 0 {
+                                       errorf("segment %q has Addr=%#x, Filesz=%d, want Addr=%#x, Filesz=%d", load.Name, load.Addr, load.Filesz, 0, 0)
+                               }
+                               prog.UnmappedSize = Addr(load.Memsz)
+                               continue
+                       }
+
+                       if !strings.HasPrefix(load.Name, "__") {
+                               errorf("segment name %q does not begin with %q", load.Name, "__")
+                       }
+                       if strings.ToUpper(load.Name) != load.Name {
+                               errorf("segment name %q is not all upper case", load.Name)
+                       }
+
+                       seg := &Segment{
+                               Name:       strings.ToLower(strings.TrimPrefix(load.Name, "__")),
+                               VirtAddr:   Addr(load.Addr),
+                               VirtSize:   Addr(load.Memsz),
+                               FileOffset: Addr(load.Offset),
+                               FileSize:   Addr(load.Filesz),
+                       }
+                       prog.Segments = append(prog.Segments, seg)
+
+                       data, err := load.Data()
+                       if err != nil {
+                               errorf("loading data from %q: %v", load.Name, err)
+                       }
+                       seg.Data = data
+
+                       var maxprot, prot uint32
+                       if load.Name == "__TEXT" {
+                               maxprot, prot = 7, 5
+                       } else {
+                               maxprot, prot = 3, 3
+                       }
+                       if load.Maxprot != maxprot || load.Prot != prot {
+                               errorf("segment %q protection is %d, %d, want %d, %d",
+                                       load.Maxprot, load.Prot, maxprot, prot)
+                       }
+
+                       for len(msects) > 0 && msects[0].Addr < load.Addr+load.Memsz {
+                               msect := msects[0]
+                               msects = msects[1:]
+
+                               if msect.Offset > 0 && prog.HeaderSize == 0 {
+                                       prog.HeaderSize = Addr(msect.Offset)
+                                       if seg.FileOffset != 0 {
+                                               errorf("initial segment %q does not map header", load.Name)
+                                       }
+                                       seg.VirtAddr += prog.HeaderSize
+                                       seg.VirtSize -= prog.HeaderSize
+                                       seg.FileOffset += prog.HeaderSize
+                                       seg.FileSize -= prog.HeaderSize
+                                       seg.Data = seg.Data[prog.HeaderSize:]
+                               }
+
+                               if msect.Addr < load.Addr {
+                                       errorf("section %q at address %#x is missing segment", msect.Name, msect.Addr)
+                                       continue
+                               }
+
+                               if !strings.HasPrefix(msect.Name, "__") {
+                                       errorf("section name %q does not begin with %q", msect.Name, "__")
+                               }
+                               if strings.ToLower(msect.Name) != msect.Name {
+                                       errorf("section name %q is not all lower case", msect.Name)
+                               }
+                               if msect.Seg != load.Name {
+                                       errorf("section %q is lists segment name %q, want %q",
+                                               msect.Name, msect.Seg, load.Name)
+                               }
+                               if uint64(msect.Offset) != uint64(load.Offset)+msect.Addr-load.Addr {
+                                       errorf("section %q file offset is %#x, want %#x",
+                                               msect.Name, msect.Offset, load.Offset+msect.Addr-load.Addr)
+                               }
+                               if msect.Reloff != 0 || msect.Nreloc != 0 {
+                                       errorf("section %q has reloff %d,%d, want %d,%d",
+                                               msect.Name, msect.Reloff, msect.Nreloc, 0, 0)
+                               }
+                               flags := uint32(0)
+                               if msect.Name == "__text" {
+                                       flags = 0x400
+                               }
+                               if msect.Offset == 0 {
+                                       flags = 1
+                               }
+                               if msect.Flags != flags {
+                                       errorf("section %q flags = %#x, want %#x", msect.Flags, flags)
+                               }
+                               sect := &Section{
+                                       Name:     strings.ToLower(strings.TrimPrefix(msect.Name, "__")),
+                                       VirtAddr: Addr(msect.Addr),
+                                       Size:     Addr(msect.Size),
+                                       Align:    1 << msect.Align,
+                               }
+                               seg.Sections = append(seg.Sections, sect)
+                       }
+               }
+       }
+
+       for _, msect := range msects {
+               errorf("section %q has no segment", msect.Name)
+       }
+
+       limit = 0
+       for _, msect := range f.Sections {
+               if msect.Addr < limit {
+                       errorf("sections out of order: %q at %#x after %#x", msect.Name, msect.Addr, limit)
+               }
+               limit = msect.Addr + msect.Size
+       }
+
+       err = nil
+       if errors != nil {
+               err = fmt.Errorf("%s", strings.Join(errors, "\n\t"))
+       }
+       return prog, err
+}
diff --git a/src/cmd/link/testdata/macho.amd64.exit9 b/src/cmd/link/testdata/macho.amd64.exit9
new file mode 100755 (executable)
index 0000000..d068b12
Binary files /dev/null and b/src/cmd/link/testdata/macho.amd64.exit9 differ
diff --git a/src/cmd/link/testdata/macho.amd64.hello b/src/cmd/link/testdata/macho.amd64.hello
new file mode 100755 (executable)
index 0000000..8e93969
Binary files /dev/null and b/src/cmd/link/testdata/macho.amd64.hello differ
diff --git a/src/cmd/link/testdata/macho.amd64.helloro b/src/cmd/link/testdata/macho.amd64.helloro
new file mode 100755 (executable)
index 0000000..55a6249
Binary files /dev/null and b/src/cmd/link/testdata/macho.amd64.helloro differ