Home | History | Annotate | Download | only in macho
      1 // Copyright 2014 The Go Authors.  All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package macho
      6 
      7 import (
      8 	"encoding/binary"
      9 	"fmt"
     10 	"io"
     11 	"os"
     12 )
     13 
     14 // A FatFile is a Mach-O universal binary that contains at least one architecture.
     15 type FatFile struct {
     16 	Magic  uint32
     17 	Arches []FatArch
     18 	closer io.Closer
     19 }
     20 
     21 // A FatArchHeader represents a fat header for a specific image architecture.
     22 type FatArchHeader struct {
     23 	Cpu    Cpu
     24 	SubCpu uint32
     25 	Offset uint32
     26 	Size   uint32
     27 	Align  uint32
     28 }
     29 
     30 const fatArchHeaderSize = 5 * 4
     31 
     32 // A FatArch is a Mach-O File inside a FatFile.
     33 type FatArch struct {
     34 	FatArchHeader
     35 	*File
     36 }
     37 
     38 // ErrNotFat is returned from NewFatFile or OpenFat when the file is not a
     39 // universal binary but may be a thin binary, based on its magic number.
     40 var ErrNotFat = &FormatError{0, "not a fat Mach-O file", nil}
     41 
     42 // NewFatFile creates a new FatFile for accessing all the Mach-O images in a
     43 // universal binary. The Mach-O binary is expected to start at position 0 in
     44 // the ReaderAt.
     45 func NewFatFile(r io.ReaderAt) (*FatFile, error) {
     46 	var ff FatFile
     47 	sr := io.NewSectionReader(r, 0, 1<<63-1)
     48 
     49 	// Read the fat_header struct, which is always in big endian.
     50 	// Start with the magic number.
     51 	err := binary.Read(sr, binary.BigEndian, &ff.Magic)
     52 	if err != nil {
     53 		return nil, &FormatError{0, "error reading magic number", nil}
     54 	} else if ff.Magic != MagicFat {
     55 		// See if this is a Mach-O file via its magic number. The magic
     56 		// must be converted to little endian first though.
     57 		var buf [4]byte
     58 		binary.BigEndian.PutUint32(buf[:], ff.Magic)
     59 		leMagic := binary.LittleEndian.Uint32(buf[:])
     60 		if leMagic == Magic32 || leMagic == Magic64 {
     61 			return nil, ErrNotFat
     62 		} else {
     63 			return nil, &FormatError{0, "invalid magic number", nil}
     64 		}
     65 	}
     66 	offset := int64(4)
     67 
     68 	// Read the number of FatArchHeaders that come after the fat_header.
     69 	var narch uint32
     70 	err = binary.Read(sr, binary.BigEndian, &narch)
     71 	if err != nil {
     72 		return nil, &FormatError{offset, "invalid fat_header", nil}
     73 	}
     74 	offset += 4
     75 
     76 	if narch < 1 {
     77 		return nil, &FormatError{offset, "file contains no images", nil}
     78 	}
     79 
     80 	// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
     81 	// there are not duplicate architectures.
     82 	seenArches := make(map[uint64]bool, narch)
     83 	// Make sure that all images are for the same MH_ type.
     84 	var machoType Type
     85 
     86 	// Following the fat_header comes narch fat_arch structs that index
     87 	// Mach-O images further in the file.
     88 	ff.Arches = make([]FatArch, narch)
     89 	for i := uint32(0); i < narch; i++ {
     90 		fa := &ff.Arches[i]
     91 		err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
     92 		if err != nil {
     93 			return nil, &FormatError{offset, "invalid fat_arch header", nil}
     94 		}
     95 		offset += fatArchHeaderSize
     96 
     97 		fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
     98 		fa.File, err = NewFile(fr)
     99 		if err != nil {
    100 			return nil, err
    101 		}
    102 
    103 		// Make sure the architecture for this image is not duplicate.
    104 		seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
    105 		if o, k := seenArches[seenArch]; o || k {
    106 			return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil}
    107 		}
    108 		seenArches[seenArch] = true
    109 
    110 		// Make sure the Mach-O type matches that of the first image.
    111 		if i == 0 {
    112 			machoType = fa.Type
    113 		} else {
    114 			if fa.Type != machoType {
    115 				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}
    116 			}
    117 		}
    118 	}
    119 
    120 	return &ff, nil
    121 }
    122 
    123 // OpenFat opens the named file using os.Open and prepares it for use as a Mach-O
    124 // universal binary.
    125 func OpenFat(name string) (ff *FatFile, err error) {
    126 	f, err := os.Open(name)
    127 	if err != nil {
    128 		return nil, err
    129 	}
    130 	ff, err = NewFatFile(f)
    131 	if err != nil {
    132 		f.Close()
    133 		return nil, err
    134 	}
    135 	ff.closer = f
    136 	return
    137 }
    138 
    139 func (ff *FatFile) Close() error {
    140 	var err error
    141 	if ff.closer != nil {
    142 		err = ff.closer.Close()
    143 		ff.closer = nil
    144 	}
    145 	return err
    146 }
    147