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