cmd/splitdwarf: copy debug/macho

The splitdwarf command will need a modified version of debug/macho.
This is a verbatim copy from std as of Go 1.11, which we'll modify in
the next CL.

Change-Id: Ia9ded870d1ba91dad21f9f6dc5bb38f9f6cc0e80
Reviewed-on: https://go-review.googlesource.com/c/152240
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/cmd/splitdwarf/internal/macho/fat.go b/cmd/splitdwarf/internal/macho/fat.go
new file mode 100644
index 0000000..6bd730d
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/fat.go
@@ -0,0 +1,146 @@
+// 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 macho
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+	"os"
+)
+
+// A FatFile is a Mach-O universal binary that contains at least one architecture.
+type FatFile struct {
+	Magic  uint32
+	Arches []FatArch
+	closer io.Closer
+}
+
+// A FatArchHeader represents a fat header for a specific image architecture.
+type FatArchHeader struct {
+	Cpu    Cpu
+	SubCpu uint32
+	Offset uint32
+	Size   uint32
+	Align  uint32
+}
+
+const fatArchHeaderSize = 5 * 4
+
+// A FatArch is a Mach-O File inside a FatFile.
+type FatArch struct {
+	FatArchHeader
+	*File
+}
+
+// ErrNotFat is returned from NewFatFile or OpenFat when the file is not a
+// universal binary but may be a thin binary, based on its magic number.
+var ErrNotFat = &FormatError{0, "not a fat Mach-O file", nil}
+
+// NewFatFile creates a new FatFile for accessing all the Mach-O images in a
+// universal binary. The Mach-O binary is expected to start at position 0 in
+// the ReaderAt.
+func NewFatFile(r io.ReaderAt) (*FatFile, error) {
+	var ff FatFile
+	sr := io.NewSectionReader(r, 0, 1<<63-1)
+
+	// Read the fat_header struct, which is always in big endian.
+	// Start with the magic number.
+	err := binary.Read(sr, binary.BigEndian, &ff.Magic)
+	if err != nil {
+		return nil, &FormatError{0, "error reading magic number", nil}
+	} else if ff.Magic != MagicFat {
+		// See if this is a Mach-O file via its magic number. The magic
+		// must be converted to little endian first though.
+		var buf [4]byte
+		binary.BigEndian.PutUint32(buf[:], ff.Magic)
+		leMagic := binary.LittleEndian.Uint32(buf[:])
+		if leMagic == Magic32 || leMagic == Magic64 {
+			return nil, ErrNotFat
+		} else {
+			return nil, &FormatError{0, "invalid magic number", nil}
+		}
+	}
+	offset := int64(4)
+
+	// Read the number of FatArchHeaders that come after the fat_header.
+	var narch uint32
+	err = binary.Read(sr, binary.BigEndian, &narch)
+	if err != nil {
+		return nil, &FormatError{offset, "invalid fat_header", nil}
+	}
+	offset += 4
+
+	if narch < 1 {
+		return nil, &FormatError{offset, "file contains no images", nil}
+	}
+
+	// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
+	// there are not duplicate architectures.
+	seenArches := make(map[uint64]bool, narch)
+	// Make sure that all images are for the same MH_ type.
+	var machoType Type
+
+	// Following the fat_header comes narch fat_arch structs that index
+	// Mach-O images further in the file.
+	ff.Arches = make([]FatArch, narch)
+	for i := uint32(0); i < narch; i++ {
+		fa := &ff.Arches[i]
+		err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
+		if err != nil {
+			return nil, &FormatError{offset, "invalid fat_arch header", nil}
+		}
+		offset += fatArchHeaderSize
+
+		fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
+		fa.File, err = NewFile(fr)
+		if err != nil {
+			return nil, err
+		}
+
+		// Make sure the architecture for this image is not duplicate.
+		seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
+		if o, k := seenArches[seenArch]; o || k {
+			return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil}
+		}
+		seenArches[seenArch] = true
+
+		// Make sure the Mach-O type matches that of the first image.
+		if i == 0 {
+			machoType = fa.Type
+		} else {
+			if fa.Type != machoType {
+				return nil, &FormatError{offset, fmt.Sprintf("Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType), nil}
+			}
+		}
+	}
+
+	return &ff, nil
+}
+
+// OpenFat opens the named file using os.Open and prepares it for use as a Mach-O
+// universal binary.
+func OpenFat(name string) (*FatFile, error) {
+	f, err := os.Open(name)
+	if err != nil {
+		return nil, err
+	}
+	ff, err := NewFatFile(f)
+	if err != nil {
+		f.Close()
+		return nil, err
+	}
+	ff.closer = f
+	return ff, nil
+}
+
+func (ff *FatFile) Close() error {
+	var err error
+	if ff.closer != nil {
+		err = ff.closer.Close()
+		ff.closer = nil
+	}
+	return err
+}
diff --git a/cmd/splitdwarf/internal/macho/file.go b/cmd/splitdwarf/internal/macho/file.go
new file mode 100644
index 0000000..16708e5
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/file.go
@@ -0,0 +1,688 @@
+// Copyright 2009 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 macho implements access to Mach-O object files.
+package macho
+
+// High level access to low level data structures.
+
+import (
+	"bytes"
+	"compress/zlib"
+	"debug/dwarf"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+)
+
+// A File represents an open Mach-O file.
+type File struct {
+	FileHeader
+	ByteOrder binary.ByteOrder
+	Loads     []Load
+	Sections  []*Section
+
+	Symtab   *Symtab
+	Dysymtab *Dysymtab
+
+	closer io.Closer
+}
+
+// A Load represents any Mach-O load command.
+type Load interface {
+	Raw() []byte
+}
+
+// A LoadBytes is the uninterpreted bytes of a Mach-O load command.
+type LoadBytes []byte
+
+func (b LoadBytes) Raw() []byte { return b }
+
+// A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command.
+type SegmentHeader struct {
+	Cmd     LoadCmd
+	Len     uint32
+	Name    string
+	Addr    uint64
+	Memsz   uint64
+	Offset  uint64
+	Filesz  uint64
+	Maxprot uint32
+	Prot    uint32
+	Nsect   uint32
+	Flag    uint32
+}
+
+// A Segment represents a Mach-O 32-bit or 64-bit load segment command.
+type Segment struct {
+	LoadBytes
+	SegmentHeader
+
+	// Embed ReaderAt for ReadAt method.
+	// Do not embed SectionReader directly
+	// to avoid having Read and Seek.
+	// If a client wants Read and Seek it must use
+	// Open() to avoid fighting over the seek offset
+	// with other clients.
+	io.ReaderAt
+	sr *io.SectionReader
+}
+
+// Data reads and returns the contents of the segment.
+func (s *Segment) Data() ([]byte, error) {
+	dat := make([]byte, s.sr.Size())
+	n, err := s.sr.ReadAt(dat, 0)
+	if n == len(dat) {
+		err = nil
+	}
+	return dat[0:n], err
+}
+
+// Open returns a new ReadSeeker reading the segment.
+func (s *Segment) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
+
+type SectionHeader struct {
+	Name   string
+	Seg    string
+	Addr   uint64
+	Size   uint64
+	Offset uint32
+	Align  uint32
+	Reloff uint32
+	Nreloc uint32
+	Flags  uint32
+}
+
+// A Reloc represents a Mach-O relocation.
+type Reloc struct {
+	Addr  uint32
+	Value uint32
+	// when Scattered == false && Extern == true, Value is the symbol number.
+	// when Scattered == false && Extern == false, Value is the section number.
+	// when Scattered == true, Value is the value that this reloc refers to.
+	Type      uint8
+	Len       uint8 // 0=byte, 1=word, 2=long, 3=quad
+	Pcrel     bool
+	Extern    bool // valid if Scattered == false
+	Scattered bool
+}
+
+type Section struct {
+	SectionHeader
+	Relocs []Reloc
+
+	// Embed ReaderAt for ReadAt method.
+	// Do not embed SectionReader directly
+	// to avoid having Read and Seek.
+	// If a client wants Read and Seek it must use
+	// Open() to avoid fighting over the seek offset
+	// with other clients.
+	io.ReaderAt
+	sr *io.SectionReader
+}
+
+// Data reads and returns the contents of the Mach-O section.
+func (s *Section) Data() ([]byte, error) {
+	dat := make([]byte, s.sr.Size())
+	n, err := s.sr.ReadAt(dat, 0)
+	if n == len(dat) {
+		err = nil
+	}
+	return dat[0:n], err
+}
+
+// Open returns a new ReadSeeker reading the Mach-O section.
+func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
+
+// A Dylib represents a Mach-O load dynamic library command.
+type Dylib struct {
+	LoadBytes
+	Name           string
+	Time           uint32
+	CurrentVersion uint32
+	CompatVersion  uint32
+}
+
+// A Symtab represents a Mach-O symbol table command.
+type Symtab struct {
+	LoadBytes
+	SymtabCmd
+	Syms []Symbol
+}
+
+// A Dysymtab represents a Mach-O dynamic symbol table command.
+type Dysymtab struct {
+	LoadBytes
+	DysymtabCmd
+	IndirectSyms []uint32 // indices into Symtab.Syms
+}
+
+// A Rpath represents a Mach-O rpath command.
+type Rpath struct {
+	LoadBytes
+	Path string
+}
+
+// A Symbol is a Mach-O 32-bit or 64-bit symbol table entry.
+type Symbol struct {
+	Name  string
+	Type  uint8
+	Sect  uint8
+	Desc  uint16
+	Value uint64
+}
+
+/*
+ * Mach-O reader
+ */
+
+// FormatError is returned by some operations if the data does
+// not have the correct format for an object file.
+type FormatError struct {
+	off int64
+	msg string
+	val interface{}
+}
+
+func (e *FormatError) Error() string {
+	msg := e.msg
+	if e.val != nil {
+		msg += fmt.Sprintf(" '%v'", e.val)
+	}
+	msg += fmt.Sprintf(" in record at byte %#x", e.off)
+	return msg
+}
+
+// Open opens the named file using os.Open and prepares it for use as a Mach-O binary.
+func Open(name string) (*File, error) {
+	f, err := os.Open(name)
+	if err != nil {
+		return nil, err
+	}
+	ff, err := NewFile(f)
+	if err != nil {
+		f.Close()
+		return nil, err
+	}
+	ff.closer = f
+	return ff, nil
+}
+
+// Close closes the File.
+// If the File was created using NewFile directly instead of Open,
+// Close has no effect.
+func (f *File) Close() error {
+	var err error
+	if f.closer != nil {
+		err = f.closer.Close()
+		f.closer = nil
+	}
+	return err
+}
+
+// NewFile creates a new File for accessing a Mach-O binary in an underlying reader.
+// The Mach-O binary is expected to start at position 0 in the ReaderAt.
+func NewFile(r io.ReaderAt) (*File, error) {
+	f := new(File)
+	sr := io.NewSectionReader(r, 0, 1<<63-1)
+
+	// Read and decode Mach magic to determine byte order, size.
+	// Magic32 and Magic64 differ only in the bottom bit.
+	var ident [4]byte
+	if _, err := r.ReadAt(ident[0:], 0); err != nil {
+		return nil, err
+	}
+	be := binary.BigEndian.Uint32(ident[0:])
+	le := binary.LittleEndian.Uint32(ident[0:])
+	switch Magic32 &^ 1 {
+	case be &^ 1:
+		f.ByteOrder = binary.BigEndian
+		f.Magic = be
+	case le &^ 1:
+		f.ByteOrder = binary.LittleEndian
+		f.Magic = le
+	default:
+		return nil, &FormatError{0, "invalid magic number", nil}
+	}
+
+	// Read entire file header.
+	if err := binary.Read(sr, f.ByteOrder, &f.FileHeader); err != nil {
+		return nil, err
+	}
+
+	// Then load commands.
+	offset := int64(fileHeaderSize32)
+	if f.Magic == Magic64 {
+		offset = fileHeaderSize64
+	}
+	dat := make([]byte, f.Cmdsz)
+	if _, err := r.ReadAt(dat, offset); err != nil {
+		return nil, err
+	}
+	f.Loads = make([]Load, f.Ncmd)
+	bo := f.ByteOrder
+	for i := range f.Loads {
+		// Each load command begins with uint32 command and length.
+		if len(dat) < 8 {
+			return nil, &FormatError{offset, "command block too small", nil}
+		}
+		cmd, siz := LoadCmd(bo.Uint32(dat[0:4])), bo.Uint32(dat[4:8])
+		if siz < 8 || siz > uint32(len(dat)) {
+			return nil, &FormatError{offset, "invalid command block size", nil}
+		}
+		var cmddat []byte
+		cmddat, dat = dat[0:siz], dat[siz:]
+		offset += int64(siz)
+		var s *Segment
+		switch cmd {
+		default:
+			f.Loads[i] = LoadBytes(cmddat)
+
+		case LoadCmdRpath:
+			var hdr RpathCmd
+			b := bytes.NewReader(cmddat)
+			if err := binary.Read(b, bo, &hdr); err != nil {
+				return nil, err
+			}
+			l := new(Rpath)
+			if hdr.Path >= uint32(len(cmddat)) {
+				return nil, &FormatError{offset, "invalid path in rpath command", hdr.Path}
+			}
+			l.Path = cstring(cmddat[hdr.Path:])
+			l.LoadBytes = LoadBytes(cmddat)
+			f.Loads[i] = l
+
+		case LoadCmdDylib:
+			var hdr DylibCmd
+			b := bytes.NewReader(cmddat)
+			if err := binary.Read(b, bo, &hdr); err != nil {
+				return nil, err
+			}
+			l := new(Dylib)
+			if hdr.Name >= uint32(len(cmddat)) {
+				return nil, &FormatError{offset, "invalid name in dynamic library command", hdr.Name}
+			}
+			l.Name = cstring(cmddat[hdr.Name:])
+			l.Time = hdr.Time
+			l.CurrentVersion = hdr.CurrentVersion
+			l.CompatVersion = hdr.CompatVersion
+			l.LoadBytes = LoadBytes(cmddat)
+			f.Loads[i] = l
+
+		case LoadCmdSymtab:
+			var hdr SymtabCmd
+			b := bytes.NewReader(cmddat)
+			if err := binary.Read(b, bo, &hdr); err != nil {
+				return nil, err
+			}
+			strtab := make([]byte, hdr.Strsize)
+			if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
+				return nil, err
+			}
+			var symsz int
+			if f.Magic == Magic64 {
+				symsz = 16
+			} else {
+				symsz = 12
+			}
+			symdat := make([]byte, int(hdr.Nsyms)*symsz)
+			if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
+				return nil, err
+			}
+			st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset)
+			if err != nil {
+				return nil, err
+			}
+			f.Loads[i] = st
+			f.Symtab = st
+
+		case LoadCmdDysymtab:
+			var hdr DysymtabCmd
+			b := bytes.NewReader(cmddat)
+			if err := binary.Read(b, bo, &hdr); err != nil {
+				return nil, err
+			}
+			dat := make([]byte, hdr.Nindirectsyms*4)
+			if _, err := r.ReadAt(dat, int64(hdr.Indirectsymoff)); err != nil {
+				return nil, err
+			}
+			x := make([]uint32, hdr.Nindirectsyms)
+			if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil {
+				return nil, err
+			}
+			st := new(Dysymtab)
+			st.LoadBytes = LoadBytes(cmddat)
+			st.DysymtabCmd = hdr
+			st.IndirectSyms = x
+			f.Loads[i] = st
+			f.Dysymtab = st
+
+		case LoadCmdSegment:
+			var seg32 Segment32
+			b := bytes.NewReader(cmddat)
+			if err := binary.Read(b, bo, &seg32); err != nil {
+				return nil, err
+			}
+			s = new(Segment)
+			s.LoadBytes = cmddat
+			s.Cmd = cmd
+			s.Len = siz
+			s.Name = cstring(seg32.Name[0:])
+			s.Addr = uint64(seg32.Addr)
+			s.Memsz = uint64(seg32.Memsz)
+			s.Offset = uint64(seg32.Offset)
+			s.Filesz = uint64(seg32.Filesz)
+			s.Maxprot = seg32.Maxprot
+			s.Prot = seg32.Prot
+			s.Nsect = seg32.Nsect
+			s.Flag = seg32.Flag
+			f.Loads[i] = s
+			for i := 0; i < int(s.Nsect); i++ {
+				var sh32 Section32
+				if err := binary.Read(b, bo, &sh32); err != nil {
+					return nil, err
+				}
+				sh := new(Section)
+				sh.Name = cstring(sh32.Name[0:])
+				sh.Seg = cstring(sh32.Seg[0:])
+				sh.Addr = uint64(sh32.Addr)
+				sh.Size = uint64(sh32.Size)
+				sh.Offset = sh32.Offset
+				sh.Align = sh32.Align
+				sh.Reloff = sh32.Reloff
+				sh.Nreloc = sh32.Nreloc
+				sh.Flags = sh32.Flags
+				if err := f.pushSection(sh, r); err != nil {
+					return nil, err
+				}
+			}
+
+		case LoadCmdSegment64:
+			var seg64 Segment64
+			b := bytes.NewReader(cmddat)
+			if err := binary.Read(b, bo, &seg64); err != nil {
+				return nil, err
+			}
+			s = new(Segment)
+			s.LoadBytes = cmddat
+			s.Cmd = cmd
+			s.Len = siz
+			s.Name = cstring(seg64.Name[0:])
+			s.Addr = seg64.Addr
+			s.Memsz = seg64.Memsz
+			s.Offset = seg64.Offset
+			s.Filesz = seg64.Filesz
+			s.Maxprot = seg64.Maxprot
+			s.Prot = seg64.Prot
+			s.Nsect = seg64.Nsect
+			s.Flag = seg64.Flag
+			f.Loads[i] = s
+			for i := 0; i < int(s.Nsect); i++ {
+				var sh64 Section64
+				if err := binary.Read(b, bo, &sh64); err != nil {
+					return nil, err
+				}
+				sh := new(Section)
+				sh.Name = cstring(sh64.Name[0:])
+				sh.Seg = cstring(sh64.Seg[0:])
+				sh.Addr = sh64.Addr
+				sh.Size = sh64.Size
+				sh.Offset = sh64.Offset
+				sh.Align = sh64.Align
+				sh.Reloff = sh64.Reloff
+				sh.Nreloc = sh64.Nreloc
+				sh.Flags = sh64.Flags
+				if err := f.pushSection(sh, r); err != nil {
+					return nil, err
+				}
+			}
+		}
+		if s != nil {
+			s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
+			s.ReaderAt = s.sr
+		}
+	}
+	return f, nil
+}
+
+func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *SymtabCmd, offset int64) (*Symtab, error) {
+	bo := f.ByteOrder
+	symtab := make([]Symbol, hdr.Nsyms)
+	b := bytes.NewReader(symdat)
+	for i := range symtab {
+		var n Nlist64
+		if f.Magic == Magic64 {
+			if err := binary.Read(b, bo, &n); err != nil {
+				return nil, err
+			}
+		} else {
+			var n32 Nlist32
+			if err := binary.Read(b, bo, &n32); err != nil {
+				return nil, err
+			}
+			n.Name = n32.Name
+			n.Type = n32.Type
+			n.Sect = n32.Sect
+			n.Desc = n32.Desc
+			n.Value = uint64(n32.Value)
+		}
+		sym := &symtab[i]
+		if n.Name >= uint32(len(strtab)) {
+			return nil, &FormatError{offset, "invalid name in symbol table", n.Name}
+		}
+		sym.Name = cstring(strtab[n.Name:])
+		sym.Type = n.Type
+		sym.Sect = n.Sect
+		sym.Desc = n.Desc
+		sym.Value = n.Value
+	}
+	st := new(Symtab)
+	st.LoadBytes = LoadBytes(cmddat)
+	st.Syms = symtab
+	return st, nil
+}
+
+type relocInfo struct {
+	Addr   uint32
+	Symnum uint32
+}
+
+func (f *File) pushSection(sh *Section, r io.ReaderAt) error {
+	f.Sections = append(f.Sections, sh)
+	sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size))
+	sh.ReaderAt = sh.sr
+
+	if sh.Nreloc > 0 {
+		reldat := make([]byte, int(sh.Nreloc)*8)
+		if _, err := r.ReadAt(reldat, int64(sh.Reloff)); err != nil {
+			return err
+		}
+		b := bytes.NewReader(reldat)
+
+		bo := f.ByteOrder
+
+		sh.Relocs = make([]Reloc, sh.Nreloc)
+		for i := range sh.Relocs {
+			rel := &sh.Relocs[i]
+
+			var ri relocInfo
+			if err := binary.Read(b, bo, &ri); err != nil {
+				return err
+			}
+
+			if ri.Addr&(1<<31) != 0 { // scattered
+				rel.Addr = ri.Addr & (1<<24 - 1)
+				rel.Type = uint8((ri.Addr >> 24) & (1<<4 - 1))
+				rel.Len = uint8((ri.Addr >> 28) & (1<<2 - 1))
+				rel.Pcrel = ri.Addr&(1<<30) != 0
+				rel.Value = ri.Symnum
+				rel.Scattered = true
+			} else {
+				switch bo {
+				case binary.LittleEndian:
+					rel.Addr = ri.Addr
+					rel.Value = ri.Symnum & (1<<24 - 1)
+					rel.Pcrel = ri.Symnum&(1<<24) != 0
+					rel.Len = uint8((ri.Symnum >> 25) & (1<<2 - 1))
+					rel.Extern = ri.Symnum&(1<<27) != 0
+					rel.Type = uint8((ri.Symnum >> 28) & (1<<4 - 1))
+				case binary.BigEndian:
+					rel.Addr = ri.Addr
+					rel.Value = ri.Symnum >> 8
+					rel.Pcrel = ri.Symnum&(1<<7) != 0
+					rel.Len = uint8((ri.Symnum >> 5) & (1<<2 - 1))
+					rel.Extern = ri.Symnum&(1<<4) != 0
+					rel.Type = uint8(ri.Symnum & (1<<4 - 1))
+				default:
+					panic("unreachable")
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+func cstring(b []byte) string {
+	i := bytes.IndexByte(b, 0)
+	if i == -1 {
+		i = len(b)
+	}
+	return string(b[0:i])
+}
+
+// Segment returns the first Segment with the given name, or nil if no such segment exists.
+func (f *File) Segment(name string) *Segment {
+	for _, l := range f.Loads {
+		if s, ok := l.(*Segment); ok && s.Name == name {
+			return s
+		}
+	}
+	return nil
+}
+
+// Section returns the first section with the given name, or nil if no such
+// section exists.
+func (f *File) Section(name string) *Section {
+	for _, s := range f.Sections {
+		if s.Name == name {
+			return s
+		}
+	}
+	return nil
+}
+
+// DWARF returns the DWARF debug information for the Mach-O file.
+func (f *File) DWARF() (*dwarf.Data, error) {
+	dwarfSuffix := func(s *Section) string {
+		switch {
+		case strings.HasPrefix(s.Name, "__debug_"):
+			return s.Name[8:]
+		case strings.HasPrefix(s.Name, "__zdebug_"):
+			return s.Name[9:]
+		default:
+			return ""
+		}
+
+	}
+	sectionData := func(s *Section) ([]byte, error) {
+		b, err := s.Data()
+		if err != nil && uint64(len(b)) < s.Size {
+			return nil, err
+		}
+
+		if len(b) >= 12 && string(b[:4]) == "ZLIB" {
+			dlen := binary.BigEndian.Uint64(b[4:12])
+			dbuf := make([]byte, dlen)
+			r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
+			if err != nil {
+				return nil, err
+			}
+			if _, err := io.ReadFull(r, dbuf); err != nil {
+				return nil, err
+			}
+			if err := r.Close(); err != nil {
+				return nil, err
+			}
+			b = dbuf
+		}
+		return b, nil
+	}
+
+	// There are many other DWARF sections, but these
+	// are the ones the debug/dwarf package uses.
+	// Don't bother loading others.
+	var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
+	for _, s := range f.Sections {
+		suffix := dwarfSuffix(s)
+		if suffix == "" {
+			continue
+		}
+		if _, ok := dat[suffix]; !ok {
+			continue
+		}
+		b, err := sectionData(s)
+		if err != nil {
+			return nil, err
+		}
+		dat[suffix] = b
+	}
+
+	d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
+	if err != nil {
+		return nil, err
+	}
+
+	// Look for DWARF4 .debug_types sections.
+	for i, s := range f.Sections {
+		suffix := dwarfSuffix(s)
+		if suffix != "types" {
+			continue
+		}
+
+		b, err := sectionData(s)
+		if err != nil {
+			return nil, err
+		}
+
+		err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return d, nil
+}
+
+// ImportedSymbols returns the names of all symbols
+// referred to by the binary f that are expected to be
+// satisfied by other libraries at dynamic load time.
+func (f *File) ImportedSymbols() ([]string, error) {
+	if f.Dysymtab == nil || f.Symtab == nil {
+		return nil, &FormatError{0, "missing symbol table", nil}
+	}
+
+	st := f.Symtab
+	dt := f.Dysymtab
+	var all []string
+	for _, s := range st.Syms[dt.Iundefsym : dt.Iundefsym+dt.Nundefsym] {
+		all = append(all, s.Name)
+	}
+	return all, nil
+}
+
+// ImportedLibraries returns the paths of all libraries
+// referred to by the binary f that are expected to be
+// linked with the binary at dynamic link time.
+func (f *File) ImportedLibraries() ([]string, error) {
+	var all []string
+	for _, l := range f.Loads {
+		if lib, ok := l.(*Dylib); ok {
+			all = append(all, lib.Name)
+		}
+	}
+	return all, nil
+}
diff --git a/cmd/splitdwarf/internal/macho/file_test.go b/cmd/splitdwarf/internal/macho/file_test.go
new file mode 100644
index 0000000..003c14e
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/file_test.go
@@ -0,0 +1,379 @@
+// Copyright 2009 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 macho
+
+import (
+	"reflect"
+	"testing"
+)
+
+type fileTest struct {
+	file        string
+	hdr         FileHeader
+	loads       []interface{}
+	sections    []*SectionHeader
+	relocations map[string][]Reloc
+}
+
+var fileTests = []fileTest{
+	{
+		"testdata/gcc-386-darwin-exec",
+		FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0xc, 0x3c0, 0x85},
+		[]interface{}{
+			&SegmentHeader{LoadCmdSegment, 0x38, "__PAGEZERO", 0x0, 0x1000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+			&SegmentHeader{LoadCmdSegment, 0xc0, "__TEXT", 0x1000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x2, 0x0},
+			&SegmentHeader{LoadCmdSegment, 0xc0, "__DATA", 0x2000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x2, 0x0},
+			&SegmentHeader{LoadCmdSegment, 0x7c, "__IMPORT", 0x3000, 0x1000, 0x2000, 0x1000, 0x7, 0x7, 0x1, 0x0},
+			&SegmentHeader{LoadCmdSegment, 0x38, "__LINKEDIT", 0x4000, 0x1000, 0x3000, 0x12c, 0x7, 0x1, 0x0, 0x0},
+			nil, // LC_SYMTAB
+			nil, // LC_DYSYMTAB
+			nil, // LC_LOAD_DYLINKER
+			nil, // LC_UUID
+			nil, // LC_UNIXTHREAD
+			&Dylib{nil, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
+			&Dylib{nil, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
+		},
+		[]*SectionHeader{
+			{"__text", "__TEXT", 0x1f68, 0x88, 0xf68, 0x2, 0x0, 0x0, 0x80000400},
+			{"__cstring", "__TEXT", 0x1ff0, 0xd, 0xff0, 0x0, 0x0, 0x0, 0x2},
+			{"__data", "__DATA", 0x2000, 0x14, 0x1000, 0x2, 0x0, 0x0, 0x0},
+			{"__dyld", "__DATA", 0x2014, 0x1c, 0x1014, 0x2, 0x0, 0x0, 0x0},
+			{"__jump_table", "__IMPORT", 0x3000, 0xa, 0x2000, 0x6, 0x0, 0x0, 0x4000008},
+		},
+		nil,
+	},
+	{
+		"testdata/gcc-amd64-darwin-exec",
+		FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0xb, 0x568, 0x85},
+		[]interface{}{
+			&SegmentHeader{LoadCmdSegment64, 0x48, "__PAGEZERO", 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+			&SegmentHeader{LoadCmdSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x5, 0x0},
+			&SegmentHeader{LoadCmdSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x3, 0x0},
+			&SegmentHeader{LoadCmdSegment64, 0x48, "__LINKEDIT", 0x100002000, 0x1000, 0x2000, 0x140, 0x7, 0x1, 0x0, 0x0},
+			nil, // LC_SYMTAB
+			nil, // LC_DYSYMTAB
+			nil, // LC_LOAD_DYLINKER
+			nil, // LC_UUID
+			nil, // LC_UNIXTHREAD
+			&Dylib{nil, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
+			&Dylib{nil, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
+		},
+		[]*SectionHeader{
+			{"__text", "__TEXT", 0x100000f14, 0x6d, 0xf14, 0x2, 0x0, 0x0, 0x80000400},
+			{"__symbol_stub1", "__TEXT", 0x100000f81, 0xc, 0xf81, 0x0, 0x0, 0x0, 0x80000408},
+			{"__stub_helper", "__TEXT", 0x100000f90, 0x18, 0xf90, 0x2, 0x0, 0x0, 0x0},
+			{"__cstring", "__TEXT", 0x100000fa8, 0xd, 0xfa8, 0x0, 0x0, 0x0, 0x2},
+			{"__eh_frame", "__TEXT", 0x100000fb8, 0x48, 0xfb8, 0x3, 0x0, 0x0, 0x6000000b},
+			{"__data", "__DATA", 0x100001000, 0x1c, 0x1000, 0x3, 0x0, 0x0, 0x0},
+			{"__dyld", "__DATA", 0x100001020, 0x38, 0x1020, 0x3, 0x0, 0x0, 0x0},
+			{"__la_symbol_ptr", "__DATA", 0x100001058, 0x10, 0x1058, 0x2, 0x0, 0x0, 0x7},
+		},
+		nil,
+	},
+	{
+		"testdata/gcc-amd64-darwin-exec-debug",
+		FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0xa, 0x4, 0x5a0, 0},
+		[]interface{}{
+			nil, // LC_UUID
+			&SegmentHeader{LoadCmdSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x0, 0x7, 0x5, 0x5, 0x0},
+			&SegmentHeader{LoadCmdSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x0, 0x0, 0x7, 0x3, 0x3, 0x0},
+			&SegmentHeader{LoadCmdSegment64, 0x278, "__DWARF", 0x100002000, 0x1000, 0x1000, 0x1bc, 0x7, 0x3, 0x7, 0x0},
+		},
+		[]*SectionHeader{
+			{"__text", "__TEXT", 0x100000f14, 0x0, 0x0, 0x2, 0x0, 0x0, 0x80000400},
+			{"__symbol_stub1", "__TEXT", 0x100000f81, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000408},
+			{"__stub_helper", "__TEXT", 0x100000f90, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0},
+			{"__cstring", "__TEXT", 0x100000fa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
+			{"__eh_frame", "__TEXT", 0x100000fb8, 0x0, 0x0, 0x3, 0x0, 0x0, 0x6000000b},
+			{"__data", "__DATA", 0x100001000, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0},
+			{"__dyld", "__DATA", 0x100001020, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0},
+			{"__la_symbol_ptr", "__DATA", 0x100001058, 0x0, 0x0, 0x2, 0x0, 0x0, 0x7},
+			{"__debug_abbrev", "__DWARF", 0x100002000, 0x36, 0x1000, 0x0, 0x0, 0x0, 0x0},
+			{"__debug_aranges", "__DWARF", 0x100002036, 0x30, 0x1036, 0x0, 0x0, 0x0, 0x0},
+			{"__debug_frame", "__DWARF", 0x100002066, 0x40, 0x1066, 0x0, 0x0, 0x0, 0x0},
+			{"__debug_info", "__DWARF", 0x1000020a6, 0x54, 0x10a6, 0x0, 0x0, 0x0, 0x0},
+			{"__debug_line", "__DWARF", 0x1000020fa, 0x47, 0x10fa, 0x0, 0x0, 0x0, 0x0},
+			{"__debug_pubnames", "__DWARF", 0x100002141, 0x1b, 0x1141, 0x0, 0x0, 0x0, 0x0},
+			{"__debug_str", "__DWARF", 0x10000215c, 0x60, 0x115c, 0x0, 0x0, 0x0, 0x0},
+		},
+		nil,
+	},
+	{
+		"testdata/clang-386-darwin-exec-with-rpath",
+		FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0x10, 0x42c, 0x1200085},
+		[]interface{}{
+			nil, // LC_SEGMENT
+			nil, // LC_SEGMENT
+			nil, // LC_SEGMENT
+			nil, // LC_SEGMENT
+			nil, // LC_DYLD_INFO_ONLY
+			nil, // LC_SYMTAB
+			nil, // LC_DYSYMTAB
+			nil, // LC_LOAD_DYLINKER
+			nil, // LC_UUID
+			nil, // LC_VERSION_MIN_MACOSX
+			nil, // LC_SOURCE_VERSION
+			nil, // LC_MAIN
+			nil, // LC_LOAD_DYLIB
+			&Rpath{nil, "/my/rpath"},
+			nil, // LC_FUNCTION_STARTS
+			nil, // LC_DATA_IN_CODE
+		},
+		nil,
+		nil,
+	},
+	{
+		"testdata/clang-amd64-darwin-exec-with-rpath",
+		FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0x10, 0x4c8, 0x200085},
+		[]interface{}{
+			nil, // LC_SEGMENT
+			nil, // LC_SEGMENT
+			nil, // LC_SEGMENT
+			nil, // LC_SEGMENT
+			nil, // LC_DYLD_INFO_ONLY
+			nil, // LC_SYMTAB
+			nil, // LC_DYSYMTAB
+			nil, // LC_LOAD_DYLINKER
+			nil, // LC_UUID
+			nil, // LC_VERSION_MIN_MACOSX
+			nil, // LC_SOURCE_VERSION
+			nil, // LC_MAIN
+			nil, // LC_LOAD_DYLIB
+			&Rpath{nil, "/my/rpath"},
+			nil, // LC_FUNCTION_STARTS
+			nil, // LC_DATA_IN_CODE
+		},
+		nil,
+		nil,
+	},
+	{
+		"testdata/clang-386-darwin.obj",
+		FileHeader{0xfeedface, Cpu386, 0x3, 0x1, 0x4, 0x138, 0x2000},
+		nil,
+		nil,
+		map[string][]Reloc{
+			"__text": []Reloc{
+				{
+					Addr:      0x1d,
+					Type:      uint8(GENERIC_RELOC_VANILLA),
+					Len:       2,
+					Pcrel:     true,
+					Extern:    true,
+					Value:     1,
+					Scattered: false,
+				},
+				{
+					Addr:      0xe,
+					Type:      uint8(GENERIC_RELOC_LOCAL_SECTDIFF),
+					Len:       2,
+					Pcrel:     false,
+					Value:     0x2d,
+					Scattered: true,
+				},
+				{
+					Addr:      0x0,
+					Type:      uint8(GENERIC_RELOC_PAIR),
+					Len:       2,
+					Pcrel:     false,
+					Value:     0xb,
+					Scattered: true,
+				},
+			},
+		},
+	},
+	{
+		"testdata/clang-amd64-darwin.obj",
+		FileHeader{0xfeedfacf, CpuAmd64, 0x3, 0x1, 0x4, 0x200, 0x2000},
+		nil,
+		nil,
+		map[string][]Reloc{
+			"__text": []Reloc{
+				{
+					Addr:   0x19,
+					Type:   uint8(X86_64_RELOC_BRANCH),
+					Len:    2,
+					Pcrel:  true,
+					Extern: true,
+					Value:  1,
+				},
+				{
+					Addr:   0xb,
+					Type:   uint8(X86_64_RELOC_SIGNED),
+					Len:    2,
+					Pcrel:  true,
+					Extern: false,
+					Value:  2,
+				},
+			},
+			"__compact_unwind": []Reloc{
+				{
+					Addr:   0x0,
+					Type:   uint8(X86_64_RELOC_UNSIGNED),
+					Len:    3,
+					Pcrel:  false,
+					Extern: false,
+					Value:  1,
+				},
+			},
+		},
+	},
+}
+
+func TestOpen(t *testing.T) {
+	for i := range fileTests {
+		tt := &fileTests[i]
+
+		f, err := Open(tt.file)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+		if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
+			t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr)
+			continue
+		}
+		for i, l := range f.Loads {
+			if len(l.Raw()) < 8 {
+				t.Errorf("open %s, command %d:\n\tload command %T don't have enough data\n", tt.file, i, l)
+			}
+		}
+		if tt.loads != nil {
+			for i, l := range f.Loads {
+				if i >= len(tt.loads) {
+					break
+				}
+
+				want := tt.loads[i]
+				if want == nil {
+					continue
+				}
+
+				switch l := l.(type) {
+				case *Segment:
+					have := &l.SegmentHeader
+					if !reflect.DeepEqual(have, want) {
+						t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
+					}
+				case *Dylib:
+					have := l
+					have.LoadBytes = nil
+					if !reflect.DeepEqual(have, want) {
+						t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
+					}
+				case *Rpath:
+					have := l
+					have.LoadBytes = nil
+					if !reflect.DeepEqual(have, want) {
+						t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
+					}
+				default:
+					t.Errorf("open %s, command %d: unknown load command\n\thave %#v\n\twant %#v\n", tt.file, i, l, want)
+				}
+			}
+			tn := len(tt.loads)
+			fn := len(f.Loads)
+			if tn != fn {
+				t.Errorf("open %s: len(Loads) = %d, want %d", tt.file, fn, tn)
+			}
+		}
+
+		if tt.sections != nil {
+			for i, sh := range f.Sections {
+				if i >= len(tt.sections) {
+					break
+				}
+				have := &sh.SectionHeader
+				want := tt.sections[i]
+				if !reflect.DeepEqual(have, want) {
+					t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
+				}
+			}
+			tn := len(tt.sections)
+			fn := len(f.Sections)
+			if tn != fn {
+				t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn)
+			}
+		}
+
+		if tt.relocations != nil {
+			for i, sh := range f.Sections {
+				have := sh.Relocs
+				want := tt.relocations[sh.Name]
+				if !reflect.DeepEqual(have, want) {
+					t.Errorf("open %s, relocations in section %d (%s):\n\thave %#v\n\twant %#v\n", tt.file, i, sh.Name, have, want)
+				}
+			}
+		}
+	}
+}
+
+func TestOpenFailure(t *testing.T) {
+	filename := "file.go"    // not a Mach-O file
+	_, err := Open(filename) // don't crash
+	if err == nil {
+		t.Errorf("open %s: succeeded unexpectedly", filename)
+	}
+}
+
+func TestOpenFat(t *testing.T) {
+	ff, err := OpenFat("testdata/fat-gcc-386-amd64-darwin-exec")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if ff.Magic != MagicFat {
+		t.Errorf("OpenFat: got magic number %#x, want %#x", ff.Magic, MagicFat)
+	}
+	if len(ff.Arches) != 2 {
+		t.Errorf("OpenFat: got %d architectures, want 2", len(ff.Arches))
+	}
+
+	for i := range ff.Arches {
+		arch := &ff.Arches[i]
+		ftArch := &fileTests[i]
+
+		if arch.Cpu != ftArch.hdr.Cpu || arch.SubCpu != ftArch.hdr.SubCpu {
+			t.Errorf("OpenFat: architecture #%d got cpu=%#x subtype=%#x, expected cpu=%#x, subtype=%#x", i, arch.Cpu, arch.SubCpu, ftArch.hdr.Cpu, ftArch.hdr.SubCpu)
+		}
+
+		if !reflect.DeepEqual(arch.FileHeader, ftArch.hdr) {
+			t.Errorf("OpenFat header:\n\tgot %#v\n\twant %#v\n", arch.FileHeader, ftArch.hdr)
+		}
+	}
+}
+
+func TestOpenFatFailure(t *testing.T) {
+	filename := "file.go" // not a Mach-O file
+	if _, err := OpenFat(filename); err == nil {
+		t.Errorf("OpenFat %s: succeeded unexpectedly", filename)
+	}
+
+	filename = "testdata/gcc-386-darwin-exec" // not a fat Mach-O
+	ff, err := OpenFat(filename)
+	if err != ErrNotFat {
+		t.Errorf("OpenFat %s: got %v, want ErrNotFat", filename, err)
+	}
+	if ff != nil {
+		t.Errorf("OpenFat %s: got %v, want nil", filename, ff)
+	}
+}
+
+func TestRelocTypeString(t *testing.T) {
+	if X86_64_RELOC_BRANCH.String() != "X86_64_RELOC_BRANCH" {
+		t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.String(), "X86_64_RELOC_BRANCH")
+	}
+	if X86_64_RELOC_BRANCH.GoString() != "macho.X86_64_RELOC_BRANCH" {
+		t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.GoString(), "macho.X86_64_RELOC_BRANCH")
+	}
+}
+
+func TestTypeString(t *testing.T) {
+	if TypeExec.String() != "Exec" {
+		t.Errorf("got %v, want %v", TypeExec.String(), "Exec")
+	}
+	if TypeExec.GoString() != "macho.Exec" {
+		t.Errorf("got %v, want %v", TypeExec.GoString(), "macho.Exec")
+	}
+}
diff --git a/cmd/splitdwarf/internal/macho/macho.go b/cmd/splitdwarf/internal/macho/macho.go
new file mode 100644
index 0000000..7bc1950
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/macho.go
@@ -0,0 +1,336 @@
+// Copyright 2009 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 header data structures
+// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
+
+package macho
+
+import "strconv"
+
+// A FileHeader represents a Mach-O file header.
+type FileHeader struct {
+	Magic  uint32
+	Cpu    Cpu
+	SubCpu uint32
+	Type   Type
+	Ncmd   uint32
+	Cmdsz  uint32
+	Flags  uint32
+}
+
+const (
+	fileHeaderSize32 = 7 * 4
+	fileHeaderSize64 = 8 * 4
+)
+
+const (
+	Magic32  uint32 = 0xfeedface
+	Magic64  uint32 = 0xfeedfacf
+	MagicFat uint32 = 0xcafebabe
+)
+
+// A Type is the Mach-O file type, e.g. an object file, executable, or dynamic library.
+type Type uint32
+
+const (
+	TypeObj    Type = 1
+	TypeExec   Type = 2
+	TypeDylib  Type = 6
+	TypeBundle Type = 8
+)
+
+var typeStrings = []intName{
+	{uint32(TypeObj), "Obj"},
+	{uint32(TypeExec), "Exec"},
+	{uint32(TypeDylib), "Dylib"},
+	{uint32(TypeBundle), "Bundle"},
+}
+
+func (t Type) String() string   { return stringName(uint32(t), typeStrings, false) }
+func (t Type) GoString() string { return stringName(uint32(t), typeStrings, true) }
+
+// A Cpu is a Mach-O cpu type.
+type Cpu uint32
+
+const cpuArch64 = 0x01000000
+
+const (
+	Cpu386   Cpu = 7
+	CpuAmd64 Cpu = Cpu386 | cpuArch64
+	CpuArm   Cpu = 12
+	CpuArm64 Cpu = CpuArm | cpuArch64
+	CpuPpc   Cpu = 18
+	CpuPpc64 Cpu = CpuPpc | cpuArch64
+)
+
+var cpuStrings = []intName{
+	{uint32(Cpu386), "Cpu386"},
+	{uint32(CpuAmd64), "CpuAmd64"},
+	{uint32(CpuArm), "CpuArm"},
+	{uint32(CpuArm64), "CpuArm64"},
+	{uint32(CpuPpc), "CpuPpc"},
+	{uint32(CpuPpc64), "CpuPpc64"},
+}
+
+func (i Cpu) String() string   { return stringName(uint32(i), cpuStrings, false) }
+func (i Cpu) GoString() string { return stringName(uint32(i), cpuStrings, true) }
+
+// A LoadCmd is a Mach-O load command.
+type LoadCmd uint32
+
+const (
+	LoadCmdSegment    LoadCmd = 0x1
+	LoadCmdSymtab     LoadCmd = 0x2
+	LoadCmdThread     LoadCmd = 0x4
+	LoadCmdUnixThread LoadCmd = 0x5 // thread+stack
+	LoadCmdDysymtab   LoadCmd = 0xb
+	LoadCmdDylib      LoadCmd = 0xc // load dylib command
+	LoadCmdDylinker   LoadCmd = 0xf // id dylinker command (not load dylinker command)
+	LoadCmdSegment64  LoadCmd = 0x19
+	LoadCmdRpath      LoadCmd = 0x8000001c
+)
+
+var cmdStrings = []intName{
+	{uint32(LoadCmdSegment), "LoadCmdSegment"},
+	{uint32(LoadCmdThread), "LoadCmdThread"},
+	{uint32(LoadCmdUnixThread), "LoadCmdUnixThread"},
+	{uint32(LoadCmdDylib), "LoadCmdDylib"},
+	{uint32(LoadCmdSegment64), "LoadCmdSegment64"},
+	{uint32(LoadCmdRpath), "LoadCmdRpath"},
+}
+
+func (i LoadCmd) String() string   { return stringName(uint32(i), cmdStrings, false) }
+func (i LoadCmd) GoString() string { return stringName(uint32(i), cmdStrings, true) }
+
+type (
+	// A Segment32 is a 32-bit Mach-O segment load command.
+	Segment32 struct {
+		Cmd     LoadCmd
+		Len     uint32
+		Name    [16]byte
+		Addr    uint32
+		Memsz   uint32
+		Offset  uint32
+		Filesz  uint32
+		Maxprot uint32
+		Prot    uint32
+		Nsect   uint32
+		Flag    uint32
+	}
+
+	// A Segment64 is a 64-bit Mach-O segment load command.
+	Segment64 struct {
+		Cmd     LoadCmd
+		Len     uint32
+		Name    [16]byte
+		Addr    uint64
+		Memsz   uint64
+		Offset  uint64
+		Filesz  uint64
+		Maxprot uint32
+		Prot    uint32
+		Nsect   uint32
+		Flag    uint32
+	}
+
+	// A SymtabCmd is a Mach-O symbol table command.
+	SymtabCmd struct {
+		Cmd     LoadCmd
+		Len     uint32
+		Symoff  uint32
+		Nsyms   uint32
+		Stroff  uint32
+		Strsize uint32
+	}
+
+	// A DysymtabCmd is a Mach-O dynamic symbol table command.
+	DysymtabCmd struct {
+		Cmd            LoadCmd
+		Len            uint32
+		Ilocalsym      uint32
+		Nlocalsym      uint32
+		Iextdefsym     uint32
+		Nextdefsym     uint32
+		Iundefsym      uint32
+		Nundefsym      uint32
+		Tocoffset      uint32
+		Ntoc           uint32
+		Modtaboff      uint32
+		Nmodtab        uint32
+		Extrefsymoff   uint32
+		Nextrefsyms    uint32
+		Indirectsymoff uint32
+		Nindirectsyms  uint32
+		Extreloff      uint32
+		Nextrel        uint32
+		Locreloff      uint32
+		Nlocrel        uint32
+	}
+
+	// A DylibCmd is a Mach-O load dynamic library command.
+	DylibCmd struct {
+		Cmd            LoadCmd
+		Len            uint32
+		Name           uint32
+		Time           uint32
+		CurrentVersion uint32
+		CompatVersion  uint32
+	}
+
+	// A RpathCmd is a Mach-O rpath command.
+	RpathCmd struct {
+		Cmd  LoadCmd
+		Len  uint32
+		Path uint32
+	}
+
+	// A Thread is a Mach-O thread state command.
+	Thread struct {
+		Cmd  LoadCmd
+		Len  uint32
+		Type uint32
+		Data []uint32
+	}
+)
+
+const (
+	FlagNoUndefs              uint32 = 0x1
+	FlagIncrLink              uint32 = 0x2
+	FlagDyldLink              uint32 = 0x4
+	FlagBindAtLoad            uint32 = 0x8
+	FlagPrebound              uint32 = 0x10
+	FlagSplitSegs             uint32 = 0x20
+	FlagLazyInit              uint32 = 0x40
+	FlagTwoLevel              uint32 = 0x80
+	FlagForceFlat             uint32 = 0x100
+	FlagNoMultiDefs           uint32 = 0x200
+	FlagNoFixPrebinding       uint32 = 0x400
+	FlagPrebindable           uint32 = 0x800
+	FlagAllModsBound          uint32 = 0x1000
+	FlagSubsectionsViaSymbols uint32 = 0x2000
+	FlagCanonical             uint32 = 0x4000
+	FlagWeakDefines           uint32 = 0x8000
+	FlagBindsToWeak           uint32 = 0x10000
+	FlagAllowStackExecution   uint32 = 0x20000
+	FlagRootSafe              uint32 = 0x40000
+	FlagSetuidSafe            uint32 = 0x80000
+	FlagNoReexportedDylibs    uint32 = 0x100000
+	FlagPIE                   uint32 = 0x200000
+	FlagDeadStrippableDylib   uint32 = 0x400000
+	FlagHasTLVDescriptors     uint32 = 0x800000
+	FlagNoHeapExecution       uint32 = 0x1000000
+	FlagAppExtensionSafe      uint32 = 0x2000000
+)
+
+// A Section32 is a 32-bit Mach-O section header.
+type Section32 struct {
+	Name     [16]byte
+	Seg      [16]byte
+	Addr     uint32
+	Size     uint32
+	Offset   uint32
+	Align    uint32
+	Reloff   uint32
+	Nreloc   uint32
+	Flags    uint32
+	Reserve1 uint32
+	Reserve2 uint32
+}
+
+// A Section64 is a 64-bit Mach-O section header.
+type Section64 struct {
+	Name     [16]byte
+	Seg      [16]byte
+	Addr     uint64
+	Size     uint64
+	Offset   uint32
+	Align    uint32
+	Reloff   uint32
+	Nreloc   uint32
+	Flags    uint32
+	Reserve1 uint32
+	Reserve2 uint32
+	Reserve3 uint32
+}
+
+// An Nlist32 is a Mach-O 32-bit symbol table entry.
+type Nlist32 struct {
+	Name  uint32
+	Type  uint8
+	Sect  uint8
+	Desc  uint16
+	Value uint32
+}
+
+// An Nlist64 is a Mach-O 64-bit symbol table entry.
+type Nlist64 struct {
+	Name  uint32
+	Type  uint8
+	Sect  uint8
+	Desc  uint16
+	Value uint64
+}
+
+// Regs386 is the Mach-O 386 register structure.
+type Regs386 struct {
+	AX    uint32
+	BX    uint32
+	CX    uint32
+	DX    uint32
+	DI    uint32
+	SI    uint32
+	BP    uint32
+	SP    uint32
+	SS    uint32
+	FLAGS uint32
+	IP    uint32
+	CS    uint32
+	DS    uint32
+	ES    uint32
+	FS    uint32
+	GS    uint32
+}
+
+// RegsAMD64 is the Mach-O AMD64 register structure.
+type RegsAMD64 struct {
+	AX    uint64
+	BX    uint64
+	CX    uint64
+	DX    uint64
+	DI    uint64
+	SI    uint64
+	BP    uint64
+	SP    uint64
+	R8    uint64
+	R9    uint64
+	R10   uint64
+	R11   uint64
+	R12   uint64
+	R13   uint64
+	R14   uint64
+	R15   uint64
+	IP    uint64
+	FLAGS uint64
+	CS    uint64
+	FS    uint64
+	GS    uint64
+}
+
+type intName struct {
+	i uint32
+	s string
+}
+
+func stringName(i uint32, names []intName, goSyntax bool) string {
+	for _, n := range names {
+		if n.i == i {
+			if goSyntax {
+				return "macho." + n.s
+			}
+			return n.s
+		}
+	}
+	return strconv.FormatUint(uint64(i), 10)
+}
diff --git a/cmd/splitdwarf/internal/macho/reloctype.go b/cmd/splitdwarf/internal/macho/reloctype.go
new file mode 100644
index 0000000..496dfce
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/reloctype.go
@@ -0,0 +1,72 @@
+// Copyright 2017 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 macho
+
+//go:generate stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go
+
+type RelocTypeGeneric int
+
+const (
+	GENERIC_RELOC_VANILLA        RelocTypeGeneric = 0
+	GENERIC_RELOC_PAIR           RelocTypeGeneric = 1
+	GENERIC_RELOC_SECTDIFF       RelocTypeGeneric = 2
+	GENERIC_RELOC_PB_LA_PTR      RelocTypeGeneric = 3
+	GENERIC_RELOC_LOCAL_SECTDIFF RelocTypeGeneric = 4
+	GENERIC_RELOC_TLV            RelocTypeGeneric = 5
+)
+
+func (r RelocTypeGeneric) GoString() string { return "macho." + r.String() }
+
+type RelocTypeX86_64 int
+
+const (
+	X86_64_RELOC_UNSIGNED   RelocTypeX86_64 = 0
+	X86_64_RELOC_SIGNED     RelocTypeX86_64 = 1
+	X86_64_RELOC_BRANCH     RelocTypeX86_64 = 2
+	X86_64_RELOC_GOT_LOAD   RelocTypeX86_64 = 3
+	X86_64_RELOC_GOT        RelocTypeX86_64 = 4
+	X86_64_RELOC_SUBTRACTOR RelocTypeX86_64 = 5
+	X86_64_RELOC_SIGNED_1   RelocTypeX86_64 = 6
+	X86_64_RELOC_SIGNED_2   RelocTypeX86_64 = 7
+	X86_64_RELOC_SIGNED_4   RelocTypeX86_64 = 8
+	X86_64_RELOC_TLV        RelocTypeX86_64 = 9
+)
+
+func (r RelocTypeX86_64) GoString() string { return "macho." + r.String() }
+
+type RelocTypeARM int
+
+const (
+	ARM_RELOC_VANILLA        RelocTypeARM = 0
+	ARM_RELOC_PAIR           RelocTypeARM = 1
+	ARM_RELOC_SECTDIFF       RelocTypeARM = 2
+	ARM_RELOC_LOCAL_SECTDIFF RelocTypeARM = 3
+	ARM_RELOC_PB_LA_PTR      RelocTypeARM = 4
+	ARM_RELOC_BR24           RelocTypeARM = 5
+	ARM_THUMB_RELOC_BR22     RelocTypeARM = 6
+	ARM_THUMB_32BIT_BRANCH   RelocTypeARM = 7
+	ARM_RELOC_HALF           RelocTypeARM = 8
+	ARM_RELOC_HALF_SECTDIFF  RelocTypeARM = 9
+)
+
+func (r RelocTypeARM) GoString() string { return "macho." + r.String() }
+
+type RelocTypeARM64 int
+
+const (
+	ARM64_RELOC_UNSIGNED            RelocTypeARM64 = 0
+	ARM64_RELOC_SUBTRACTOR          RelocTypeARM64 = 1
+	ARM64_RELOC_BRANCH26            RelocTypeARM64 = 2
+	ARM64_RELOC_PAGE21              RelocTypeARM64 = 3
+	ARM64_RELOC_PAGEOFF12           RelocTypeARM64 = 4
+	ARM64_RELOC_GOT_LOAD_PAGE21     RelocTypeARM64 = 5
+	ARM64_RELOC_GOT_LOAD_PAGEOFF12  RelocTypeARM64 = 6
+	ARM64_RELOC_POINTER_TO_GOT      RelocTypeARM64 = 7
+	ARM64_RELOC_TLVP_LOAD_PAGE21    RelocTypeARM64 = 8
+	ARM64_RELOC_TLVP_LOAD_PAGEOFF12 RelocTypeARM64 = 9
+	ARM64_RELOC_ADDEND              RelocTypeARM64 = 10
+)
+
+func (r RelocTypeARM64) GoString() string { return "macho." + r.String() }
diff --git a/cmd/splitdwarf/internal/macho/reloctype_string.go b/cmd/splitdwarf/internal/macho/reloctype_string.go
new file mode 100644
index 0000000..9c2b131
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/reloctype_string.go
@@ -0,0 +1,49 @@
+// Code generated by "stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go"; DO NOT EDIT.
+
+package macho
+
+import "strconv"
+
+const _RelocTypeGeneric_name = "GENERIC_RELOC_VANILLAGENERIC_RELOC_PAIRGENERIC_RELOC_SECTDIFFGENERIC_RELOC_PB_LA_PTRGENERIC_RELOC_LOCAL_SECTDIFFGENERIC_RELOC_TLV"
+
+var _RelocTypeGeneric_index = [...]uint8{0, 21, 39, 61, 84, 112, 129}
+
+func (i RelocTypeGeneric) String() string {
+	if i < 0 || i >= RelocTypeGeneric(len(_RelocTypeGeneric_index)-1) {
+		return "RelocTypeGeneric(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _RelocTypeGeneric_name[_RelocTypeGeneric_index[i]:_RelocTypeGeneric_index[i+1]]
+}
+
+const _RelocTypeX86_64_name = "X86_64_RELOC_UNSIGNEDX86_64_RELOC_SIGNEDX86_64_RELOC_BRANCHX86_64_RELOC_GOT_LOADX86_64_RELOC_GOTX86_64_RELOC_SUBTRACTORX86_64_RELOC_SIGNED_1X86_64_RELOC_SIGNED_2X86_64_RELOC_SIGNED_4X86_64_RELOC_TLV"
+
+var _RelocTypeX86_64_index = [...]uint8{0, 21, 40, 59, 80, 96, 119, 140, 161, 182, 198}
+
+func (i RelocTypeX86_64) String() string {
+	if i < 0 || i >= RelocTypeX86_64(len(_RelocTypeX86_64_index)-1) {
+		return "RelocTypeX86_64(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _RelocTypeX86_64_name[_RelocTypeX86_64_index[i]:_RelocTypeX86_64_index[i+1]]
+}
+
+const _RelocTypeARM_name = "ARM_RELOC_VANILLAARM_RELOC_PAIRARM_RELOC_SECTDIFFARM_RELOC_LOCAL_SECTDIFFARM_RELOC_PB_LA_PTRARM_RELOC_BR24ARM_THUMB_RELOC_BR22ARM_THUMB_32BIT_BRANCHARM_RELOC_HALFARM_RELOC_HALF_SECTDIFF"
+
+var _RelocTypeARM_index = [...]uint8{0, 17, 31, 49, 73, 92, 106, 126, 148, 162, 185}
+
+func (i RelocTypeARM) String() string {
+	if i < 0 || i >= RelocTypeARM(len(_RelocTypeARM_index)-1) {
+		return "RelocTypeARM(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _RelocTypeARM_name[_RelocTypeARM_index[i]:_RelocTypeARM_index[i+1]]
+}
+
+const _RelocTypeARM64_name = "ARM64_RELOC_UNSIGNEDARM64_RELOC_SUBTRACTORARM64_RELOC_BRANCH26ARM64_RELOC_PAGE21ARM64_RELOC_PAGEOFF12ARM64_RELOC_GOT_LOAD_PAGE21ARM64_RELOC_GOT_LOAD_PAGEOFF12ARM64_RELOC_POINTER_TO_GOTARM64_RELOC_TLVP_LOAD_PAGE21ARM64_RELOC_TLVP_LOAD_PAGEOFF12ARM64_RELOC_ADDEND"
+
+var _RelocTypeARM64_index = [...]uint16{0, 20, 42, 62, 80, 101, 128, 158, 184, 212, 243, 261}
+
+func (i RelocTypeARM64) String() string {
+	if i < 0 || i >= RelocTypeARM64(len(_RelocTypeARM64_index)-1) {
+		return "RelocTypeARM64(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _RelocTypeARM64_name[_RelocTypeARM64_index[i]:_RelocTypeARM64_index[i+1]]
+}
diff --git a/cmd/splitdwarf/internal/macho/testdata/clang-386-darwin-exec-with-rpath b/cmd/splitdwarf/internal/macho/testdata/clang-386-darwin-exec-with-rpath
new file mode 100644
index 0000000..a8720fe
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/clang-386-darwin-exec-with-rpath
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/clang-386-darwin.obj b/cmd/splitdwarf/internal/macho/testdata/clang-386-darwin.obj
new file mode 100644
index 0000000..e79dc57
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/clang-386-darwin.obj
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/clang-amd64-darwin-exec-with-rpath b/cmd/splitdwarf/internal/macho/testdata/clang-amd64-darwin-exec-with-rpath
new file mode 100644
index 0000000..191c768
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/clang-amd64-darwin-exec-with-rpath
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/clang-amd64-darwin.obj b/cmd/splitdwarf/internal/macho/testdata/clang-amd64-darwin.obj
new file mode 100644
index 0000000..23cc3c1
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/clang-amd64-darwin.obj
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/fat-gcc-386-amd64-darwin-exec b/cmd/splitdwarf/internal/macho/testdata/fat-gcc-386-amd64-darwin-exec
new file mode 100644
index 0000000..7efd193
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/fat-gcc-386-amd64-darwin-exec
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/gcc-386-darwin-exec b/cmd/splitdwarf/internal/macho/testdata/gcc-386-darwin-exec
new file mode 100644
index 0000000..03ba1ba
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/gcc-386-darwin-exec
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/gcc-amd64-darwin-exec b/cmd/splitdwarf/internal/macho/testdata/gcc-amd64-darwin-exec
new file mode 100644
index 0000000..5155a5a
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/gcc-amd64-darwin-exec
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/gcc-amd64-darwin-exec-debug b/cmd/splitdwarf/internal/macho/testdata/gcc-amd64-darwin-exec-debug
new file mode 100644
index 0000000..a47d3ae
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/gcc-amd64-darwin-exec-debug
Binary files differ
diff --git a/cmd/splitdwarf/internal/macho/testdata/hello.c b/cmd/splitdwarf/internal/macho/testdata/hello.c
new file mode 100644
index 0000000..a689d36
--- /dev/null
+++ b/cmd/splitdwarf/internal/macho/testdata/hello.c
@@ -0,0 +1,8 @@
+#include <stdio.h>
+
+int
+main(void)
+{
+	printf("hello, world\n");
+	return 0;
+}