]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link/internal/ld: extract Mach-O load command parsing
authorElias Naur <mail@eliasnaur.com>
Wed, 20 Mar 2019 19:10:38 +0000 (20:10 +0100)
committerElias Naur <mail@eliasnaur.com>
Thu, 21 Mar 2019 01:35:21 +0000 (01:35 +0000)
We're going to need the ability to extract the LC_VERSION_MIN_* and
LC_BUILD_VERSION load commands. This CL adds peekMachoPlatform to do
that and in the process simplifies machoCombineDwarf.

While here, disable DWARF combining for Apple platforms other than
macOS (watchOS, tvOS, bridgeOS), not just iOS.

Updates #22395

Change-Id: I4862b0f15ccc87b7be1a6532b4d37b47c8f7f243
Reviewed-on: https://go-review.googlesource.com/c/go/+/168459
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/macho.go
src/cmd/link/internal/ld/macho_combine_dwarf.go

index 076d2a71b947646cc1724b0c3d3802e05c48a088..2900268a57cdfc3ba648b96fbd20d6b3c6ab6357 100644 (file)
@@ -44,6 +44,7 @@ import (
        "cmd/link/internal/sym"
        "crypto/sha1"
        "debug/elf"
+       "debug/macho"
        "encoding/base64"
        "encoding/binary"
        "encoding/hex"
@@ -1449,11 +1450,24 @@ func (ctxt *Link) hostlink() {
                }
                // For os.Rename to work reliably, must be in same directory as outfile.
                combinedOutput := *flagOutfile + "~"
-               isIOS, err := machoCombineDwarf(ctxt, *flagOutfile, dsym, combinedOutput)
+               exef, err := os.Open(*flagOutfile)
                if err != nil {
                        Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
                }
-               if !isIOS {
+               defer exef.Close()
+               exem, err := macho.NewFile(exef)
+               if err != nil {
+                       Exitf("%s: parsing Mach-O header failed: %v", os.Args[0], err)
+               }
+               load, err := peekMachoPlatform(exem)
+               if err != nil {
+                       Exitf("%s: failed to parse Mach-O load commands: %v", os.Args[0], err)
+               }
+               // Only macOS supports unmapped segments such as our __DWARF segment.
+               if load == nil || load.platform == PLATFORM_MACOS {
+                       if err := machoCombineDwarf(ctxt, exef, exem, dsym, combinedOutput); err != nil {
+                               Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
+                       }
                        os.Remove(*flagOutfile)
                        if err := os.Rename(combinedOutput, *flagOutfile); err != nil {
                                Exitf("%s: %v", os.Args[0], err)
index f2756678d6756b5f2df6080da1fe0c51f72b73a7..32b20130599324e9ea8a266a25ca41acc76b39e1 100644 (file)
@@ -5,9 +5,12 @@
 package ld
 
 import (
+       "bytes"
        "cmd/internal/objabi"
        "cmd/internal/sys"
        "cmd/link/internal/sym"
+       "debug/macho"
+       "encoding/binary"
        "sort"
        "strings"
 )
@@ -45,11 +48,20 @@ type MachoSeg struct {
        flag       uint32
 }
 
+// MachoPlatformLoad represents a LC_VERSION_MIN_* or
+// LC_BUILD_VERSION load command.
+type MachoPlatformLoad struct {
+       platform MachoPlatform // One of PLATFORM_* constants.
+       cmd      MachoLoad
+}
+
 type MachoLoad struct {
        type_ uint32
        data  []uint32
 }
 
+type MachoPlatform int
+
 /*
  * Total amount of space to reserve at the start of the file
  * for Header, PHeaders, and SHeaders.
@@ -167,6 +179,14 @@ const (
        S_ATTR_SOME_INSTRUCTIONS   = 0x00000400
 )
 
+const (
+       PLATFORM_MACOS    MachoPlatform = 1
+       PLATFORM_IOS      MachoPlatform = 2
+       PLATFORM_TVOS     MachoPlatform = 3
+       PLATFORM_WATCHOS  MachoPlatform = 4
+       PLATFORM_BRIDGEOS MachoPlatform = 5
+)
+
 // Mach-O file writing
 // https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
 
@@ -996,3 +1016,41 @@ func Machoemitreloc(ctxt *Link) {
                machorelocsect(ctxt, sect, dwarfp)
        }
 }
+
+// peekMachoPlatform returns the first LC_VERSION_MIN_* or LC_BUILD_VERSION
+// load command found in the Mach-O file, if any.
+func peekMachoPlatform(m *macho.File) (*MachoPlatformLoad, error) {
+       for _, cmd := range m.Loads {
+               raw := cmd.Raw()
+               ml := MachoLoad{
+                       type_: m.ByteOrder.Uint32(raw),
+               }
+               // Skip the type and command length.
+               data := raw[8:]
+               var p MachoPlatform
+               switch ml.type_ {
+               case LC_VERSION_MIN_IPHONEOS:
+                       p = PLATFORM_IOS
+               case LC_VERSION_MIN_MACOSX:
+                       p = PLATFORM_MACOS
+               case LC_VERSION_MIN_WATCHOS:
+                       p = PLATFORM_WATCHOS
+               case LC_VERSION_MIN_TVOS:
+                       p = PLATFORM_TVOS
+               case LC_BUILD_VERSION:
+                       p = MachoPlatform(m.ByteOrder.Uint32(data))
+               default:
+                       continue
+               }
+               ml.data = make([]uint32, len(data)/4)
+               r := bytes.NewReader(data)
+               if err := binary.Read(r, m.ByteOrder, &ml.data); err != nil {
+                       return nil, err
+               }
+               return &MachoPlatformLoad{
+                       platform: p,
+                       cmd:      ml,
+               }, nil
+       }
+       return nil, nil
+}
index 95bd4c7c36a0ef784781bba28429e9aaca5b8ab7..1e8ee48b042e06c0f0b26039161e9ce0abe47dfe 100644 (file)
@@ -86,55 +86,28 @@ func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
 }
 
 // machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
-// machoCombineDwarf returns true and skips merging if the input executable is for iOS.
 //
 // With internal linking, DWARF is embedded into the executable, this lets us do the
 // same for external linking.
-// inexe is the path to the executable with no DWARF. It must have enough room in the macho
+// exef is the file of the executable with no DWARF. It must have enough room in the macho
 // header to add the DWARF sections. (Use ld's -headerpad option)
+// exem is the macho representation of exef.
 // dsym is the path to the macho file containing DWARF from dsymutil.
 // outexe is the path where the combined executable should be saved.
-func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
-       exef, err := os.Open(inexe)
-       if err != nil {
-               return false, err
-       }
-       exem, err := macho.NewFile(exef)
-       if err != nil {
-               return false, err
-       }
-       cmdOffset := unsafe.Sizeof(exem.FileHeader)
-       is64bit := exem.Magic == macho.Magic64
-       if is64bit {
-               // mach_header_64 has one extra uint32.
-               cmdOffset += unsafe.Sizeof(exem.Magic)
-       }
-       // Check for LC_VERSION_MIN_IPHONEOS.
-       reader := loadCmdReader{next: int64(cmdOffset), f: exef, order: exem.ByteOrder}
-       for i := uint32(0); i < exem.Ncmd; i++ {
-               cmd, err := reader.Next()
-               if err != nil {
-                       return false, err
-               }
-               if cmd.Cmd == LC_VERSION_MIN_IPHONEOS {
-                       // The executable is for iOS, which doesn't support unmapped
-                       // segments such as our __DWARF segment. Skip combining.
-                       return true, nil
-               }
-       }
+func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe string) error {
        dwarff, err := os.Open(dsym)
        if err != nil {
-               return false, err
+               return err
        }
        outf, err := os.Create(outexe)
        if err != nil {
-               return false, err
+               return err
        }
        outf.Chmod(0755)
 
        dwarfm, err := macho.NewFile(dwarff)
        if err != nil {
-               return false, err
+               return err
        }
 
        // The string table needs to be the last thing in the file
@@ -142,26 +115,26 @@ func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
        // linkedit section, but all the others can be copied directly.
        linkseg = exem.Segment("__LINKEDIT")
        if linkseg == nil {
-               return false, fmt.Errorf("missing __LINKEDIT segment")
+               return fmt.Errorf("missing __LINKEDIT segment")
        }
 
        if _, err = exef.Seek(0, 0); err != nil {
-               return false, err
+               return err
        }
        if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
-               return false, err
+               return err
        }
 
        realdwarf = dwarfm.Segment("__DWARF")
        if realdwarf == nil {
-               return false, fmt.Errorf("missing __DWARF segment")
+               return fmt.Errorf("missing __DWARF segment")
        }
 
        // Try to compress the DWARF sections. This includes some Apple
        // proprietary sections like __apple_types.
        compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
        if err != nil {
-               return false, err
+               return err
        }
 
        // Now copy the dwarf data into the output.
@@ -169,12 +142,12 @@ func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
        // even though we mark this one as being 0 bytes of virtual address space.
        dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
        if _, err = outf.Seek(dwarfstart, 0); err != nil {
-               return false, err
+               return err
        }
        dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
 
        if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
-               return false, err
+               return err
        }
 
        // Write out the compressed sections, or the originals if we gave up
@@ -183,62 +156,68 @@ func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
        if compressedBytes != nil {
                dwarfsize = uint64(len(compressedBytes))
                if _, err := outf.Write(compressedBytes); err != nil {
-                       return false, err
+                       return err
                }
        } else {
                if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
-                       return false, err
+                       return err
                }
                dwarfsize = realdwarf.Filesz
        }
 
        // And finally the linkedit section.
        if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
-               return false, err
+               return err
        }
        linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign)
        linkoffset = uint32(linkstart - int64(linkseg.Offset))
        if _, err = outf.Seek(linkstart, 0); err != nil {
-               return false, err
+               return err
        }
        if _, err := io.Copy(outf, exef); err != nil {
-               return false, err
+               return err
        }
 
        // Now we need to update the headers.
        textsect := exem.Section("__text")
        if linkseg == nil {
-               return false, fmt.Errorf("missing __text section")
+               return fmt.Errorf("missing __text section")
        }
 
+       cmdOffset := unsafe.Sizeof(exem.FileHeader)
+       is64bit := exem.Magic == macho.Magic64
+       if is64bit {
+               // mach_header_64 has one extra uint32.
+               cmdOffset += unsafe.Sizeof(exem.Magic)
+       }
        dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
        availablePadding := int64(textsect.Offset) - dwarfCmdOffset
        if availablePadding < int64(realdwarf.Len) {
-               return false, fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
+               return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
        }
        // First, copy the dwarf load command into the header. It will be
        // updated later with new offsets and lengths as necessary.
        if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
-               return false, err
+               return err
        }
        if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
-               return false, err
+               return err
        }
        if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
-               return false, err
+               return err
        }
        if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
-               return false, err
+               return err
        }
        if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
-               return false, err
+               return err
        }
 
-       reader = loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
+       reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
        for i := uint32(0); i < exem.Ncmd; i++ {
                cmd, err := reader.Next()
                if err != nil {
-                       return false, err
+                       return err
                }
                switch cmd.Cmd {
                case macho.LoadCmdSegment64:
@@ -261,11 +240,11 @@ func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
                        err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
                }
                if err != nil {
-                       return false, err
+                       return err
                }
        }
        // Do the final update of the DWARF segment's load command.
-       return false, machoUpdateDwarfHeader(&reader, ctxt.BuildMode, compressedSects)
+       return machoUpdateDwarfHeader(&reader, ctxt.BuildMode, compressedSects)
 }
 
 // machoCompressSections tries to compress the DWARF segments in dwarfm,