github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/link/internal/ld/macho_combine_dwarf.go (about) 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 }