"cmd/link/internal/sym"
"crypto/sha1"
"debug/elf"
+ "debug/macho"
"encoding/base64"
"encoding/binary"
"encoding/hex"
}
// 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)
package ld
import (
+ "bytes"
"cmd/internal/objabi"
"cmd/internal/sys"
"cmd/link/internal/sym"
+ "debug/macho"
+ "encoding/binary"
"sort"
"strings"
)
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.
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
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
+}
}
// 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
// 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.
// 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
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:
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,