Home | History | Annotate | Download | only in 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 	"debug/macho"
     10 	"encoding/binary"
     11 	"fmt"
     12 	"io"
     13 	"os"
     14 	"reflect"
     15 	"unsafe"
     16 )
     17 
     18 var realdwarf, linkseg *macho.Segment
     19 var dwarfstart, linkstart int64
     20 var dwarfaddr int64
     21 var linkoffset uint32
     22 
     23 const (
     24 	pageAlign = 12 // 4096 = 1 << 12
     25 )
     26 
     27 type loadCmd struct {
     28 	Cmd macho.LoadCmd
     29 	Len uint32
     30 }
     31 
     32 type dyldInfoCmd struct {
     33 	Cmd                      macho.LoadCmd
     34 	Len                      uint32
     35 	RebaseOff, RebaseLen     uint32
     36 	BindOff, BindLen         uint32
     37 	WeakBindOff, WeakBindLen uint32
     38 	LazyBindOff, LazyBindLen uint32
     39 	ExportOff, ExportLen     uint32
     40 }
     41 
     42 type linkEditDataCmd struct {
     43 	Cmd              macho.LoadCmd
     44 	Len              uint32
     45 	DataOff, DataLen uint32
     46 }
     47 
     48 type encryptionInfoCmd struct {
     49 	Cmd                macho.LoadCmd
     50 	Len                uint32
     51 	CryptOff, CryptLen uint32
     52 	CryptId            uint32
     53 }
     54 
     55 type loadCmdReader struct {
     56 	offset, next int64
     57 	f            *os.File
     58 	order        binary.ByteOrder
     59 }
     60 
     61 func (r *loadCmdReader) Next() (cmd loadCmd, err error) {
     62 	r.offset = r.next
     63 	if _, err = r.f.Seek(r.offset, 0); err != nil {
     64 		return
     65 	}
     66 	if err = binary.Read(r.f, r.order, &cmd); err != nil {
     67 		return
     68 	}
     69 	r.next = r.offset + int64(cmd.Len)
     70 	return
     71 }
     72 
     73 func (r loadCmdReader) ReadAt(offset int64, data interface{}) error {
     74 	if _, err := r.f.Seek(r.offset+offset, 0); err != nil {
     75 		return err
     76 	}
     77 	return binary.Read(r.f, r.order, data)
     78 }
     79 
     80 func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
     81 	if _, err := r.f.Seek(r.offset+offset, 0); err != nil {
     82 		return err
     83 	}
     84 	return binary.Write(r.f, r.order, data)
     85 }
     86 
     87 // machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
     88 // With internal linking, DWARF is embedded into the executable, this lets us do the
     89 // same for external linking.
     90 // inexe is the path to the executable with no DWARF. It must have enough room in the macho
     91 // header to add the DWARF sections. (Use ld's -headerpad option)
     92 // dsym is the path to the macho file containing DWARF from dsymutil.
     93 // outexe is the path where the combined executable should be saved.
     94 func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error {
     95 	exef, err := os.Open(inexe)
     96 	if err != nil {
     97 		return err
     98 	}
     99 	dwarff, err := os.Open(dsym)
    100 	if err != nil {
    101 		return err
    102 	}
    103 	outf, err := os.Create(outexe)
    104 	if err != nil {
    105 		return err
    106 	}
    107 	outf.Chmod(0755)
    108 
    109 	exem, err := macho.NewFile(exef)
    110 	if err != nil {
    111 		return err
    112 	}
    113 	dwarfm, err := macho.NewFile(dwarff)
    114 	if err != nil {
    115 		return err
    116 	}
    117 
    118 	// The string table needs to be the last thing in the file
    119 	// for code signing to work. So we'll need to move the
    120 	// linkedit section, but all the others can be copied directly.
    121 	linkseg = exem.Segment("__LINKEDIT")
    122 	if linkseg == nil {
    123 		return fmt.Errorf("missing __LINKEDIT segment")
    124 	}
    125 
    126 	if _, err = exef.Seek(0, 0); err != nil {
    127 		return err
    128 	}
    129 	if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
    130 		return err
    131 	}
    132 
    133 	realdwarf = dwarfm.Segment("__DWARF")
    134 	if realdwarf == nil {
    135 		return fmt.Errorf("missing __DWARF segment")
    136 	}
    137 
    138 	// Now copy the dwarf data into the output.
    139 	// Kernel requires all loaded segments to be page-aligned in the file,
    140 	// even though we mark this one as being 0 bytes of virtual address space.
    141 	dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
    142 	if _, err = outf.Seek(dwarfstart, 0); err != nil {
    143 		return err
    144 	}
    145 	dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
    146 
    147 	if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
    148 		return err
    149 	}
    150 	if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
    151 		return err
    152 	}
    153 
    154 	// And finally the linkedit section.
    155 	if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
    156 		return err
    157 	}
    158 	linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign)
    159 	linkoffset = uint32(linkstart - int64(linkseg.Offset))
    160 	if _, err = outf.Seek(linkstart, 0); err != nil {
    161 		return err
    162 	}
    163 	if _, err := io.Copy(outf, exef); err != nil {
    164 		return err
    165 	}
    166 
    167 	// Now we need to update the headers.
    168 	cmdOffset := unsafe.Sizeof(exem.FileHeader)
    169 	is64bit := exem.Magic == macho.Magic64
    170 	if is64bit {
    171 		// mach_header_64 has one extra uint32.
    172 		cmdOffset += unsafe.Sizeof(exem.Magic)
    173 	}
    174 
    175 	textsect := exem.Section("__text")
    176 	if linkseg == nil {
    177 		return fmt.Errorf("missing __text section")
    178 	}
    179 
    180 	dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
    181 	availablePadding := int64(textsect.Offset) - dwarfCmdOffset
    182 	if availablePadding < int64(realdwarf.Len) {
    183 		return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
    184 	}
    185 	// First, copy the dwarf load command into the header
    186 	if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
    187 		return err
    188 	}
    189 	if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
    190 		return err
    191 	}
    192 
    193 	if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
    194 		return err
    195 	}
    196 	if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
    197 		return err
    198 	}
    199 	if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
    200 		return err
    201 	}
    202 
    203 	reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
    204 	for i := uint32(0); i < exem.Ncmd; i++ {
    205 		cmd, err := reader.Next()
    206 		if err != nil {
    207 			return err
    208 		}
    209 		switch cmd.Cmd {
    210 		case macho.LoadCmdSegment64:
    211 			err = machoUpdateSegment(reader, &macho.Segment64{}, &macho.Section64{})
    212 		case macho.LoadCmdSegment:
    213 			err = machoUpdateSegment(reader, &macho.Segment32{}, &macho.Section32{})
    214 		case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
    215 			err = machoUpdateLoadCommand(reader, &dyldInfoCmd{}, "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff")
    216 		case macho.LoadCmdSymtab:
    217 			err = machoUpdateLoadCommand(reader, &macho.SymtabCmd{}, "Symoff", "Stroff")
    218 		case macho.LoadCmdDysymtab:
    219 			err = machoUpdateLoadCommand(reader, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
    220 		case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
    221 			err = machoUpdateLoadCommand(reader, &linkEditDataCmd{}, "DataOff")
    222 		case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
    223 			err = machoUpdateLoadCommand(reader, &encryptionInfoCmd{}, "CryptOff")
    224 		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:
    225 			// Nothing to update
    226 		default:
    227 			err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
    228 		}
    229 		if err != nil {
    230 			return err
    231 		}
    232 	}
    233 	return machoUpdateDwarfHeader(&reader, buildmode)
    234 }
    235 
    236 // machoUpdateSegment updates the load command for a moved segment.
    237 // Only the linkedit segment should move, and it should have 0 sections.
    238 // seg should be a macho.Segment32 or macho.Segment64 as appropriate.
    239 // sect should be a macho.Section32 or macho.Section64 as appropriate.
    240 func machoUpdateSegment(r loadCmdReader, seg, sect interface{}) error {
    241 	if err := r.ReadAt(0, seg); err != nil {
    242 		return err
    243 	}
    244 	segValue := reflect.ValueOf(seg)
    245 	offset := reflect.Indirect(segValue).FieldByName("Offset")
    246 
    247 	// Only the linkedit segment moved, any thing before that is fine.
    248 	if offset.Uint() < linkseg.Offset {
    249 		return nil
    250 	}
    251 	offset.SetUint(offset.Uint() + uint64(linkoffset))
    252 	if err := r.WriteAt(0, seg); err != nil {
    253 		return err
    254 	}
    255 	// There shouldn't be any sections, but just to make sure...
    256 	return machoUpdateSections(r, segValue, reflect.ValueOf(sect), uint64(linkoffset), 0)
    257 }
    258 
    259 func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset, deltaAddr uint64) error {
    260 	iseg := reflect.Indirect(seg)
    261 	nsect := iseg.FieldByName("Nsect").Uint()
    262 	if nsect == 0 {
    263 		return nil
    264 	}
    265 	sectOffset := int64(iseg.Type().Size())
    266 
    267 	isect := reflect.Indirect(sect)
    268 	offsetField := isect.FieldByName("Offset")
    269 	reloffField := isect.FieldByName("Reloff")
    270 	addrField := isect.FieldByName("Addr")
    271 	sectSize := int64(isect.Type().Size())
    272 	for i := uint64(0); i < nsect; i++ {
    273 		if err := r.ReadAt(sectOffset, sect.Interface()); err != nil {
    274 			return err
    275 		}
    276 		if offsetField.Uint() != 0 {
    277 			offsetField.SetUint(offsetField.Uint() + deltaOffset)
    278 		}
    279 		if reloffField.Uint() != 0 {
    280 			reloffField.SetUint(reloffField.Uint() + deltaOffset)
    281 		}
    282 		if addrField.Uint() != 0 {
    283 			addrField.SetUint(addrField.Uint() + deltaAddr)
    284 		}
    285 		if err := r.WriteAt(sectOffset, sect.Interface()); err != nil {
    286 			return err
    287 		}
    288 		sectOffset += sectSize
    289 	}
    290 	return nil
    291 }
    292 
    293 // machoUpdateDwarfHeader updates the DWARF segment load command.
    294 func machoUpdateDwarfHeader(r *loadCmdReader, buildmode BuildMode) error {
    295 	var seg, sect interface{}
    296 	cmd, err := r.Next()
    297 	if err != nil {
    298 		return err
    299 	}
    300 	if cmd.Cmd == macho.LoadCmdSegment64 {
    301 		seg = new(macho.Segment64)
    302 		sect = new(macho.Section64)
    303 	} else {
    304 		seg = new(macho.Segment32)
    305 		sect = new(macho.Section32)
    306 	}
    307 	if err := r.ReadAt(0, seg); err != nil {
    308 		return err
    309 	}
    310 	segv := reflect.ValueOf(seg).Elem()
    311 
    312 	segv.FieldByName("Offset").SetUint(uint64(dwarfstart))
    313 	segv.FieldByName("Addr").SetUint(uint64(dwarfaddr))
    314 
    315 	deltaOffset := uint64(dwarfstart) - realdwarf.Offset
    316 	deltaAddr := uint64(dwarfaddr) - realdwarf.Addr
    317 
    318 	// If we set Memsz to 0 (and might as well set Addr too),
    319 	// then the xnu kernel will bail out halfway through load_segment
    320 	// and not apply further sanity checks that we might fail in the future.
    321 	// We don't need the DWARF information actually available in memory.
    322 	// But if we do this for buildmode=c-shared then the user-space
    323 	// dynamic loader complains about memsz < filesz. Sigh.
    324 	if buildmode != BuildModeCShared {
    325 		segv.FieldByName("Addr").SetUint(0)
    326 		segv.FieldByName("Memsz").SetUint(0)
    327 		deltaAddr = 0
    328 	}
    329 
    330 	if err := r.WriteAt(0, seg); err != nil {
    331 		return err
    332 	}
    333 	return machoUpdateSections(*r, segv, reflect.ValueOf(sect), deltaOffset, deltaAddr)
    334 }
    335 
    336 func machoUpdateLoadCommand(r loadCmdReader, cmd interface{}, fields ...string) error {
    337 	if err := r.ReadAt(0, cmd); err != nil {
    338 		return err
    339 	}
    340 	value := reflect.Indirect(reflect.ValueOf(cmd))
    341 
    342 	for _, name := range fields {
    343 		field := value.FieldByName(name)
    344 		fieldval := field.Uint()
    345 		if fieldval >= linkseg.Offset {
    346 			field.SetUint(fieldval + uint64(linkoffset))
    347 		}
    348 	}
    349 	if err := r.WriteAt(0, cmd); err != nil {
    350 		return err
    351 	}
    352 	return nil
    353 }
    354 
    355 func machoCalcStart(origAddr, newAddr uint64, alignExp uint32) int64 {
    356 	align := uint64(1 << alignExp)
    357 	if (origAddr % align) == (newAddr % align) {
    358 		return int64(newAddr)
    359 	}
    360 	padding := (align - (newAddr % align))
    361 	padding += origAddr % align
    362 	return int64(padding + newAddr)
    363 }
    364