-
Notifications
You must be signed in to change notification settings - Fork 18k
cmd/link: compress debug info #11799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
It's only tangentially related, but it's been at the back of my mind to try to figure out what would be required to make stripping Go binaries (and so being able to use detached debugging symbols) more of a supported thing. |
I think that this could reduce the size of cmd/go by about 15%. How I got that number (OS X commands): $ dwarfdump -R `which go`
[snip]
Segments
Segment Name vmaddr vmsize fileoff filesize maxprot initprot nsects flags
---------------- ---------------- ---------------- ---------------- ---------------- -------- -------- -------- --------
__PAGEZERO 0000000000000000 0000000000001000 0000000000000000 0000000000000000 00000000 00000000 00000000 00000000
__TEXT 0000000000001000 00000000007bf000 0000000000000000 00000000007bf000 00000007 00000005 00000006 00000000
__DATA 00000000007c0000 0000000000045060 00000000007bf000 0000000000020ac0 00000003 00000003 00000005 00000000
__LINKEDIT 0000000000806000 0000000000083c4c 0000000000ac6000 0000000000083c4c 00000007 00000003 00000000 00000000
__DWARF 0000000000805060 0000000000000000 00000000007e0000 00000000002e5968 00000000 00000000 00000008 00000000
Sections
Section Name Segment Name addr size offset align reloff nreloc flags reserv1 reserv2 reserv3 size size %
---------------- ---------------- ---------------- ---------------- -------- -------- -------- -------- -------- -------- -------- -------- ======== ======
__text __TEXT 0000000000002000 00000000003a6f38 00001000 00000004 00000000 00000000 00000400 00000000 00000000 00000000 3.65M 33.49%
__rodata __TEXT 00000000003a8f40 0000000000283f0c 003a7f40 00000005 00000000 00000000 00000000 00000000 00000000 00000000 2.52M 23.06%
__typelink __TEXT 000000000062ce50 000000000000e7f0 0062be50 00000003 00000000 00000000 00000000 00000000 00000000 00000000 57.98K 0.52%
__gosymtab __TEXT 000000000063b640 0000000000000000 0063a640 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0 0.00%
__gopclntab __TEXT 000000000063b640 000000000018415b 0063a640 00000005 00000000 00000000 00000000 00000000 00000000 00000000 1.52M 13.90%
__symbol_stub1 __TEXT 00000000007bf7a0 00000000000000d8 007be7a0 00000005 00000000 00000000 80000408 00000000 00000006 00000000 216 0.00%
__nl_symbol_ptr __DATA 00000000007c0000 0000000000000138 007bf000 00000002 00000000 00000000 00000006 00000024 00000000 00000000 312 0.00%
__noptrdata __DATA 00000000007c0140 0000000000014008 007bf140 00000005 00000000 00000000 00000000 00000000 00000000 00000000 80.01K 0.72%
__data __DATA 00000000007d4160 000000000000c960 007d3160 00000005 00000000 00000000 00000000 00000000 00000000 00000000 50.34K 0.45%
__bss __DATA 00000000007e0ac0 000000000001e138 00000000 00000005 00000000 00000000 00000001 00000000 00000000 00000000 120.30K 1.08%
__noptrbss __DATA 00000000007fec00 0000000000006460 00000000 00000005 00000000 00000000 00000001 00000000 00000000 00000000 25.09K 0.22%
__debug_abbrev __DWARF 0000000000805060 00000000000000ff 007e0000 00000000 00000000 00000000 02000000 00000000 00000000 00000000 255 0.00%
__debug_line __DWARF 000000000080515f 00000000000ac904 007e00ff 00000000 00000000 00000000 02000000 00000000 00000000 00000000 690.25K 6.18%
__debug_frame __DWARF 00000000008b1a63 000000000005c574 0088ca03 00000000 00000000 00000000 02000000 00000000 00000000 00000000 369.36K 3.31%
__debug_info __DWARF 000000000090dfd7 0000000000154b49 008e8f77 00000000 00000000 00000000 02000000 00000000 00000000 00000000 1.33M 12.20%
__debug_pubnames __DWARF 0000000000a62b20 000000000005f4c1 00a3dac0 00000000 00000000 00000000 02000000 00000000 00000000 00000000 381.19K 3.41%
__debug_pubtypes __DWARF 0000000000ac1fe1 000000000002897f 00a9cf81 00000000 00000000 00000000 02000000 00000000 00000000 00000000 162.37K 1.45%
__debug_aranges __DWARF 0000000000aea960 0000000000000030 00ac5900 00000000 00000000 00000000 02000000 00000000 00000000 00000000 48 0.00%
__debug_gdb_scri __DWARF 0000000000aea990 0000000000000035 00ac5930 00000000 00000000 00000000 02000000 00000000 00000000 00000000 53 0.00%
$ # manually convert fileoff and filesize from hex to dec, calculate rough compressed dwarf size
$ head -c 8257536 `which go` | tail -c 3037544 | gzip | wc -c
1048123
$ # find current binary size
$ wc -c `which go`
11836492
$ # calculate savings as a percent of current size
$ python -c "print(100*(3037544-1048123)/11836492.)"
16.807522026 I also double-checked that I'm interpreted fileoff and filesize correctly by using Note that this probably includes updating the dwarf stdlib to handle compression. See also #5158. |
I think debug/elf already supports compressed sections.
Could we compress other sections as well (and do decompression at runtime)?
The only segment we can't decompress at runtime is the code segment.
|
I took yet another stab at this, but I am having a hard time making friends with the linker, so I'm going to leave this for @aarzilli or @heschik. Here are some very useful implementation details from @ianlancetaylor, copied from #20463: DWARF compression is done at the file container level. There are two ways to do it. The older way is to prefix the DWARF section name with a 'z', as in '.zdebug_info'. The newer way, which applies to sections of all types, not just DWARF sections, is to set the Clearly Mach-O doesn't support the A
|
I've spent a little time digging into this and I'll leave some notes for myself or whoever comes next. The linker doesn't have the .debug_info section as a whole unit in memory at any point. This won't work for external linking, because the .debug_info section contains relocations to text symbols that will be placed by the external linker. New versions of GNU ld (I'm not sure when it was added exactly) support --compress-debug-sections to compress debug sections passed to. (gcc has another flag -gz, but presumably that's just for the individual CUs it's creating, not the whole section. That doesn't help with the Go CUs.) |
I don't think we should compress the debug info when doing external linking. The object file that we create is only going to live briefly, probably in /tmp, likely on a RAM disk. It will be deleted after the external linker runs. Compressing the debug info in that case will buy us nothing; we should leave it to the external linker. |
I agree, it's both impossible and undesirable :) I might work on this for 1.11 as part of getting DWARF location lists enabled by default, to offset the increase in binary size. |
What about providing the option to completely strip debug info? The |
@r04r That is a completely different problem that should be discussed on a different issue, not here. Thanks. |
@ianlancetaylor This issue seems to be about acquiring a "significant, cheap file size win.". Instead of compressing, just removing this information seems more significant, and cheaper. Is it viable? Should I make another issue? |
@r04r This issue is about a cheap file size win without losing any information. Removing the debug info produces a binary that can not be used with a debugger, so that is a different matter--not a win at all for people who want to use a debugger. Yes, if the linker's |
Marking as release-blocker for 1.11, since https://go-review.googlesource.com/c/go/+/100738 just went in and executable sizes are up >15%. |
Spent a little more time. More notes, adding to #11799 (comment). Revisiting it, I'm not totally sure where to put the compression any more. The linker calculates the output's layout in |
Other options:
|
To expand on @heschik's comment, perhaps the root of the problem here is that |
I'd like to stress that this really should happen for Go 1.11, as our binaries have gotten 20% bigger since Go 1.10 and that's not something users will be super happy about. |
I'll see if I can put together a fix for this, but it's definitely not an easy change. We'll see how it turns out. |
@aclements I'm also happy to look at this if you prefer. |
Change https://golang.org/cl/118276 mentions this issue: |
Change https://golang.org/cl/118277 mentions this issue: |
Currently these two forms of layout are done in a single pass. This makes it difficult to compress DWARF sections because that must be done after relocations are applied, which must happen after virtual address layout, but we can't layout the file until we've compressed the DWARF sections. Fix this by separating the two layout steps. In the process, we can also unify the copy-pasted code in Link.address to compute file offsets. Currently, each instance of this is slightly different, but there's no reason for it to be. For example, we don't perform PEFILEALIGN alignment on Segrodata or Selreltodata even when HeadType == Hwindows, but it turns out it doesn't matter whether you do or don't because these segments simply don't exist on Windows. Hence, in the unified code path, we do this alignment for all segments. Likewise, there are two ways of computing Fileoff: seg.Vaddr - prev.Vaddr + prev.Fileoff and prev.Fileoff + uint64(Rnd(int64(prev.Filelen), int64(*FlagRound))) At the moment, these always have the same value, but the latter will continue to work after we start compressing sections on disk. Tested by comparing test binaries for all packages in std before and after this change for GOOS={linux,windows,darwin,plan9}. All binaries are identical. For #11799. Change-Id: If09f28771bb4d78dd392fd58b8d7c9d5f22b0b9f Reviewed-on: https://go-review.googlesource.com/111682 Run-TryBot: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
bd83774 seems to be cause of issue #25863 |
Change https://golang.org/cl/118716 mentions this issue: |
Currently these two forms of layout are done in a single pass. This makes it difficult to compress DWARF sections because that must be done after relocations are applied, which must happen after virtual address layout, but we can't layout the file until we've compressed the DWARF sections. Fix this by separating the two layout steps. In the process, we can also unify the copy-pasted code in Link.address to compute file offsets. Currently, each instance of this is slightly different, but there's no reason for it to be. For example, we don't perform PEFILEALIGN alignment on Segrodata or Selreltodata even when HeadType == Hwindows, but it turns out it doesn't matter whether you do or don't because these segments simply don't exist on Windows. Hence, in the unified code path, we do this alignment for all segments. Likewise, there are two ways of computing Fileoff: seg.Vaddr - prev.Vaddr + prev.Fileoff and prev.Fileoff + uint64(Rnd(int64(prev.Filelen), int64(*FlagRound))) At the moment, these always have the same value, but the latter will continue to work after we start compressing sections on disk. Tested by comparing test binaries for all packages in std before and after this change for GOOS={linux,windows,darwin,plan9}. All binaries are identical. For golang#11799. Change-Id: If09f28771bb4d78dd392fd58b8d7c9d5f22b0b9f Reviewed-on: https://go-review.googlesource.com/111682 Run-TryBot: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Currently these two forms of layout are done in a single pass. This makes it difficult to compress DWARF sections because that must be done after relocations are applied, which must happen after virtual address layout, but we can't layout the file until we've compressed the DWARF sections. Fix this by separating the two layout steps. In the process, we can also unify the copy-pasted code in Link.address to compute file offsets, which currently has some unnecessary variation. Unlike the current file offset computation, which depends on virtual addresses, the new computation only uses file offsets and sizes. This will let us compress the file representation of a segment and create the file layout based on its on-disk size rather than its original in-memory size. Tested by comparing the test binary for the "strings" package on all supported GOOS/GOARCH combinations. All binaries are identical (except, of course, their build IDs). This is a second attempt at CL 111682. For #11799. Fixes #25863. Change-Id: If09f28771bb4d78dd392fd58b8d7c9d5f22b0b9e Reviewed-on: https://go-review.googlesource.com/118716 Run-TryBot: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Forked from CL 111895. For #11799. Change-Id: Ie1346ac2c9122de494823b9058df3a0971e9dfe1 Reviewed-on: https://go-review.googlesource.com/118277 Run-TryBot: Heschi Kreinick <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Austin Clements <[email protected]>
This should be left open until all platforms, not just ELF, do this. |
Then #25927 should be closed I believe. |
I think it's fine to leave #25927 open for the Windows-specific aspects of this. There's already been a decent amount of discussion there. For macOS, I'm concerned because my understanding is that lldb doesn't support compressed DWARF sections in Mach-O binaries (in fact, it didn't support them in ELF binaries until last month). And I believe dsymutil doesn't support them either (@heschik?) |
Well, dsymutil doesn't matter so much since we only use it during external linking, and we would need the host linker to do the compression in that case. But there's no Then there's the question of whether we want to. dwarfdump and lldb won't support it, but Delve could. So I suppose it's technically feasible as long as we're willing to leave lldb behind. I would want a flag so that I could use dwarfdump when necessary though. |
Change https://golang.org/cl/119816 mentions this issue: |
Change https://golang.org/cl/119815 mentions this issue: |
Simple follow-on to CL 118276. Everything worked except that the compressed sections need to be aligned at PEFILEALIGN. Fixes #25927 Updates #11799 Change-Id: Iec871defe30e3e66055d64a5ae77d5a7aca355f5 Reviewed-on: https://go-review.googlesource.com/119816 Run-TryBot: Heschi Kreinick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-by: Austin Clements <[email protected]>
Since we're going to start compressing DWARF on Windows and maybe Darwin, copy the ELF support for .zdebug sections to macho and pe. The code is almost completely the same across the three. While I was here I added support for compressed .debug_type sections, which I presume were overlooked before. Tests will come in a later CL once we can actually generate compressed PE/Mach-O binaries, since there's no other good way to get test data. Updates #25927, #11799 Change-Id: Ie920b6a16e9270bc3df214ce601a263837810376 Reviewed-on: https://go-review.googlesource.com/119815 Run-TryBot: Heschi Kreinick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-by: Austin Clements <[email protected]>
Change https://golang.org/cl/120155 mentions this issue: |
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]>
Here's the state of things as of that last commit. We now enable DWARF compression by default on almost all platforms. I didn't do anything for plan9, wasm, and nacl, but I'm not actually sure if they get debug information anyway. To debug a binary with DWARF compression, you'll need a modern version of GDB or a version of Delve after derekparker/delve@440b440 compiled with tip Go. Tip versions of lldb might work, but I haven't tested them. Compression can be disabled with I'm not aware of any significant remaining work to do here, so closing. Please comment if I missed something. |
Change https://golang.org/cl/138182 mentions this issue: |
Update #11799 Change-Id: I2646a52bfb8aecb67a664a7c6fba25511a1aa49f Reviewed-on: https://go-review.googlesource.com/138182 Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: David Chase <[email protected]>
Change https://golang.org/cl/138184 mentions this issue: |
Compressing our debug info might offer a significant, cheap file size win.
Things to do:
@ianlancetaylor commented off-list:
Related: #11773
The text was updated successfully, but these errors were encountered: