Skip to content

Commit 726a2d0

Browse files
committed
cmd/link: support DWARF compression on Darwin
We want to compress DWARF even on macOS, but the native toolchain isn't going to understand it. Add a flag that can be used to disable compression, then add Darwin to the whitelist used during internal linking. Unlike GNU ld, the Darwin linker doesn't have a handy linker flag to do compression. But since we're already doing surgery to put the DWARF in the output executable in the first place, compressing it at the same time isn't unduly difficult. This does have the slightly odd effect of compressing some Apple proprietary debug sections, which absolutely nothing will understand. Leaving them uncompressed didn't make much sense, though, since I doubt they're useful without (say) __debug_info. Updates #11799 Change-Id: Ie00b0215c630a798c59d009a641e2d13f0e7ea01 Reviewed-on: https://go-review.googlesource.com/120155 Run-TryBot: Heschi Kreinick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 3dced51 commit 726a2d0

File tree

5 files changed

+142
-26
lines changed

5 files changed

+142
-26
lines changed

src/cmd/link/internal/ld/dwarf.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1951,7 +1951,8 @@ func dwarfaddelfsectionsyms(ctxt *Link) {
19511951
// relocations are applied. After this, dwarfp will contain a
19521952
// different (new) set of symbols, and sections may have been replaced.
19531953
func dwarfcompress(ctxt *Link) {
1954-
if !(ctxt.IsELF || ctxt.HeadType == objabi.Hwindows) || ctxt.LinkMode == LinkExternal {
1954+
supported := ctxt.IsELF || ctxt.HeadType == objabi.Hwindows || ctxt.HeadType == objabi.Hdarwin
1955+
if !ctxt.compressDWARF || !supported || ctxt.LinkMode != LinkInternal {
19551956
return
19561957
}
19571958

src/cmd/link/internal/ld/lib.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,7 +1222,7 @@ func (ctxt *Link) hostlink() {
12221222
}
12231223

12241224
const compressDWARF = "-Wl,--compress-debug-sections=zlib-gnu"
1225-
if linkerFlagSupported(argv[0], compressDWARF) {
1225+
if ctxt.compressDWARF && linkerFlagSupported(argv[0], compressDWARF) {
12261226
argv = append(argv, compressDWARF)
12271227
}
12281228

@@ -1336,7 +1336,7 @@ func (ctxt *Link) hostlink() {
13361336
}
13371337
// For os.Rename to work reliably, must be in same directory as outfile.
13381338
combinedOutput := *flagOutfile + "~"
1339-
isIOS, err := machoCombineDwarf(*flagOutfile, dsym, combinedOutput, ctxt.BuildMode)
1339+
isIOS, err := machoCombineDwarf(ctxt, *flagOutfile, dsym, combinedOutput)
13401340
if err != nil {
13411341
Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
13421342
}

src/cmd/link/internal/ld/link.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ type Link struct {
6363
IsELF bool
6464
HeadType objabi.HeadType
6565

66-
linkShared bool // link against installed Go shared libraries
67-
LinkMode LinkMode
68-
BuildMode BuildMode
66+
linkShared bool // link against installed Go shared libraries
67+
LinkMode LinkMode
68+
BuildMode BuildMode
69+
compressDWARF bool
6970

7071
Tlsg *sym.Symbol
7172
Libdir []string

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

Lines changed: 133 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package ld
66

77
import (
88
"bytes"
9+
"compress/zlib"
910
"debug/macho"
1011
"encoding/binary"
1112
"fmt"
@@ -93,7 +94,7 @@ func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
9394
// header to add the DWARF sections. (Use ld's -headerpad option)
9495
// dsym is the path to the macho file containing DWARF from dsymutil.
9596
// outexe is the path where the combined executable should be saved.
96-
func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, error) {
97+
func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
9798
exef, err := os.Open(inexe)
9899
if err != nil {
99100
return false, err
@@ -156,6 +157,13 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, e
156157
return false, fmt.Errorf("missing __DWARF segment")
157158
}
158159

160+
// Try to compress the DWARF sections. This includes some Apple
161+
// proprietary sections like __apple_types.
162+
compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
163+
if err != nil {
164+
return false, err
165+
}
166+
159167
// Now copy the dwarf data into the output.
160168
// Kernel requires all loaded segments to be page-aligned in the file,
161169
// even though we mark this one as being 0 bytes of virtual address space.
@@ -168,15 +176,27 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, e
168176
if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
169177
return false, err
170178
}
171-
if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
172-
return false, err
179+
180+
// Write out the compressed sections, or the originals if we gave up
181+
// on compressing them.
182+
var dwarfsize uint64
183+
if compressedBytes != nil {
184+
dwarfsize = uint64(len(compressedBytes))
185+
if _, err := outf.Write(compressedBytes); err != nil {
186+
return false, err
187+
}
188+
} else {
189+
if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
190+
return false, err
191+
}
192+
dwarfsize = realdwarf.Filesz
173193
}
174194

175195
// And finally the linkedit section.
176196
if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
177197
return false, err
178198
}
179-
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign)
199+
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign)
180200
linkoffset = uint32(linkstart - int64(linkseg.Offset))
181201
if _, err = outf.Seek(linkstart, 0); err != nil {
182202
return false, err
@@ -196,14 +216,14 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, e
196216
if availablePadding < int64(realdwarf.Len) {
197217
return false, fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
198218
}
199-
// First, copy the dwarf load command into the header
219+
// First, copy the dwarf load command into the header. It will be
220+
// updated later with new offsets and lengths as necessary.
200221
if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
201222
return false, err
202223
}
203224
if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
204225
return false, err
205226
}
206-
207227
if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
208228
return false, err
209229
}
@@ -244,7 +264,76 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, e
244264
return false, err
245265
}
246266
}
247-
return false, machoUpdateDwarfHeader(&reader, buildmode)
267+
// Do the final update of the DWARF segment's load command.
268+
return false, machoUpdateDwarfHeader(&reader, ctxt.BuildMode, compressedSects)
269+
}
270+
271+
// machoCompressSections tries to compress the DWARF segments in dwarfm,
272+
// returning the updated sections and segment contents, nils if the sections
273+
// weren't compressed, or an error if there was a problem reading dwarfm.
274+
func machoCompressSections(ctxt *Link, dwarfm *macho.File) ([]*macho.Section, []byte, error) {
275+
if !ctxt.compressDWARF {
276+
return nil, nil, nil
277+
}
278+
279+
dwarfseg := dwarfm.Segment("__DWARF")
280+
var sects []*macho.Section
281+
var bytes []byte
282+
283+
for _, sect := range dwarfm.Sections {
284+
if sect.Seg != "__DWARF" {
285+
continue
286+
}
287+
288+
// As of writing, there are no relocations in dsymutil's output
289+
// so there's no point in worrying about them. Bail out if that
290+
// changes.
291+
if sect.Nreloc != 0 {
292+
return nil, nil, nil
293+
}
294+
295+
data, err := sect.Data()
296+
if err != nil {
297+
return nil, nil, err
298+
}
299+
300+
compressed, contents, err := machoCompressSection(data)
301+
if err != nil {
302+
return nil, nil, err
303+
}
304+
305+
newSec := *sect
306+
newSec.Offset = uint32(dwarfseg.Offset) + uint32(len(bytes))
307+
newSec.Addr = dwarfseg.Addr + uint64(len(bytes))
308+
if compressed {
309+
newSec.Name = "__z" + sect.Name[2:]
310+
newSec.Size = uint64(len(contents))
311+
}
312+
sects = append(sects, &newSec)
313+
bytes = append(bytes, contents...)
314+
}
315+
return sects, bytes, nil
316+
}
317+
318+
// machoCompressSection compresses secBytes if it results in less data.
319+
func machoCompressSection(sectBytes []byte) (compressed bool, contents []byte, err error) {
320+
var buf bytes.Buffer
321+
buf.Write([]byte("ZLIB"))
322+
var sizeBytes [8]byte
323+
binary.BigEndian.PutUint64(sizeBytes[:], uint64(len(sectBytes)))
324+
buf.Write(sizeBytes[:])
325+
326+
z := zlib.NewWriter(&buf)
327+
if _, err := z.Write(sectBytes); err != nil {
328+
return false, nil, err
329+
}
330+
if err := z.Close(); err != nil {
331+
return false, nil, err
332+
}
333+
if len(buf.Bytes()) >= len(sectBytes) {
334+
return false, sectBytes, nil
335+
}
336+
return true, buf.Bytes(), nil
248337
}
249338

