1
0
mirror of https://github.com/golang/go synced 2024-09-25 09:10:14 -06:00

cmd/link/internal/ld: extract Mach-O load command parsing

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>
This commit is contained in:
Elias Naur 2019-03-20 20:10:38 +01:00
parent ba965640a4
commit 0fe1986a72
3 changed files with 109 additions and 58 deletions

View File

@ -44,6 +44,7 @@ import (
"cmd/link/internal/sym" "cmd/link/internal/sym"
"crypto/sha1" "crypto/sha1"
"debug/elf" "debug/elf"
"debug/macho"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
@ -1449,11 +1450,24 @@ func (ctxt *Link) hostlink() {
} }
// For os.Rename to work reliably, must be in same directory as outfile. // For os.Rename to work reliably, must be in same directory as outfile.
combinedOutput := *flagOutfile + "~" combinedOutput := *flagOutfile + "~"
isIOS, err := machoCombineDwarf(ctxt, *flagOutfile, dsym, combinedOutput) exef, err := os.Open(*flagOutfile)
if err != nil { if err != nil {
Exitf("%s: combining dwarf failed: %v", os.Args[0], err) 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) os.Remove(*flagOutfile)
if err := os.Rename(combinedOutput, *flagOutfile); err != nil { if err := os.Rename(combinedOutput, *flagOutfile); err != nil {
Exitf("%s: %v", os.Args[0], err) Exitf("%s: %v", os.Args[0], err)

View File

@ -5,9 +5,12 @@
package ld package ld
import ( import (
"bytes"
"cmd/internal/objabi" "cmd/internal/objabi"
"cmd/internal/sys" "cmd/internal/sys"
"cmd/link/internal/sym" "cmd/link/internal/sym"
"debug/macho"
"encoding/binary"
"sort" "sort"
"strings" "strings"
) )
@ -45,11 +48,20 @@ type MachoSeg struct {
flag uint32 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 MachoLoad struct {
type_ uint32 type_ uint32
data []uint32 data []uint32
} }
type MachoPlatform int
/* /*
* Total amount of space to reserve at the start of the file * Total amount of space to reserve at the start of the file
* for Header, PHeaders, and SHeaders. * for Header, PHeaders, and SHeaders.
@ -167,6 +179,14 @@ const (
S_ATTR_SOME_INSTRUCTIONS = 0x00000400 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 // Mach-O file writing
// https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html // 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) 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
}

View 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 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 // With internal linking, DWARF is embedded into the executable, this lets us do the
// same for external linking. // 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) // 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. // dsym is the path to the macho file containing DWARF from dsymutil.
// outexe is the path where the combined executable should be saved. // outexe is the path where the combined executable should be saved.
func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) { func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe string) 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
}
}
dwarff, err := os.Open(dsym) dwarff, err := os.Open(dsym)
if err != nil { if err != nil {
return false, err return err
} }
outf, err := os.Create(outexe) outf, err := os.Create(outexe)
if err != nil { if err != nil {
return false, err return err
} }
outf.Chmod(0755) outf.Chmod(0755)
dwarfm, err := macho.NewFile(dwarff) dwarfm, err := macho.NewFile(dwarff)
if err != nil { if err != nil {
return false, err return err
} }
// The string table needs to be the last thing in the file // 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. // linkedit section, but all the others can be copied directly.
linkseg = exem.Segment("__LINKEDIT") linkseg = exem.Segment("__LINKEDIT")
if linkseg == nil { if linkseg == nil {
return false, fmt.Errorf("missing __LINKEDIT segment") return fmt.Errorf("missing __LINKEDIT segment")
} }
if _, err = exef.Seek(0, 0); err != nil { if _, err = exef.Seek(0, 0); err != nil {
return false, err return err
} }
if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil { if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
return false, err return err
} }
realdwarf = dwarfm.Segment("__DWARF") realdwarf = dwarfm.Segment("__DWARF")
if realdwarf == nil { 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 // Try to compress the DWARF sections. This includes some Apple
// proprietary sections like __apple_types. // proprietary sections like __apple_types.
compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm) compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
if err != nil { if err != nil {
return false, err return err
} }
// Now copy the dwarf data into the output. // 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. // even though we mark this one as being 0 bytes of virtual address space.
dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign) dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
if _, err = outf.Seek(dwarfstart, 0); err != nil { 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)) dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil { 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 // 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 { if compressedBytes != nil {
dwarfsize = uint64(len(compressedBytes)) dwarfsize = uint64(len(compressedBytes))
if _, err := outf.Write(compressedBytes); err != nil { if _, err := outf.Write(compressedBytes); err != nil {
return false, err return err
} }
} else { } else {
if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil { if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
return false, err return err
} }
dwarfsize = realdwarf.Filesz dwarfsize = realdwarf.Filesz
} }
// And finally the linkedit section. // And finally the linkedit section.
if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil { if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
return false, err return err
} }
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign) linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign)
linkoffset = uint32(linkstart - int64(linkseg.Offset)) linkoffset = uint32(linkstart - int64(linkseg.Offset))
if _, err = outf.Seek(linkstart, 0); err != nil { if _, err = outf.Seek(linkstart, 0); err != nil {
return false, err return err
} }
if _, err := io.Copy(outf, exef); err != nil { if _, err := io.Copy(outf, exef); err != nil {
return false, err return err
} }
// Now we need to update the headers. // Now we need to update the headers.
textsect := exem.Section("__text") textsect := exem.Section("__text")
if linkseg == nil { 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) dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
availablePadding := int64(textsect.Offset) - dwarfCmdOffset availablePadding := int64(textsect.Offset) - dwarfCmdOffset
if availablePadding < int64(realdwarf.Len) { 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 // First, copy the dwarf load command into the header. It will be
// updated later with new offsets and lengths as necessary. // updated later with new offsets and lengths as necessary.
if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil { 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 { 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 { 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 { 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 { 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++ { for i := uint32(0); i < exem.Ncmd; i++ {
cmd, err := reader.Next() cmd, err := reader.Next()
if err != nil { if err != nil {
return false, err return err
} }
switch cmd.Cmd { switch cmd.Cmd {
case macho.LoadCmdSegment64: 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) err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
} }
if err != nil { if err != nil {
return false, err return err
} }
} }
// Do the final update of the DWARF segment's load command. // 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, // machoCompressSections tries to compress the DWARF segments in dwarfm,