Go Home Page
The Go Programming Language

Source file src/pkg/debug/macho/file.go

// 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]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 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[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.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[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) 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)
}