]> Cypherpunks repositories - gostls13.git/commitdiff
debug/macho: Add support for opening fat/universal binaries.
authorRobert Sesek <rsesek@google.com>
Thu, 13 Feb 2014 00:04:13 +0000 (11:04 +1100)
committerDave Cheney <dave@cheney.net>
Thu, 13 Feb 2014 00:04:13 +0000 (11:04 +1100)
New testdata was created from existing using:
$ lipo gcc-386-darwin-exec gcc-amd64-darwin-exec -create -output fat-gcc-386-amd64-darwin-exec

Fixes #7250.

LGTM=dave
R=golang-codereviews, dave, josharian, bradfitz
CC=golang-codereviews
https://golang.org/cl/60190043

src/pkg/debug/macho/fat.go [new file with mode: 0644]
src/pkg/debug/macho/file_test.go
src/pkg/debug/macho/macho.go

diff --git a/src/pkg/debug/macho/fat.go b/src/pkg/debug/macho/fat.go
new file mode 100644 (file)
index 0000000..93b8315
--- /dev/null
@@ -0,0 +1,146 @@
+// 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"
+       "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, narch)
+       // 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.
+       ff.Arches = make([]FatArch, narch)
+       for i := uint32(0); i < narch; i++ {
+               fa := &ff.Arches[i]
+               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}
+                       }
+               }
+       }
+
+       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) (ff *FatFile, err 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
+}
+
+func (ff *FatFile) Close() error {
+       var err error
+       if ff.closer != nil {
+               err = ff.closer.Close()
+               ff.closer = nil
+       }
+       return err
+}
index 640225b3291b138c6462107d5b62adf3024c98f1..0de9184c22746a22d4e1eb05f8b08b8ec7041b8b 100644 (file)
@@ -165,3 +165,46 @@ func TestOpenFailure(t *testing.T) {
                t.Errorf("open %s: succeeded unexpectedly", filename)
        }
 }
+
+func TestOpenFat(t *testing.T) {
+       ff, err := OpenFat("testdata/fat-gcc-386-amd64-darwin-exec")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       if ff.Magic != MagicFat {
+               t.Errorf("OpenFat: got magic number %#x, want %#x", ff.Magic, MagicFat)
+       }
+       if len(ff.Arches) != 2 {
+               t.Errorf("OpenFat: got %d architectures, want 2", len(ff.Arches))
+       }
+
+       for i := range ff.Arches {
+               arch := &ff.Arches[i]
+               ftArch := &fileTests[i]
+
+               if arch.Cpu != ftArch.hdr.Cpu || arch.SubCpu != ftArch.hdr.SubCpu {
+                       t.Error("OpenFat: architecture #%d got cpu=%#x subtype=%#x, expected cpu=%#x, subtype=%#x", i, arch.Cpu, arch.SubCpu, ftArch.hdr.Cpu, ftArch.hdr.SubCpu)
+               }
+
+               if !reflect.DeepEqual(arch.FileHeader, ftArch.hdr) {
+                       t.Errorf("OpenFat header:\n\tgot %#v\n\twant %#v\n", arch.FileHeader, ftArch.hdr)
+               }
+       }
+}
+
+func TestOpenFatFailure(t *testing.T) {
+       filename := "file.go" // not a Mach-O file
+       if _, err := OpenFat(filename); err == nil {
+               t.Errorf("OpenFat %s: succeeded unexpectedly", filename)
+       }
+
+       filename = "testdata/gcc-386-darwin-exec" // not a fat Mach-O
+       ff, err := OpenFat(filename)
+       if err != ErrNotFat {
+               t.Errorf("OpenFat %s: got %v, want ErrNotFat", err)
+       }
+       if ff != nil {
+               t.Errorf("OpenFat %s: got %v, want nil", ff)
+       }
+}
index bc14226c565a0c8045d8dcd68f95a4f5dd21b55a..09f4d0ec91c63b7fcf2c8931929aa8aa75847aff 100644 (file)
@@ -26,16 +26,19 @@ const (
 )
 
 const (
-       Magic32 uint32 = 0xfeedface
-       Magic64 uint32 = 0xfeedfacf
+       Magic32  uint32 = 0xfeedface
+       Magic64  uint32 = 0xfeedfacf
+       MagicFat uint32 = 0xcafebabe
 )
 
-// A Type is a Mach-O file type, either an object or an executable.
+// A Type is the Mach-O file type, e.g. an object file, executable, or dynamic library.
 type Type uint32
 
 const (
-       TypeObj  Type = 1
-       TypeExec Type = 2
+       TypeObj    Type = 1
+       TypeExec   Type = 2
+       TypeDylib  Type = 6
+       TypeBundle Type = 8
 )
 
 // A Cpu is a Mach-O cpu type.