250339
// machoUpdateSegment updates the load command for a moved segment.
@@ -267,10 +356,10 @@ func machoUpdateSegment(r loadCmdReader, seg, sect interface{}) error {
267356
return err
268357
}
269358
// There shouldn't be any sections, but just to make sure...
270-
return machoUpdateSections(r, segValue, reflect.ValueOf(sect), uint64(linkoffset), 0)
359+
return machoUpdateSections(r, segValue, reflect.ValueOf(sect), uint64(linkoffset), 0, nil)
271360
}
272361

273-
func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset, deltaAddr uint64) error {
362+
func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset, deltaAddr uint64, compressedSects []*macho.Section) error {
274363
iseg := reflect.Indirect(seg)
275364
nsect := iseg.FieldByName("Nsect").Uint()
276365
if nsect == 0 {
@@ -282,19 +371,35 @@ func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset,
282371
offsetField := isect.FieldByName("Offset")
283372
reloffField := isect.FieldByName("Reloff")
284373
addrField := isect.FieldByName("Addr")
374+
nameField := isect.FieldByName("Name")
375+
sizeField := isect.FieldByName("Size")
285376
sectSize := int64(isect.Type().Size())
286377
for i := uint64(0); i < nsect; i++ {
287378
if err := r.ReadAt(sectOffset, sect.Interface()); err != nil {
288379
return err
289380
}
290-
if offsetField.Uint() != 0 {
291-
offsetField.SetUint(offsetField.Uint() + deltaOffset)
292-
}
293-
if reloffField.Uint() != 0 {
294-
reloffField.SetUint(reloffField.Uint() + deltaOffset)
295-
}
296-
if addrField.Uint() != 0 {
297-
addrField.SetUint(addrField.Uint() + deltaAddr)
381+
if compressedSects != nil {
382+
cSect := compressedSects[i]
383+
var name [16]byte
384+
copy(name[:], []byte(cSect.Name))
385+
nameField.Set(reflect.ValueOf(name))
386+
sizeField.SetUint(cSect.Size)
387+
if cSect.Offset != 0 {
388+
offsetField.SetUint(uint64(cSect.Offset) + deltaOffset)
389+
}
390+
if cSect.Addr != 0 {
391+
addrField.SetUint(cSect.Addr + deltaAddr)
392+
}
393+
} else {
394+
if offsetField.Uint() != 0 {
395+
offsetField.SetUint(offsetField.Uint() + deltaOffset)
396+
}
397+
if reloffField.Uint() != 0 {
398+
reloffField.SetUint(reloffField.Uint() + deltaOffset)
399+
}
400+
if addrField.Uint() != 0 {
401+
addrField.SetUint(addrField.Uint() + deltaAddr)
402+
}
298403
}
299404
if err := r.WriteAt(sectOffset, sect.Interface()); err != nil {
300405
return err
@@ -305,7 +410,7 @@ func machoUpdateSections(r loadCmdReader, seg, sect reflect.Value, deltaOffset,
305410
}
306411

307412
// machoUpdateDwarfHeader updates the DWARF segment load command.
308-
func machoUpdateDwarfHeader(r *loadCmdReader, buildmode BuildMode) error {
413+
func machoUpdateDwarfHeader(r *loadCmdReader, buildmode BuildMode, compressedSects []*macho.Section) error {
309414
var seg, sect interface{}
310415
cmd, err := r.Next()
311416
if err != nil {
@@ -322,10 +427,18 @@ func machoUpdateDwarfHeader(r *loadCmdReader, buildmode BuildMode) error {
322427
return err
323428
}
324429
segv := reflect.ValueOf(seg).Elem()
325-
326430
segv.FieldByName("Offset").SetUint(uint64(dwarfstart))
327431
segv.FieldByName("Addr").SetUint(uint64(dwarfaddr))
328432

433+
if compressedSects != nil {
434+
var segSize uint64
435+
for _, newSect := range compressedSects {
436+
segSize += newSect.Size
437+
}
438+
segv.FieldByName("Filesz").SetUint(segSize)
439+
segv.FieldByName("Memsz").SetUint(uint64(Rnd(int64(segSize), 1<<pageAlign)))
440+
}
441+
329442
deltaOffset := uint64(dwarfstart) - realdwarf.Offset
330443
deltaAddr := uint64(dwarfaddr) - realdwarf.Addr
331444

@@ -344,7 +457,7 @@ func machoUpdateDwarfHeader(r *loadCmdReader, buildmode BuildMode) error {
344457
if err := r.WriteAt(0, seg); err != nil {
345458
return err
346459
}
347-
return machoUpdateSections(*r, segv, reflect.ValueOf(sect), deltaOffset, deltaAddr)
460+
return machoUpdateSections(*r, segv, reflect.ValueOf(sect), deltaOffset, deltaAddr, compressedSects)
348461
}
349462

350463
func machoUpdateLoadCommand(r loadCmdReader, cmd interface{}, fields ...string) error {

src/cmd/link/internal/ld/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func Main(arch *sys.Arch, theArch Arch) {
122122
flag.BoolVar(&ctxt.linkShared, "linkshared", false, "link against installed Go shared libraries")
123123
flag.Var(&ctxt.LinkMode, "linkmode", "set link `mode`")
124124
flag.Var(&ctxt.BuildMode, "buildmode", "set build `mode`")
125+
flag.BoolVar(&ctxt.compressDWARF, "compressdwarf", true, "compress DWARF if possible")
125126
objabi.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF", addbuildinfo)
126127
objabi.Flagfn1("L", "add specified `directory` to library path", func(a string) { Lflag(ctxt, a) })
127128
objabi.AddVersionFlag() // -V

0 commit comments

Comments
 (0)