...
Run Format

Source file src/cmd/link/internal/ld/macho_combine_dwarf.go

Documentation: cmd/link/internal/ld

  // 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)
  }
  

View as plain text