...

Source file src/pkg/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		"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	//
    90	// With internal linking, DWARF is embedded into the executable, this lets us do the
    91	// same for external linking.
    92	// exef is the file of the executable with no DWARF. It must have enough room in the macho
    93	// header to add the DWARF sections. (Use ld's -headerpad option)
    94	// exem is the macho representation of exef.
    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, exef *os.File, exem *macho.File, dsym, outexe string) error {
    98		dwarff, err := os.Open(dsym)
    99		if err != nil {
   100			return err
   101		}
   102		defer dwarff.Close()
   103		outf, err := os.Create(outexe)
   104		if err != nil {
   105			return err
   106		}
   107		outf.Chmod(0755)
   108	
   109		dwarfm, err := macho.NewFile(dwarff)
   110		if err != nil {
   111			return err
   112		}
   113	
   114		// The string table needs to be the last thing in the file
   115		// for code signing to work. So we'll need to move the
   116		// linkedit section, but all the others can be copied directly.
   117		linkseg = exem.Segment("__LINKEDIT")
   118		if linkseg == nil {
   119			return fmt.Errorf("missing __LINKEDIT segment")
   120		}
   121	
   122		if _, err = exef.Seek(0, 0); err != nil {
   123			return err
   124		}
   125		if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
   126			return err
   127		}
   128	
   129		realdwarf = dwarfm.Segment("__DWARF")
   130		if realdwarf == nil {
   131			return fmt.Errorf("missing __DWARF segment")
   132		}
   133	
   134		// Try to compress the DWARF sections. This includes some Apple
   135		// proprietary sections like __apple_types.
   136		compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
   137		if err != nil {
   138			return err
   139		}
   140	
   141		// Now copy the dwarf data into the output.
   142		// Kernel requires all loaded segments to be page-aligned in the file,
   143		// even though we mark this one as being 0 bytes of virtual address space.
   144		dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
   145		if _, err = outf.Seek(dwarfstart, 0); err != nil {
   146			return err
   147		}
   148		dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
   149	
   150		if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
   151			return err
   152		}
   153	
   154		// Write out the compressed sections, or the originals if we gave up
   155		// on compressing them.
   156		var dwarfsize uint64
   157		if compressedBytes != nil {
   158			dwarfsize = uint64(len(compressedBytes))
   159			if _, err := outf.Write(compressedBytes); err != nil {
   160				return err
   161			}
   162		} else {
   163			if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
   164				return err
   165			}
   166			dwarfsize = realdwarf.Filesz
   167		}
   168	
   169		// And finally the linkedit section.
   170		if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
   171			return err
   172		}
   173		linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign)
   174		linkoffset = uint32(linkstart - int64(linkseg.Offset))
   175		if _, err = outf.Seek(linkstart, 0); err != nil {
   176			return err
   177		}
   178		if _, err := io.Copy(outf, exef); err != nil {
   179			return err
   180		}
   181	
   182		// Now we need to update the headers.
   183		textsect := exem.Section("__text")
   184		if linkseg == nil {
   185			return fmt.Errorf("missing __text section")
   186		}
   187	
   188		cmdOffset := unsafe.Sizeof(exem.FileHeader)
   189		is64bit := exem.Magic == macho.Magic64
   190		if is64bit {
   191			// mach_header_64 has one extra uint32.
   192			cmdOffset += unsafe.Sizeof(exem.Magic)
   193		}
   194		dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
   195		availablePadding := int64(textsect.Offset) - dwarfCmdOffset
   196		if availablePadding < int64(realdwarf.Len) {
   197			return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
   198		}
   199		// First, copy the dwarf load command into the header. It will be
   200		// updated later with new offsets and lengths as necessary.
   201		if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
   202			return err
   203		}
   204		if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
   205			return err
   206		}
   207		if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
   208			return err
   209		}
   210		if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
   211			return err
   212		}
   213		if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
   214			return err
   215		}
   216	
   217		reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
   218		for i := uint32(0); i < exem.Ncmd; i++ {
   219			cmd, err := reader.Next()
   220			if err != nil {
   221				return err
   222			}
   223			switch cmd.Cmd {
   224			case macho.LoadCmdSegment64:
   225				err = machoUpdateSegment(reader, &macho.Segment64{}, &macho.Section64{})
   226			case macho.LoadCmdSegment:
   227				err = machoUpdateSegment(reader, &macho.Segment32{}, &macho.Section32{})
   228			case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
   229				err = machoUpdateLoadCommand(reader, &dyldInfoCmd{}, "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff")
   230			case macho.LoadCmdSymtab:
   231				err = machoUpdateLoadCommand(reader, &macho.SymtabCmd{}, "Symoff", "Stroff")
   232			case macho.LoadCmdDysymtab:
   233				err = machoUpdateLoadCommand(reader, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
   234			case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
   235				err = machoUpdateLoadCommand(reader, &linkEditDataCmd{}, "DataOff")
   236			case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
   237				err = machoUpdateLoadCommand(reader, &encryptionInfoCmd{}, "CryptOff")
   238			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:
   239				// Nothing to update
   240			default:
   241				err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
   242			}
   243			if err != nil {
   244				return err
   245			}
   246		}
   247		// Do the final update of the DWARF segment's load command.
   248		return machoUpdateDwarfHeader(&reader, compressedSects, dwarfsize)
   249	}
   250	
   251	// machoCompressSections tries to compress the DWARF segments in dwarfm,
   252	// returning the updated sections and segment contents, nils if the sections
   253	// weren't compressed, or an error if there was a problem reading dwarfm.
   254	func machoCompressSections(ctxt *Link, dwarfm *macho.File) ([]*macho.Section, []byte, error) {
   255		if !ctxt.compressDWARF {
   256			return nil, nil, nil
   257		}
   258	
   259		dwarfseg := dwarfm.Segment("__DWARF")
   260		var sects []*macho.Section
   261		var bytes []byte
   262	
   263		for _, sect := range dwarfm.Sections {
   264			if sect.Seg != "__DWARF" {
   265				continue
   266			}
   267	
   268			// As of writing, there are no relocations in dsymutil's output
   269			// so there's no point in worrying about them. Bail out if that
   270			// changes.
   271			if sect.Nreloc != 0 {
   272				return nil, nil, nil
   273			}
   274	
   275			data, err := sect.Data()
   276			if err != nil {
   277				return nil, nil, err
   278			}
   279	
   280			compressed, contents, err := machoCompressSection(data)
   281			if err != nil {
   282				return nil, nil, err
   283			}
   284	
   285			newSec := *sect
   286			newSec.Offset = uint32(dwarfseg.Offset) + uint32(len(bytes))
   287			newSec.Addr = dwarfseg.Addr + uint64(len(bytes))
   288			if compressed {
   289				newSec.Name = "__z" + sect.Name[2:]
   290				newSec.Size = uint64(len(contents))
   291			}
   292			sects = append(sects, &newSec)
   293			bytes = append(bytes, contents...)
   294		}
   295		return sects, bytes, nil
   296	}
   297	
   298	// machoCompressSection compresses secBytes if it results in less data.
   299	func machoCompressSection(sectBytes []byte) (compressed bool, contents []byte, err error) {
   300		var buf bytes.Buffer
   301		buf.Write([]byte("ZLIB"))
   302		var sizeBytes [8]byte
   303		binary.BigEndian.PutUint64(sizeBytes[:], uint64(len(sectBytes)))
   304		buf.Write(sizeBytes[:])
   305	
   306		z := zlib.NewWriter(&buf)
   307		if _, err := z.Write(sectBytes); err != nil {
   308			return false, nil, err
   309		}
   310		if err := z.Close(); err != nil {
   311			return false, nil, err
   312		}
   313		if len(buf.Bytes()) >= len(sectBytes) {
   314			return false, sectBytes, nil
   315		}
   316		return true, buf.Bytes(), nil
   317	}
   318	
   319	// machoUpdateSegment updates the load command for a moved segment.
   320	// Only the linkedit segment should move, and it should have 0 sections.
   321	// seg should be a macho.Segment32 or macho.Segment64 as appropriate.
   322	// sect should be a macho.Section32 or macho.Section64 as appropriate.
   323	func machoUpdateSegment(r loadCmdReader, seg, sect interface{}) error {
   324		if err := r.ReadAt(0, seg); err != nil {
   325			return err
   326		}
   327		segValue := reflect.ValueOf(seg)
   328		offset := reflect.Indirect(segValue).FieldByName("Offset")
   329	
   330		// Only the linkedit segment moved, any thing before that is fine.
   331		if offset.Uint() < linkseg.Offset {
   332			return nil
   333		}
   334		offset.SetUint(offset.Uint() + uint64(linkoffset))
   335		if err := r.WriteAt(0, seg); err != nil {
   336			return err
   337		}
   338		// There shouldn't be any sections, but just to make sure...
   339		return machoUpdateSections(r, segValue, reflect.ValueOf(sect), uint64(linkoffset), 0, nil)
   340	}
   341	
   342	func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset, deltaAddr uint64, compressedSects []*macho.Section) error {
   343		iseg := reflect.Indirect(seg)
   344		nsect := iseg.FieldByName("Nsect").Uint()
   345		if nsect == 0 {
   346			return nil
   347		}
   348		sectOffset := int64(iseg.Type().Size())
   349	
   350		isect := reflect.Indirect(sect)
   351		offsetField := isect.FieldByName("Offset")
   352		reloffField := isect.FieldByName("Reloff")
   353		addrField := isect.FieldByName("Addr")
   354		nameField := isect.FieldByName("Name")
   355		sizeField := isect.FieldByName("Size")
   356		sectSize := int64(isect.Type().Size())
   357		for i := uint64(0); i < nsect; i++ {
   358			if err := r.ReadAt(sectOffset, sect.Interface()); err != nil {
   359				return err
   360			}
   361			if compressedSects != nil {
   362				cSect := compressedSects[i]
   363				var name [16]byte
   364				copy(name[:], []byte(cSect.Name))
   365				nameField.Set(reflect.ValueOf(name))
   366				sizeField.SetUint(cSect.Size)
   367				if cSect.Offset != 0 {
   368					offsetField.SetUint(uint64(cSect.Offset) + deltaOffset)
   369				}
   370				if cSect.Addr != 0 {
   371					addrField.SetUint(cSect.Addr + deltaAddr)
   372				}
   373			} else {
   374				if offsetField.Uint() != 0 {
   375					offsetField.SetUint(offsetField.Uint() + deltaOffset)
   376				}
   377				if reloffField.Uint() != 0 {
   378					reloffField.SetUint(reloffField.Uint() + deltaOffset)
   379				}
   380				if addrField.Uint() != 0 {
   381					addrField.SetUint(addrField.Uint() + deltaAddr)
   382				}
   383			}
   384			if err := r.WriteAt(sectOffset, sect.Interface()); err != nil {
   385				return err
   386			}
   387			sectOffset += sectSize
   388		}
   389		return nil
   390	}
   391	
   392	// machoUpdateDwarfHeader updates the DWARF segment load command.
   393	func machoUpdateDwarfHeader(r *loadCmdReader, compressedSects []*macho.Section, dwarfsize uint64) error {
   394		var seg, sect interface{}
   395		cmd, err := r.Next()
   396		if err != nil {
   397			return err
   398		}
   399		if cmd.Cmd == macho.LoadCmdSegment64 {
   400			seg = new(macho.Segment64)
   401			sect = new(macho.Section64)
   402		} else {
   403			seg = new(macho.Segment32)
   404			sect = new(macho.Section32)
   405		}
   406		if err := r.ReadAt(0, seg); err != nil {
   407			return err
   408		}
   409		segv := reflect.ValueOf(seg).Elem()
   410		segv.FieldByName("Offset").SetUint(uint64(dwarfstart))
   411	
   412		if compressedSects != nil {
   413			var segSize uint64
   414			for _, newSect := range compressedSects {
   415				segSize += newSect.Size
   416			}
   417			segv.FieldByName("Filesz").SetUint(segSize)
   418		} else {
   419			segv.FieldByName("Filesz").SetUint(dwarfsize)
   420		}
   421	
   422		deltaOffset := uint64(dwarfstart) - realdwarf.Offset
   423		deltaAddr := uint64(dwarfaddr) - realdwarf.Addr
   424	
   425		// We want the DWARF segment to be considered non-loadable, so
   426		// force vmaddr and vmsize to zero. In addition, set the initial
   427		// protection to zero so as to make the dynamic loader happy,
   428		// since otherwise it may complain that that the vm size and file
   429		// size don't match for the segment. See issues 21647 and 32673
   430		// for more context. Also useful to refer to the Apple dynamic
   431		// loader source, specifically ImageLoaderMachO::sniffLoadCommands
   432		// in ImageLoaderMachO.cpp (various versions can be found online, see
   433		// https://opensource.apple.com/source/dyld/dyld-519.2.2/src/ImageLoaderMachO.cpp.auto.html
   434		// as one example).
   435		segv.FieldByName("Addr").SetUint(0)
   436		segv.FieldByName("Memsz").SetUint(0)
   437		segv.FieldByName("Prot").SetUint(0)
   438		deltaAddr = 0
   439	
   440		if err := r.WriteAt(0, seg); err != nil {
   441			return err
   442		}
   443		return machoUpdateSections(*r, segv, reflect.ValueOf(sect), deltaOffset, deltaAddr, compressedSects)
   444	}
   445	
   446	func machoUpdateLoadCommand(r loadCmdReader, cmd interface{}, fields ...string) error {
   447		if err := r.ReadAt(0, cmd); err != nil {
   448			return err
   449		}
   450		value := reflect.Indirect(reflect.ValueOf(cmd))
   451	
   452		for _, name := range fields {
   453			field := value.FieldByName(name)
   454			fieldval := field.Uint()
   455			if fieldval >= linkseg.Offset {
   456				field.SetUint(fieldval + uint64(linkoffset))
   457			}
   458		}
   459		if err := r.WriteAt(0, cmd); err != nil {
   460			return err
   461		}
   462		return nil
   463	}
   464	
   465	func machoCalcStart(origAddr, newAddr uint64, alignExp uint32) int64 {
   466		align := uint64(1 << alignExp)
   467		if (origAddr % align) == (newAddr % align) {
   468			return int64(newAddr)
   469		}
   470		padding := (align - (newAddr % align))
   471		padding += origAddr % align
   472		return int64(padding + newAddr)
   473	}
   474	

View as plain text