fat.mx raw

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