| // Copyright 2014 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 | |
| import ( | |
| "encoding/binary" | |
| "fmt" | |
| "internal/saferio" | |
| "io" | |
| "os" | |
| ) | |
| // A FatFile is a Mach-O universal binary that contains at least one architecture. | |
| type FatFile struct { | |
| Magic uint32 | |
| Arches []FatArch | |
| closer io.Closer | |
| } | |
| // A FatArchHeader represents a fat header for a specific image architecture. | |
| type FatArchHeader struct { | |
| Cpu Cpu | |
| SubCpu uint32 | |
| Offset uint32 | |
| Size uint32 | |
| Align uint32 | |
| } | |
| const fatArchHeaderSize = 5 * 4 | |
| // A FatArch is a Mach-O File inside a FatFile. | |
| type FatArch struct { | |
| FatArchHeader | |
| *File | |
| } | |
| // ErrNotFat is returned from [NewFatFile] or [OpenFat] when the file is not a | |
| // universal binary but may be a thin binary, based on its magic number. | |
| var ErrNotFat = &FormatError{0, "not a fat Mach-O file", nil} | |
| // NewFatFile creates a new [FatFile] for accessing all the Mach-O images in a | |
| // universal binary. The Mach-O binary is expected to start at position 0 in | |
| // the ReaderAt. | |
| func NewFatFile(r io.ReaderAt) (*FatFile, error) { | |
| var ff FatFile | |
| sr := io.NewSectionReader(r, 0, 1<<63-1) | |
| // Read the fat_header struct, which is always in big endian. | |
| // Start with the magic number. | |
| err := binary.Read(sr, binary.BigEndian, &ff.Magic) | |
| if err != nil { | |
| return nil, &FormatError{0, "error reading magic number", nil} | |
| } else if ff.Magic != MagicFat { | |
| // See if this is a Mach-O file via its magic number. The magic | |
| // must be converted to little endian first though. | |
| var buf [4]byte | |
| binary.BigEndian.PutUint32(buf[:], ff.Magic) | |
| leMagic := binary.LittleEndian.Uint32(buf[:]) | |
| if leMagic == Magic32 || leMagic == Magic64 { | |
| return nil, ErrNotFat | |
| } else { | |
| return nil, &FormatError{0, "invalid magic number", nil} | |
| } | |
| } | |
| offset := int64(4) | |
| // Read the number of FatArchHeaders that come after the fat_header. | |
| var narch uint32 | |
| err = binary.Read(sr, binary.BigEndian, &narch) | |
| if err != nil { | |
| return nil, &FormatError{offset, "invalid fat_header", nil} | |
| } | |
| offset += 4 | |
| if narch < 1 { | |
| return nil, &FormatError{offset, "file contains no images", nil} | |
| } | |
| // Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure | |
| // there are not duplicate architectures. | |
| seenArches := make(map[uint64]bool) | |
| // Make sure that all images are for the same MH_ type. | |
| var machoType Type | |
| // Following the fat_header comes narch fat_arch structs that index | |
| // Mach-O images further in the file. | |
| c := saferio.SliceCap[FatArch](uint64(narch)) | |
| if c < 0 { | |
| return nil, &FormatError{offset, "too many images", nil} | |
| } | |
| ff.Arches = make([]FatArch, 0, c) | |
| for i := uint32(0); i < narch; i++ { | |
| var fa FatArch | |
| err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader) | |
| if err != nil { | |
| return nil, &FormatError{offset, "invalid fat_arch header", nil} | |
| } | |
| offset += fatArchHeaderSize | |
| fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size)) | |
| fa.File, err = NewFile(fr) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Make sure the architecture for this image is not duplicate. | |
| seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu) | |
| if o, k := seenArches[seenArch]; o || k { | |
| return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil} | |
| } | |
| seenArches[seenArch] = true | |
| // Make sure the Mach-O type matches that of the first image. | |
| if i == 0 { | |
| machoType = fa.Type | |
| } else { | |
| if fa.Type != machoType { | |
| 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} | |
| } | |
| } | |
| ff.Arches = append(ff.Arches, fa) | |
| } | |
| return &ff, nil | |
| } | |
| // OpenFat opens the named file using [os.Open] and prepares it for use as a Mach-O | |
| // universal binary. | |
| func OpenFat(name string) (*FatFile, error) { | |
| f, err := os.Open(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| ff, err := NewFatFile(f) | |
| if err != nil { | |
| f.Close() | |
| return nil, err | |
| } | |
| ff.closer = f | |
| return ff, nil | |
| } | |
| func (ff *FatFile) Close() error { | |
| var err error | |
| if ff.closer != nil { | |
| err = ff.closer.Close() | |
| ff.closer = nil | |
| } | |
| return err | |
| } |