// 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, as defined by // http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html. 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; 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, os.Error) { dat := make([]byte, s.sr.Size()); n, err := s.sr.ReadAt(dat, 0); 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, os.Error) { dat := make([]byte, s.sr.Size()); n, err := s.sr.ReadAt(dat, 0); 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) } /* * Mach-O reader */ type FormatError struct { off int64; msg string; val interface{}; } func (e *FormatError) String() 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, os.Error) { f, err := os.Open(name, os.O_RDONLY, 0); 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() os.Error { var err os.Error; if f.closer != nil { err = f.closer.Close(); f.closer = nil; } return err; } // NewFile creates a new File for acecssing 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, os.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]uint8; if _, err := r.ReadAt(&ident, 0); err != nil { return nil, err } be := binary.BigEndian.Uint32(&ident); le := binary.LittleEndian.Uint32(&ident); switch Magic32 &^ 1 { case be &^ 1: f.ByteOrder = binary.BigEndian; f.Magic = be; case le &^ 1: f.ByteOrder = binary.LittleEndian; f.Magic = le; } // 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 LoadCmdSegment: var seg32 Segment32; b := bytes.NewBuffer(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); 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); sh.Seg = cstring(&sh32.Seg); 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.NewBuffer(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); 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); sh.Seg = cstring(&sh64.Seg); 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) pushSection(sh *Section, r io.ReaderAt) { n := len(f.Sections); if n >= cap(f.Sections) { m := (n + 1) * 2; new := make([]*Section, n, m); for i, sh := range f.Sections { new[i] = sh } f.Sections = new; } f.Sections = f.Sections[0 : n+1]; f.Sections[n] = 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, os.Error) { // There are many other DWARF sections, but these // are the required ones, and the debug/dwarf package // does not use the others, so don't bother loading them. var names = [...]string{"abbrev", "info", "str"}; var dat [len(names)][]byte; for i, name := range names { name = "__debug_" + name; s := f.Section(name); if s == nil { return nil, os.NewError("missing Mach-O section " + name) } b, err := s.Data(); if err != nil && uint64(len(b)) < s.Size { return nil, err } dat[i] = b; } abbrev, info, str := dat[0], dat[1], dat[2]; return dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str); }