// 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" "debug/dwarf" "encoding/binary" "fmt" "io" "os" ) // 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 } type Section struct { SectionHeader // 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 } /* * 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 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 f.pushSection(sh, r) } 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 f.pushSection(sh, r) } } 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 } func (f *File) pushSection(sh *Section, r io.ReaderAt) { f.Sections = append(f.Sections, sh) sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size)) sh.ReaderAt = sh.sr } func cstring(b []byte) string { var i int for i = 0; i < len(b) && b[i] != 0; i++ { } 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) { // There are many other DWARF sections, but these // are the ones the debug/dwarf package uses. // Don't bother loading others. var names = [...]string{"abbrev", "info", "line", "str"} var dat [len(names)][]byte for i, name := range names { name = "__debug_" + name s := f.Section(name) if s == nil { continue } b, err := s.Data() if err != nil && uint64(len(b)) < s.Size { return nil, err } dat[i] = b } abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3] return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str) } // 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 }