Commit 0fe1986a authored by Elias Naur's avatar Elias Naur

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/+/168459Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent ba965640
......@@ -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)
......
......@@ -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
}
......@@ -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,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment