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