blob: 8c6c4a86acfecc55b6cc34c2ab649914ad9e0b58 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ld
import (
"bytes"
"debug/macho"
"encoding/binary"
"fmt"
"io"
"os"
"reflect"
"unsafe"
)
var realdwarf, linkseg *macho.Segment
var dwarfstart, linkstart int64
var dwarfaddr, linkaddr int64
var linkoffset uint32
const (
LC_ID_DYLIB = 0xd
LC_LOAD_DYLINKER = 0xe
LC_PREBOUND_DYLIB = 0x10
LC_LOAD_WEAK_DYLIB = 0x18
LC_UUID = 0x1b
LC_RPATH = 0x8000001c
LC_CODE_SIGNATURE = 0x1d
LC_SEGMENT_SPLIT_INFO = 0x1e
LC_REEXPORT_DYLIB = 0x8000001f
LC_ENCRYPTION_INFO = 0x21
LC_DYLD_INFO = 0x22
LC_DYLD_INFO_ONLY = 0x80000022
LC_VERSION_MIN_MACOSX = 0x24
LC_VERSION_MIN_IPHONEOS = 0x25
LC_FUNCTION_STARTS = 0x26
LC_MAIN = 0x80000028
LC_DATA_IN_CODE = 0x29
LC_SOURCE_VERSION = 0x2A
LC_DYLIB_CODE_SIGN_DRS = 0x2B
LC_ENCRYPTION_INFO_64 = 0x2C
pageAlign = 12 // 4096 = 1 << 12
)
type loadCmd struct {
Cmd macho.LoadCmd
Len uint32
}
type dyldInfoCmd struct {
Cmd macho.LoadCmd
Len uint32
RebaseOff, RebaseLen uint32
BindOff, BindLen uint32
WeakBindOff, WeakBindLen uint32
LazyBindOff, LazyBindLen uint32
ExportOff, ExportLen uint32
}
type linkEditDataCmd struct {
Cmd macho.LoadCmd
Len uint32
DataOff, DataLen uint32
}
type encryptionInfoCmd struct {
Cmd macho.LoadCmd
Len uint32
CryptOff, CryptLen uint32
CryptId uint32
}
type loadCmdReader struct {
offset, next int64
f *os.File
order binary.ByteOrder
}
func (r *loadCmdReader) Next() (cmd loadCmd, err error) {
r.offset = r.next
if _, err = r.f.Seek(r.offset, 0); err != nil {
return
}
if err = binary.Read(r.f, r.order, &cmd); err != nil {
return
}
r.next = r.offset + int64(cmd.Len)
return
}
func (r loadCmdReader) ReadAt(offset int64, data interface{}) error {
if _, err := r.f.Seek(r.offset+offset, 0); err != nil {
return err
}
return binary.Read(r.f, r.order, data)
}
func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
if _, err := r.f.Seek(r.offset+offset, 0); err != nil {
return err
}
return binary.Write(r.f, r.order, data)
}
// machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
// 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
// header to add the DWARF sections. (Use ld's -headerpad option)
// 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(inexe, dsym, outexe string) error {
exef, err := os.Open(inexe)
if err != nil {
return err
}
dwarff, err := os.Open(dsym)
if err != nil {
return err
}
outf, err := os.Create(outexe)
if err != nil {
return err
}
outf.Chmod(0755)
exem, err := macho.NewFile(exef)
if err != nil {
return err
}
dwarfm, err := macho.NewFile(dwarff)
if err != nil {
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 fmt.Errorf("missing __LINKEDIT segment")
}
if _, err = exef.Seek(0, 0); err != nil {
return err
}
if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
return err
}
realdwarf = dwarfm.Segment("__DWARF")
if realdwarf == nil {
return fmt.Errorf("missing __DWARF segment")
}
// 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 err
}
dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
return err
}
if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
return err
}
// And finally the linkedit section.
if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
return err
}
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign)
linkoffset = uint32(linkstart - int64(linkseg.Offset))
if _, err = outf.Seek(linkstart, 0); err != nil {
return err
}
if _, err := io.Copy(outf, exef); err != nil {
return err
}
// Now we need to update the headers.
cmdOffset := unsafe.Sizeof(exem.FileHeader)
is64bit := exem.Magic == macho.Magic64
if is64bit {
// mach_header_64 has one extra uint32.
cmdOffset += unsafe.Sizeof(exem.Magic)
}
textsect := exem.Section("__text")
if linkseg == nil {
return fmt.Errorf("missing __text section")
}
dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
availablePadding := int64(textsect.Offset) - dwarfCmdOffset
if availablePadding < int64(realdwarf.Len) {
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
if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
return err
}
if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
return err
}
if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
return err
}
if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
return err
}
if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
return err
}
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 err
}
switch cmd.Cmd {
case macho.LoadCmdSegment64:
err = machoUpdateSegment(reader, &macho.Segment64{}, &macho.Section64{})
case macho.LoadCmdSegment:
err = machoUpdateSegment(reader, &macho.Segment32{}, &macho.Section32{})
case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
err = machoUpdateLoadCommand(reader, &dyldInfoCmd{}, "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff")
case macho.LoadCmdSymtab:
err = machoUpdateLoadCommand(reader, &macho.SymtabCmd{}, "Symoff", "Stroff")
case macho.LoadCmdDysymtab:
err = machoUpdateLoadCommand(reader, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
err = machoUpdateLoadCommand(reader, &linkEditDataCmd{}, "DataOff")
case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
err = machoUpdateLoadCommand(reader, &encryptionInfoCmd{}, "CryptOff")
case macho.LoadCmdDylib, macho.LoadCmdThread, macho.LoadCmdUnixThread, LC_PREBOUND_DYLIB, LC_UUID, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_SOURCE_VERSION, LC_MAIN, LC_LOAD_DYLINKER, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_RPATH, LC_ID_DYLIB:
// Nothing to update
default:
err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
}
if err != nil {
return err
}
}
return machoUpdateDwarfHeader(&reader)
}
// machoUpdateSegment updates the load command for a moved segment.
// Only the linkedit segment should move, and it should have 0 sections.
// seg should be a macho.Segment32 or macho.Segment64 as appropriate.
// sect should be a macho.Section32 or macho.Section64 as appropriate.
func machoUpdateSegment(r loadCmdReader, seg, sect interface{}) error {
if err := r.ReadAt(0, seg); err != nil {
return err
}
segValue := reflect.ValueOf(seg)
offset := reflect.Indirect(segValue).FieldByName("Offset")
// Only the linkedit segment moved, any thing before that is fine.
if offset.Uint() < linkseg.Offset {
return nil
}
offset.SetUint(offset.Uint() + uint64(linkoffset))
if err := r.WriteAt(0, seg); err != nil {
return err
}
// There shouldn't be any sections, but just to make sure...
return machoUpdateSections(r, segValue, reflect.ValueOf(sect), uint64(linkoffset), 0)
}
func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset, deltaAddr uint64) error {
iseg := reflect.Indirect(seg)
nsect := iseg.FieldByName("Nsect").Uint()
if nsect == 0 {
return nil
}
sectOffset := int64(iseg.Type().Size())
isect := reflect.Indirect(sect)
offsetField := isect.FieldByName("Offset")
reloffField := isect.FieldByName("Reloff")
addrField := isect.FieldByName("Addr")
sectSize := int64(isect.Type().Size())
for i := uint64(0); i < nsect; i++ {
if err := r.ReadAt(sectOffset, sect.Interface()); err != nil {
return err
}
if offsetField.Uint() != 0 {
offsetField.SetUint(offsetField.Uint() + deltaOffset)
}
if reloffField.Uint() != 0 {
reloffField.SetUint(reloffField.Uint() + deltaOffset)
}
if addrField.Uint() != 0 {
addrField.SetUint(addrField.Uint() + deltaAddr)
}
if err := r.WriteAt(sectOffset, sect.Interface()); err != nil {
return err
}
sectOffset += sectSize
}
return nil
}
// machoUpdateDwarfHeader updates the DWARF segment load command.
func machoUpdateDwarfHeader(r *loadCmdReader) error {
var seg, sect interface{}
cmd, err := r.Next()
if err != nil {
return err
}
if cmd.Cmd == macho.LoadCmdSegment64 {
seg = new(macho.Segment64)
sect = new(macho.Section64)
} else {
seg = new(macho.Segment32)
sect = new(macho.Section32)
}
if err := r.ReadAt(0, seg); err != nil {
return err
}
segv := reflect.ValueOf(seg).Elem()
segv.FieldByName("Offset").SetUint(uint64(dwarfstart))
segv.FieldByName("Addr").SetUint(uint64(dwarfaddr))
deltaOffset := uint64(dwarfstart) - realdwarf.Offset
deltaAddr := uint64(dwarfaddr) - realdwarf.Addr
// If we set Memsz to 0 (and might as well set Addr too),
// then the xnu kernel will bail out halfway through load_segment
// and not apply further sanity checks that we might fail in the future.
// We don't need the DWARF information actually available in memory.
// But if we do this for buildmode=c-shared then the user-space
// dynamic loader complains about memsz < filesz. Sigh.
if Buildmode != BuildmodeCShared {
segv.FieldByName("Addr").SetUint(0)
segv.FieldByName("Memsz").SetUint(0)
deltaAddr = 0
}
if err := r.WriteAt(0, seg); err != nil {
return err
}
return machoUpdateSections(*r, segv, reflect.ValueOf(sect), deltaOffset, deltaAddr)
}
func machoUpdateLoadCommand(r loadCmdReader, cmd interface{}, fields ...string) error {
if err := r.ReadAt(0, cmd); err != nil {
return err
}
value := reflect.Indirect(reflect.ValueOf(cmd))
for _, name := range fields {
field := value.FieldByName(name)
fieldval := field.Uint()
if fieldval >= linkseg.Offset {
field.SetUint(fieldval + uint64(linkoffset))
}
}
if err := r.WriteAt(0, cmd); err != nil {
return err
}
return nil
}
func machoCalcStart(origAddr, newAddr uint64, alignExp uint32) int64 {
align := uint64(1 << alignExp)
if (origAddr % align) == (newAddr % align) {
return int64(newAddr)
}
padding := (align - (newAddr % align))
padding += origAddr % align
return int64(padding + newAddr)
}