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

     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  	imacho "cmd/internal/macho"
     9  
    10  	"bytes"
    11  	"compress/zlib"
    12  	"debug/macho"
    13  	"encoding/binary"
    14  	"fmt"
    15  	"io"
    16  	"os"
    17  	"reflect"
    18  	"unsafe"
    19  )
    20  
    21  type dyldInfoCmd struct {
    22  	Cmd                      macho.LoadCmd
    23  	Len                      uint32
    24  	RebaseOff, RebaseLen     uint32
    25  	BindOff, BindLen         uint32
    26  	WeakBindOff, WeakBindLen uint32
    27  	LazyBindOff, LazyBindLen uint32
    28  	ExportOff, ExportLen     uint32
    29  }
    30  
    31  type linkEditDataCmd struct {
    32  	Cmd              macho.LoadCmd
    33  	Len              uint32
    34  	DataOff, DataLen uint32
    35  }
    36  
    37  type encryptionInfoCmd struct {
    38  	Cmd                macho.LoadCmd
    39  	Len                uint32
    40  	CryptOff, CryptLen uint32
    41  	CryptId            uint32
    42  }
    43  
    44  type uuidCmd struct {
    45  	Cmd  macho.LoadCmd
    46  	Len  uint32
    47  	Uuid [16]byte
    48  }
    49  
    50  // machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
    51  //
    52  // With internal linking, DWARF is embedded into the executable, this lets us do the
    53  // same for external linking.
    54  // exef is the file of the executable with no DWARF. It must have enough room in the macho
    55  // header to add the DWARF sections. (Use ld's -headerpad option)
    56  // exem is the macho representation of exef.
    57  // dsym is the path to the macho file containing DWARF from dsymutil.
    58  // outexe is the path where the combined executable should be saved.
    59  func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe string) error {
    60  	dwarff, err := os.Open(dsym)
    61  	if err != nil {
    62  		return err
    63  	}
    64  	defer dwarff.Close()
    65  	outf, err := os.OpenFile(outexe, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	defer outf.Close()
    70  	dwarfm, err := macho.NewFile(dwarff)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	defer dwarfm.Close()
    75  
    76  	// The string table needs to be the last thing in the file
    77  	// for code signing to work. So we'll need to move the
    78  	// linkedit section, but all the others can be copied directly.
    79  	linkseg := exem.Segment("__LINKEDIT")
    80  	if linkseg == nil {
    81  		return fmt.Errorf("missing __LINKEDIT segment")
    82  	}
    83  
    84  	if _, err := exef.Seek(0, 0); err != nil {
    85  		return err
    86  	}
    87  	if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
    88  		return err
    89  	}
    90  
    91  	realdwarf := dwarfm.Segment("__DWARF")
    92  	if realdwarf == nil {
    93  		return fmt.Errorf("missing __DWARF segment")
    94  	}
    95  
    96  	// Try to compress the DWARF sections. This includes some Apple
    97  	// proprietary sections like __apple_types.
    98  	compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Now copy the dwarf data into the output.
   104  	// Kernel requires all loaded segments to be page-aligned in the file,
   105  	// even though we mark this one as being 0 bytes of virtual address space.
   106  	dwarfstart := Rnd(int64(linkseg.Offset), *FlagRound)
   107  	if _, err := outf.Seek(dwarfstart, 0); err != nil {
   108  		return err
   109  	}
   110  
   111  	if _, err := dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
   112  		return err
   113  	}
   114  
   115  	// Write out the compressed sections, or the originals if we gave up
   116  	// on compressing them.
   117  	var dwarfsize uint64
   118  	if compressedBytes != nil {
   119  		dwarfsize = uint64(len(compressedBytes))
   120  		if _, err := outf.Write(compressedBytes); err != nil {
   121  			return err
   122  		}
   123  	} else {
   124  		if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
   125  			return err
   126  		}
   127  		dwarfsize = realdwarf.Filesz
   128  	}
   129  
   130  	// And finally the linkedit section.
   131  	if _, err := exef.Seek(int64(linkseg.Offset), 0); err != nil {
   132  		return err
   133  	}
   134  	linkstart := Rnd(dwarfstart+int64(dwarfsize), *FlagRound)
   135  	if _, err := outf.Seek(linkstart, 0); err != nil {
   136  		return err
   137  	}
   138  	if _, err := io.Copy(outf, exef); err != nil {
   139  		return err
   140  	}
   141  
   142  	// Now we need to update the headers.
   143  	textsect := exem.Section("__text")
   144  	if textsect == nil {
   145  		return fmt.Errorf("missing __text section")
   146  	}
   147  
   148  	cmdOffset := imacho.FileHeaderSize(exem)
   149  	dwarfCmdOffset := uint32(cmdOffset) + exem.FileHeader.Cmdsz
   150  	availablePadding := textsect.Offset - dwarfCmdOffset
   151  	if availablePadding < realdwarf.Len {
   152  		return fmt.Errorf("no room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
   153  	}
   154  	// First, copy the dwarf load command into the header. It will be
   155  	// updated later with new offsets and lengths as necessary.
   156  	if _, err := outf.Seek(int64(dwarfCmdOffset), 0); err != nil {
   157  		return err
   158  	}
   159  	if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
   160  		return err
   161  	}
   162  	if _, err := outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
   163  		return err
   164  	}
   165  	if err := binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
   166  		return err
   167  	}
   168  	if err := binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
   169  		return err
   170  	}
   171  
   172  	reader := imacho.NewLoadCmdUpdater(outf, exem.ByteOrder, cmdOffset)
   173  	for i := uint32(0); i < exem.Ncmd; i++ {
   174  		cmd, err := reader.Next()
   175  		if err != nil {
   176  			return err
   177  		}
   178  		linkoffset := uint64(linkstart) - linkseg.Offset
   179  		switch cmd.Cmd {
   180  		case macho.LoadCmdSegment64:
   181  			err = machoUpdateSegment(reader, linkseg, linkoffset)
   182  		case macho.LoadCmdSegment:
   183  			panic("unexpected 32-bit segment")
   184  		case imacho.LC_DYLD_INFO, imacho.LC_DYLD_INFO_ONLY:
   185  			err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &dyldInfoCmd{}, "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff")
   186  		case macho.LoadCmdSymtab:
   187  			err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.SymtabCmd{}, "Symoff", "Stroff")
   188  		case macho.LoadCmdDysymtab:
   189  			err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
   190  		case imacho.LC_CODE_SIGNATURE, imacho.LC_SEGMENT_SPLIT_INFO, imacho.LC_FUNCTION_STARTS, imacho.LC_DATA_IN_CODE, imacho.LC_DYLIB_CODE_SIGN_DRS,
   191  			imacho.LC_DYLD_EXPORTS_TRIE, imacho.LC_DYLD_CHAINED_FIXUPS:
   192  			err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &linkEditDataCmd{}, "DataOff")
   193  		case imacho.LC_ENCRYPTION_INFO, imacho.LC_ENCRYPTION_INFO_64:
   194  			err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &encryptionInfoCmd{}, "CryptOff")
   195  		case imacho.LC_UUID:
   196  			var u uuidCmd
   197  			err = reader.ReadAt(0, &u)
   198  			if err == nil && len(buildinfo) > 0 {
   199  				clear(u.Uuid[:])
   200  				copy(u.Uuid[:], buildinfo)
   201  				err = reader.WriteAt(0, &u)
   202  			}
   203  		case macho.LoadCmdDylib, macho.LoadCmdThread, macho.LoadCmdUnixThread,
   204  			imacho.LC_PREBOUND_DYLIB, imacho.LC_VERSION_MIN_MACOSX, imacho.LC_VERSION_MIN_IPHONEOS, imacho.LC_SOURCE_VERSION,
   205  			imacho.LC_MAIN, imacho.LC_LOAD_DYLINKER, imacho.LC_LOAD_WEAK_DYLIB, imacho.LC_REEXPORT_DYLIB, imacho.LC_RPATH, imacho.LC_ID_DYLIB,
   206  			imacho.LC_SYMSEG, imacho.LC_LOADFVMLIB, imacho.LC_IDFVMLIB, imacho.LC_IDENT, imacho.LC_FVMFILE, imacho.LC_PREPAGE, imacho.LC_ID_DYLINKER,
   207  			imacho.LC_ROUTINES, imacho.LC_SUB_FRAMEWORK, imacho.LC_SUB_UMBRELLA, imacho.LC_SUB_CLIENT, imacho.LC_SUB_LIBRARY, imacho.LC_TWOLEVEL_HINTS,
   208  			imacho.LC_PREBIND_CKSUM, imacho.LC_ROUTINES_64, imacho.LC_LAZY_LOAD_DYLIB, imacho.LC_LOAD_UPWARD_DYLIB, imacho.LC_DYLD_ENVIRONMENT,
   209  			imacho.LC_LINKER_OPTION, imacho.LC_LINKER_OPTIMIZATION_HINT, imacho.LC_VERSION_MIN_TVOS, imacho.LC_VERSION_MIN_WATCHOS,
   210  			imacho.LC_VERSION_NOTE, imacho.LC_BUILD_VERSION:
   211  			// Nothing to update
   212  		default:
   213  			err = fmt.Errorf("unknown load command 0x%x (%s)", int(cmd.Cmd), cmd.Cmd)
   214  		}
   215  		if err != nil {
   216  			return err
   217  		}
   218  	}
   219  	// Do the final update of the DWARF segment's load command.
   220  	return machoUpdateDwarfHeader(&reader, compressedSects, dwarfsize, dwarfstart, realdwarf)
   221  }
   222  
   223  // machoCompressSections tries to compress the DWARF segments in dwarfm,
   224  // returning the updated sections and segment contents, nils if the sections
   225  // weren't compressed, or an error if there was a problem reading dwarfm.
   226  func machoCompressSections(ctxt *Link, dwarfm *macho.File) ([]*macho.Section, []byte, error) {
   227  	if !ctxt.compressDWARF {
   228  		return nil, nil, nil
   229  	}
   230  
   231  	dwarfseg := dwarfm.Segment("__DWARF")
   232  	var sects []*macho.Section
   233  	var buf bytes.Buffer
   234  
   235  	for _, sect := range dwarfm.Sections {
   236  		if sect.Seg != "__DWARF" {
   237  			continue
   238  		}
   239  
   240  		// As of writing, there are no relocations in dsymutil's output
   241  		// so there's no point in worrying about them. Bail out if that
   242  		// changes.
   243  		if sect.Nreloc != 0 {
   244  			return nil, nil, nil
   245  		}
   246  
   247  		data, err := sect.Data()
   248  		if err != nil {
   249  			return nil, nil, err
   250  		}
   251  
   252  		compressed, contents, err := machoCompressSection(data)
   253  		if err != nil {
   254  			return nil, nil, err
   255  		}
   256  
   257  		newSec := *sect
   258  		newSec.Offset = uint32(dwarfseg.Offset) + uint32(buf.Len())
   259  		newSec.Addr = dwarfseg.Addr + uint64(buf.Len())
   260  		if compressed {
   261  			newSec.Name = "__z" + sect.Name[2:]
   262  			newSec.Size = uint64(len(contents))
   263  		}
   264  		sects = append(sects, &newSec)
   265  		buf.Write(contents)
   266  	}
   267  	return sects, buf.Bytes(), nil
   268  }
   269  
   270  // machoCompressSection compresses secBytes if it results in less data.
   271  func machoCompressSection(sectBytes []byte) (compressed bool, contents []byte, err error) {
   272  	var buf bytes.Buffer
   273  	buf.WriteString("ZLIB")
   274  	var sizeBytes [8]byte
   275  	binary.BigEndian.PutUint64(sizeBytes[:], uint64(len(sectBytes)))
   276  	buf.Write(sizeBytes[:])
   277  
   278  	z := zlib.NewWriter(&buf)
   279  	if _, err := z.Write(sectBytes); err != nil {
   280  		return false, nil, err
   281  	}
   282  	if err := z.Close(); err != nil {
   283  		return false, nil, err
   284  	}
   285  	if buf.Len() >= len(sectBytes) {
   286  		return false, sectBytes, nil
   287  	}
   288  	return true, buf.Bytes(), nil
   289  }
   290  
   291  // machoUpdateSegment updates the load command for a moved segment.
   292  // Only the linkedit segment should move, and it should have 0 sections.
   293  func machoUpdateSegment(r imacho.LoadCmdUpdater, linkseg *macho.Segment, linkoffset uint64) error {
   294  	var seg macho.Segment64
   295  	if err := r.ReadAt(0, &seg); err != nil {
   296  		return err
   297  	}
   298  
   299  	// Only the linkedit segment moved, anything before that is fine.
   300  	if seg.Offset < linkseg.Offset {
   301  		return nil
   302  	}
   303  	seg.Offset += linkoffset
   304  	if err := r.WriteAt(0, &seg); err != nil {
   305  		return err
   306  	}
   307  	// There shouldn't be any sections, but just to make sure...
   308  	return machoUpdateSections(r, &seg, linkoffset, nil)
   309  }
   310  
   311  func machoUpdateSections(r imacho.LoadCmdUpdater, seg *macho.Segment64, deltaOffset uint64, compressedSects []*macho.Section) error {
   312  	nsect := seg.Nsect
   313  	if nsect == 0 {
   314  		return nil
   315  	}
   316  	sectOffset := int64(unsafe.Sizeof(*seg))
   317  
   318  	var sect macho.Section64
   319  	sectSize := int64(unsafe.Sizeof(sect))
   320  	for i := uint32(0); i < nsect; i++ {
   321  		if err := r.ReadAt(sectOffset, &sect); err != nil {
   322  			return err
   323  		}
   324  		if compressedSects != nil {
   325  			cSect := compressedSects[i]
   326  			copy(sect.Name[:], cSect.Name)
   327  			sect.Size = cSect.Size
   328  			if cSect.Offset != 0 {
   329  				sect.Offset = cSect.Offset + uint32(deltaOffset)
   330  			}
   331  			if cSect.Addr != 0 {
   332  				sect.Addr = cSect.Addr
   333  			}
   334  		} else {
   335  			if sect.Offset != 0 {
   336  				sect.Offset += uint32(deltaOffset)
   337  			}
   338  			if sect.Reloff != 0 {
   339  				sect.Reloff += uint32(deltaOffset)
   340  			}
   341  		}
   342  		if err := r.WriteAt(sectOffset, &sect); err != nil {
   343  			return err
   344  		}
   345  		sectOffset += sectSize
   346  	}
   347  	return nil
   348  }
   349  
   350  // machoUpdateDwarfHeader updates the DWARF segment load command.
   351  func machoUpdateDwarfHeader(r *imacho.LoadCmdUpdater, compressedSects []*macho.Section, dwarfsize uint64, dwarfstart int64, realdwarf *macho.Segment) error {
   352  	cmd, err := r.Next()
   353  	if err != nil {
   354  		return err
   355  	}
   356  	if cmd.Cmd != macho.LoadCmdSegment64 {
   357  		panic("not a Segment64")
   358  	}
   359  	var seg macho.Segment64
   360  	if err := r.ReadAt(0, &seg); err != nil {
   361  		return err
   362  	}
   363  	seg.Offset = uint64(dwarfstart)
   364  
   365  	if compressedSects != nil {
   366  		var segSize uint64
   367  		for _, newSect := range compressedSects {
   368  			segSize += newSect.Size
   369  		}
   370  		seg.Filesz = segSize
   371  	} else {
   372  		seg.Filesz = dwarfsize
   373  	}
   374  
   375  	// We want the DWARF segment to be considered non-loadable, so
   376  	// force vmaddr and vmsize to zero. In addition, set the initial
   377  	// protection to zero so as to make the dynamic loader happy,
   378  	// since otherwise it may complain that the vm size and file
   379  	// size don't match for the segment. See issues 21647 and 32673
   380  	// for more context. Also useful to refer to the Apple dynamic
   381  	// loader source, specifically ImageLoaderMachO::sniffLoadCommands
   382  	// in ImageLoaderMachO.cpp (various versions can be found online, see
   383  	// https://opensource.apple.com/source/dyld/dyld-519.2.2/src/ImageLoaderMachO.cpp.auto.html
   384  	// as one example).
   385  	seg.Addr = 0
   386  	seg.Memsz = 0
   387  	seg.Prot = 0
   388  
   389  	if err := r.WriteAt(0, &seg); err != nil {
   390  		return err
   391  	}
   392  	return machoUpdateSections(*r, &seg, uint64(dwarfstart)-realdwarf.Offset, compressedSects)
   393  }
   394  
   395  func machoUpdateLoadCommand(r imacho.LoadCmdUpdater, linkseg *macho.Segment, linkoffset uint64, cmd interface{}, fields ...string) error {
   396  	if err := r.ReadAt(0, cmd); err != nil {
   397  		return err
   398  	}
   399  	value := reflect.Indirect(reflect.ValueOf(cmd))
   400  
   401  	for _, name := range fields {
   402  		field := value.FieldByName(name)
   403  		if fieldval := field.Uint(); fieldval >= linkseg.Offset {
   404  			field.SetUint(fieldval + linkoffset)
   405  		}
   406  	}
   407  	if err := r.WriteAt(0, cmd); err != nil {
   408  		return err
   409  	}
   410  	return nil
   411  }
   412  

View as plain text