Skip to content

Commit

Permalink
cmd/link/internal/ld: extract Mach-O load command parsing
Browse files Browse the repository at this point in the history
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 golang#22395

Change-Id: I4862b0f15ccc87b7be1a6532b4d37b47c8f7f243
Reviewed-on: https://go-review.googlesource.com/c/go/+/168459
Reviewed-by: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
eliasnaur committed Mar 21, 2019
1 parent ba96564 commit 0fe1986
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 58 deletions.
18 changes: 16 additions & 2 deletions src/cmd/link/internal/ld/lib.go
Expand Up @@ -44,6 +44,7 @@ import (
"cmd/link/internal/sym"
"crypto/sha1"
"debug/elf"
"debug/macho"
"encoding/base64"
"encoding/binary"
"encoding/hex"
Expand Down Expand Up @@ -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)
Expand Down
58 changes: 58 additions & 0 deletions src/cmd/link/internal/ld/macho.go
Expand Up @@ -5,9 +5,12 @@
package ld

import (
"bytes"
"cmd/internal/objabi"
"cmd/internal/sys"
"cmd/link/internal/sym"
"debug/macho"
"encoding/binary"
"sort"
"strings"
)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
}
91 changes: 35 additions & 56 deletions src/cmd/link/internal/ld/macho_combine_dwarf.go
Expand Up @@ -86,95 +86,68 @@ 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
// for code signing to work. So we'll need to move the
// 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.
// Kernel requires all loaded segments to be page-aligned in the file,
// 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
Expand All @@ -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:
Expand All @@ -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,
Expand Down

0 comments on commit 0fe1986

Please sign in to comment.