...
Run Format

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

Documentation: cmd/link/internal/ld

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ld
     6  
     7  import (
     8  	"bytes"
     9  	"compress/zlib"
    10  	"debug/macho"
    11  	"encoding/binary"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"reflect"
    16  	"unsafe"
    17  )
    18  
    19  var realdwarf, linkseg *macho.Segment
    20  var dwarfstart, linkstart int64
    21  var dwarfaddr int64
    22  var linkoffset uint32
    23  
    24  const (
    25  	pageAlign = 12 // 4096 = 1 << 12
    26  )
    27  
    28  type loadCmd struct {
    29  	Cmd macho.LoadCmd
    30  	Len uint32
    31  }
    32  
    33  type dyldInfoCmd struct {
    34  	Cmd                      macho.LoadCmd
    35  	Len                      uint32
    36  	RebaseOff, RebaseLen     uint32
    37  	BindOff, BindLen         uint32
    38  	WeakBindOff, WeakBindLen uint32
    39  	LazyBindOff, LazyBindLen uint32
    40  	ExportOff, ExportLen     uint32
    41  }
    42  
    43  type linkEditDataCmd struct {
    44  	Cmd              macho.LoadCmd
    45  	Len              uint32
    46  	DataOff, DataLen uint32
    47  }
    48  
    49  type encryptionInfoCmd struct {
    50  	Cmd                macho.LoadCmd
    51  	Len                uint32
    52  	CryptOff, CryptLen uint32
    53  	CryptId            uint32
    54  }
    55  
    56  type loadCmdReader struct {
    57  	offset, next int64
    58  	f            *os.File
    59  	order        binary.ByteOrder
    60  }
    61  
    62  func (r *loadCmdReader) Next() (cmd loadCmd, err error) {
    63  	r.offset = r.next
    64  	if _, err = r.f.Seek(r.offset, 0); err != nil {
    65  		return
    66  	}
    67  	if err = binary.Read(r.f, r.order, &cmd); err != nil {
    68  		return
    69  	}
    70  	r.next = r.offset + int64(cmd.Len)
    71  	return
    72  }
    73  
    74  func (r loadCmdReader) ReadAt(offset int64, data interface{}) error {
    75  	if _, err := r.f.Seek(r.offset+offset, 0); err != nil {
    76  		return err
    77  	}
    78  	return binary.Read(r.f, r.order, data)
    79  }
    80  
    81  func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
    82  	if _, err := r.f.Seek(r.offset+offset, 0); err != nil {
    83  		return err
    84  	}
    85  	return binary.Write(r.f, r.order, data)
    86  }
    87  
    88  // machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
    89  // machoCombineDwarf returns true and skips merging if the input executable is for iOS.
    90  //
    91  // With internal linking, DWARF is embedded into the executable, this lets us do the
    92  // same for external linking.
    93  // inexe is the path to the executable with no DWARF. It must have enough room in the macho
    94  // header to add the DWARF sections. (Use ld's -headerpad option)
    95  // dsym is the path to the macho file containing DWARF from dsymutil.
    96  // outexe is the path where the combined executable should be saved.
    97  func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
    98  	exef, err := os.Open(inexe)
    99  	if err != nil {
   100  		return false, err
   101  	}
   102  	exem, err := macho.NewFile(exef)
   103  	if err != nil {
   104  		return false, err
   105  	}
   106  	cmdOffset := unsafe.Sizeof(exem.FileHeader)
   107  	is64bit := exem.Magic == macho.Magic64
   108  	if is64bit {
   109  		// mach_header_64 has one extra uint32.
   110  		cmdOffset += unsafe.Sizeof(exem.Magic)
   111  	}
   112  	// Check for LC_VERSION_MIN_IPHONEOS.
   113  	reader := loadCmdReader{next: int64(cmdOffset), f: exef, order: exem.ByteOrder}
   114  	for i := uint32(0); i < exem.Ncmd; i++ {
   115  		cmd, err := reader.Next()
   116  		if err != nil {
   117  			return false, err
   118  		}
   119  		if cmd.Cmd == LC_VERSION_MIN_IPHONEOS {
   120  			// The executable is for iOS, which doesn't support unmapped
   121  			// segments such as our __DWARF segment. Skip combining.
   122  			return true, nil
   123  		}
   124  	}
   125  	dwarff, err := os.Open(dsym)
   126  	if err != nil {
   127  		return false, err
   128  	}
   129  	outf, err := os.Create(outexe)
   130  	if err != nil {
   131  		return false, err
   132  	}
   133  	outf.Chmod(0755)
   134  
   135  	dwarfm, err := macho.NewFile(dwarff)
   136  	if err != nil {
   137  		return false, err
   138  	}
   139  
   140  	// The string table needs to be the last thing in the file
   141  	// for code signing to work. So we'll need to move the
   142  	// linkedit section, but all the others can be copied directly.
   143  	linkseg = exem.Segment("__LINKEDIT")
   144  	if linkseg == nil {
   145  		return false, fmt.Errorf("missing __LINKEDIT segment")
   146  	}
   147  
   148  	if _, err = exef.Seek(0, 0); err != nil {
   149  		return false, err
   150  	}
   151  	if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
   152  		return false, err
   153  	}
   154  
   155  	realdwarf = dwarfm.Segment("__DWARF")
   156  	if realdwarf == nil {
   157  		return false, fmt.Errorf("missing __DWARF segment")
   158  	}
   159  
   160  	// Try to compress the DWARF sections. This includes some Apple
   161  	// proprietary sections like __apple_types.
   162  	compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
   163  	if err != nil {
   164  		return false, err
   165  	}
   166  
   167  	// Now copy the dwarf data into the output.
   168  	// Kernel requires all loaded segments to be page-aligned in the file,
   169  	// even though we mark this one as being 0 bytes of virtual address space.
   170  	dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
   171  	if _, err = outf.Seek(dwarfstart, 0); err != nil {
   172  		return false, err
   173  	}
   174  	dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
   175  
   176  	if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
   177  		return false, err
   178  	}
   179  
   180  	// Write out the compressed sections, or the originals if we gave up
   181  	// on compressing them.
   182  	var dwarfsize uint64
   183  	if compressedBytes != nil {
   184  		dwarfsize = uint64(len(compressedBytes))
   185  		if _, err := outf.Write(compressedBytes); err != nil {
   186  			return false, err
   187  		}
   188  	} else {
   189  		if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
   190  			return false, err
   191  		}
   192  		dwarfsize = realdwarf.Filesz
   193  	}
   194  
   195  	// And finally the linkedit section.
   196  	if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
   197  		return false, err
   198  	}
   199  	linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign)
   200  	linkoffset = uint32(linkstart - int64(linkseg.Offset))
   201  	if _, err = outf.Seek(linkstart, 0); err != nil {
   202  		return false, err
   203  	}
   204  	if _, err := io.Copy(outf, exef); err != nil {
   205  		return false, err
   206  	}
   207  
   208  	// Now we need to update the headers.
   209  	textsect := exem.Section("__text")
   210  	if linkseg == nil {
   211  		return false, fmt.Errorf("missing __text section")
   212  	}
   213  
   214  	dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
   215  	availablePadding := int64(textsect.Offset) - dwarfCmdOffset
   216  	if availablePadding < int64(realdwarf.Len) {
   217  		return false, fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
   218  	}
   219  	// First, copy the dwarf load command into the header. It will be
   220  	// updated later with new offsets and lengths as necessary.
   221  	if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
   222  		return false, err
   223  	}
   224  	if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
   225  		return false, err
   226  	}
   227  	if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
   228  		return false, err
   229  	}
   230  	if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
   231  		return false, err
   232  	}
   233  	if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
   234  		return false, err
   235  	}
   236  
   237  	reader = loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
   238  	for i := uint32(0); i < exem.Ncmd; i++ {
   239  		cmd, err := reader.Next()
   240  		if err != nil {
   241  			return false, err
   242  		}
   243  		switch cmd.Cmd {
   244  		case macho.LoadCmdSegment64:
   245  			err = machoUpdateSegment(reader, &macho.Segment64{}, &macho.Section64{})
   246  		case macho.LoadCmdSegment:
   247  			err = machoUpdateSegment(reader, &macho.Segment32{}, &macho.Section32{})
   248  		case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
   249  			err = machoUpdateLoadCommand(reader, &dyldInfoCmd{}, "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff")
   250  		case macho.LoadCmdSymtab:
   251  			err = machoUpdateLoadCommand(reader, &macho.SymtabCmd{}, "Symoff", "Stroff")
   252  		case macho.LoadCmdDysymtab:
   253  			err = machoUpdateLoadCommand(reader, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
   254  		case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
   255  			err = machoUpdateLoadCommand(reader, &linkEditDataCmd{}, "DataOff")
   256  		case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
   257  			err = machoUpdateLoadCommand(reader, &encryptionInfoCmd{}, "CryptOff")
   258  		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, LC_SYMSEG, LC_LOADFVMLIB, LC_IDFVMLIB, LC_IDENT, LC_FVMFILE, LC_PREPAGE, LC_ID_DYLINKER, LC_ROUTINES, LC_SUB_FRAMEWORK, LC_SUB_UMBRELLA, LC_SUB_CLIENT, LC_SUB_LIBRARY, LC_TWOLEVEL_HINTS, LC_PREBIND_CKSUM, LC_ROUTINES_64, LC_LAZY_LOAD_DYLIB, LC_LOAD_UPWARD_DYLIB, LC_DYLD_ENVIRONMENT, LC_LINKER_OPTION, LC_LINKER_OPTIMIZATION_HINT, LC_VERSION_MIN_TVOS, LC_VERSION_MIN_WATCHOS, LC_VERSION_NOTE, LC_BUILD_VERSION:
   259  			// Nothing to update
   260  		default:
   261  			err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
   262  		}
   263  		if err != nil {
   264  			return false, err
   265  		}
   266  	}
   267  	// Do the final update of the DWARF segment's load command.
   268  	return false, machoUpdateDwarfHeader(&reader, ctxt.BuildMode, compressedSects)
   269  }
   270  
   271  // machoCompressSections tries to compress the DWARF segments in dwarfm,
   272  // returning the updated sections and segment contents, nils if the sections
   273  // weren't compressed, or an error if there was a problem reading dwarfm.
   274  func machoCompressSections(ctxt *Link, dwarfm *macho.File) ([]*macho.Section, []byte, error) {
   275  	if !ctxt.compressDWARF {
   276  		return nil, nil, nil
   277  	}
   278  
   279  	dwarfseg := dwarfm.Segment("__DWARF")
   280  	var sects []*macho.Section
   281  	var bytes []byte
   282  
   283  	for _, sect := range dwarfm.Sections {
   284  		if sect.Seg != "__DWARF" {
   285  			continue
   286  		}
   287  
   288  		// As of writing, there are no relocations in dsymutil's output
   289  		// so there's no point in worrying about them. Bail out if that
   290  		// changes.
   291  		if sect.Nreloc != 0 {
   292  			return nil, nil, nil
   293  		}
   294  
   295  		data, err := sect.Data()
   296  		if err != nil {
   297  			return nil, nil, err
   298  		}
   299  
   300  		compressed, contents, err := machoCompressSection(data)
   301  		if err != nil {
   302  			return nil, nil, err
   303  		}
   304  
   305  		newSec := *sect
   306  		newSec.Offset = uint32(dwarfseg.Offset) + uint32(len(bytes))
   307  		newSec.Addr = dwarfseg.Addr + uint64(len(bytes))
   308  		if compressed {
   309  			newSec.Name = "__z" + sect.Name[2:]
   310  			newSec.Size = uint64(len(contents))
   311  		}
   312  		sects = append(sects, &newSec)
   313  		bytes = append(bytes, contents...)
   314  	}
   315  	return sects, bytes, nil
   316  }
   317  
   318  // machoCompressSection compresses secBytes if it results in less data.
   319  func machoCompressSection(sectBytes []byte) (compressed bool, contents []byte, err error) {
   320  	var buf bytes.Buffer
   321  	buf.Write([]byte("ZLIB"))
   322  	var sizeBytes [8]byte
   323  	binary.BigEndian.PutUint64(sizeBytes[:], uint64(len(sectBytes)))
   324  	buf.Write(sizeBytes[:])
   325  
   326  	z := zlib.NewWriter(&buf)
   327  	if _, err := z.Write(sectBytes); err != nil {
   328  		return false, nil, err
   329  	}
   330  	if err := z.Close(); err != nil {
   331  		return false, nil, err
   332  	}
   333  	if len(buf.Bytes()) >= len(sectBytes) {
   334  		return false, sectBytes, nil
   335  	}
   336  	return true, buf.Bytes(), nil
   337  }
   338  
   339  // machoUpdateSegment updates the load command for a moved segment.
   340  // Only the linkedit segment should move, and it should have 0 sections.
   341  // seg should be a macho.Segment32 or macho.Segment64 as appropriate.
   342  // sect should be a macho.Section32 or macho.Section64 as appropriate.
   343  func machoUpdateSegment(r loadCmdReader, seg, sect interface{}) error {
   344  	if err := r.ReadAt(0, seg); err != nil {
   345  		return err
   346  	}
   347  	segValue := reflect.ValueOf(seg)
   348  	offset := reflect.Indirect(segValue).FieldByName("Offset")
   349  
   350  	// Only the linkedit segment moved, any thing before that is fine.
   351  	if offset.Uint() < linkseg.Offset {
   352  		return nil
   353  	}
   354  	offset.SetUint(offset.Uint() + uint64(linkoffset))
   355  	if err := r.WriteAt(0, seg); err != nil {
   356  		return err
   357  	}
   358  	// There shouldn't be any sections, but just to make sure...
   359  	return machoUpdateSections(r, segValue, reflect.ValueOf(sect), uint64(linkoffset), 0, nil)
   360  }
   361  
   362  func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset, deltaAddr uint64, compressedSects []*macho.Section) error {
   363  	iseg := reflect.Indirect(seg)
   364  	nsect := iseg.FieldByName("Nsect").Uint()
   365  	if nsect == 0 {
   366  		return nil
   367  	}
   368  	sectOffset := int64(iseg.Type().Size())
   369  
   370  	isect := reflect.Indirect(sect)
   371  	offsetField := isect.FieldByName("Offset")
   372  	reloffField := isect.FieldByName("Reloff")
   373  	addrField := isect.FieldByName("Addr")
   374  	nameField := isect.FieldByName("Name")
   375  	sizeField := isect.FieldByName("Size")
   376  	sectSize := int64(isect.Type().Size())
   377  	for i := uint64(0); i < nsect; i++ {
   378  		if err := r.ReadAt(sectOffset, sect.Interface()); err != nil {
   379  			return err
   380  		}
   381  		if compressedSects != nil {
   382  			cSect := compressedSects[i]
   383  			var name [16]byte
   384  			copy(name[:], []byte(cSect.Name))
   385  			nameField.Set(reflect.ValueOf(name))
   386  			sizeField.SetUint(cSect.Size)
   387  			if cSect.Offset != 0 {
   388  				offsetField.SetUint(uint64(cSect.Offset) + deltaOffset)
   389  			}
   390  			if cSect.Addr != 0 {
   391  				addrField.SetUint(cSect.Addr + deltaAddr)
   392  			}
   393  		} else {
   394  			if offsetField.Uint() != 0 {
   395  				offsetField.SetUint(offsetField.Uint() + deltaOffset)
   396  			}
   397  			if reloffField.Uint() != 0 {
   398  				reloffField.SetUint(reloffField.Uint() + deltaOffset)
   399  			}
   400  			if addrField.Uint() != 0 {
   401  				addrField.SetUint(addrField.Uint() + deltaAddr)
   402  			}
   403  		}
   404  		if err := r.WriteAt(sectOffset, sect.Interface()); err != nil {
   405  			return err
   406  		}
   407  		sectOffset += sectSize
   408  	}
   409  	return nil
   410  }
   411  
   412  // machoUpdateDwarfHeader updates the DWARF segment load command.
   413  func machoUpdateDwarfHeader(r *loadCmdReader, buildmode BuildMode, compressedSects []*macho.Section) error {
   414  	var seg, sect interface{}
   415  	cmd, err := r.Next()
   416  	if err != nil {
   417  		return err
   418  	}
   419  	if cmd.Cmd == macho.LoadCmdSegment64 {
   420  		seg = new(macho.Segment64)
   421  		sect = new(macho.Section64)
   422  	} else {
   423  		seg = new(macho.Segment32)
   424  		sect = new(macho.Section32)
   425  	}
   426  	if err := r.ReadAt(0, seg); err != nil {
   427  		return err
   428  	}
   429  	segv := reflect.ValueOf(seg).Elem()
   430  	segv.FieldByName("Offset").SetUint(uint64(dwarfstart))
   431  	segv.FieldByName("Addr").SetUint(uint64(dwarfaddr))
   432  
   433  	if compressedSects != nil {
   434  		var segSize uint64
   435  		for _, newSect := range compressedSects {
   436  			segSize += newSect.Size
   437  		}
   438  		segv.FieldByName("Filesz").SetUint(segSize)
   439  		segv.FieldByName("Memsz").SetUint(uint64(Rnd(int64(segSize), 1<<pageAlign)))
   440  	}
   441  
   442  	deltaOffset := uint64(dwarfstart) - realdwarf.Offset
   443  	deltaAddr := uint64(dwarfaddr) - realdwarf.Addr
   444  
   445  	// If we set Memsz to 0 (and might as well set Addr too),
   446  	// then the xnu kernel will bail out halfway through load_segment
   447  	// and not apply further sanity checks that we might fail in the future.
   448  	// We don't need the DWARF information actually available in memory.
   449  	// But if we do this for buildmode=c-shared then the user-space
   450  	// dynamic loader complains about memsz < filesz. Sigh.
   451  	if buildmode != BuildModeCShared {
   452  		segv.FieldByName("Addr").SetUint(0)
   453  		segv.FieldByName("Memsz").SetUint(0)
   454  		deltaAddr = 0
   455  	}
   456  
   457  	if err := r.WriteAt(0, seg); err != nil {
   458  		return err
   459  	}
   460  	return machoUpdateSections(*r, segv, reflect.ValueOf(sect), deltaOffset, deltaAddr, compressedSects)
   461  }
   462  
   463  func machoUpdateLoadCommand(r loadCmdReader, cmd interface{}, fields ...string) error {
   464  	if err := r.ReadAt(0, cmd); err != nil {
   465  		return err
   466  	}
   467  	value := reflect.Indirect(reflect.ValueOf(cmd))
   468  
   469  	for _, name := range fields {
   470  		field := value.FieldByName(name)
   471  		fieldval := field.Uint()
   472  		if fieldval >= linkseg.Offset {
   473  			field.SetUint(fieldval + uint64(linkoffset))
   474  		}
   475  	}
   476  	if err := r.WriteAt(0, cmd); err != nil {
   477  		return err
   478  	}
   479  	return nil
   480  }
   481  
   482  func machoCalcStart(origAddr, newAddr uint64, alignExp uint32) int64 {
   483  	align := uint64(1 << alignExp)
   484  	if (origAddr % align) == (newAddr % align) {
   485  		return int64(newAddr)
   486  	}
   487  	padding := (align - (newAddr % align))
   488  	padding += origAddr % align
   489  	return int64(padding + newAddr)
   490  }
   491  

View as plain text