Commit 594eae5a authored by Heschi Kreinick's avatar Heschi Kreinick

cmd/link: compress DWARF sections in ELF binaries

Forked from CL 111895.

The trickiest part of this is that the binary layout code (blk,
elfshbits, and various other things) assumes a constant offset between
symbols' and sections' file locations and their virtual addresses.
Compression, of course, breaks this constant offset. But we need to
assign virtual addresses to everything before compression in order to
resolve relocations before compression. As a result, compression needs
to re-compute the "address" of the DWARF sections and symbols based on
their compressed size. Luckily, these are at the end of the file, so
this doesn't perturb any other sections or symbols. (And there is, of
course, a surprising amount of code that assumes the DWARF segment
comes last, so what's one more place?)

Relevant benchmarks:
name        old time/op     new time/op     delta
StdCmd          10.3s ± 2%      10.8s ± 1%   +5.43%  (p=0.000 n=30+30)

name        old text-bytes  new text-bytes  delta
HelloSize       746kB ± 0%      746kB ± 0%     ~     (all equal)
CmdGoSize      8.41MB ± 0%     8.41MB ± 0%     ~     (all equal)
[Geo mean]     2.50MB          2.50MB        +0.00%

name        old data-bytes  new data-bytes  delta
HelloSize      10.6kB ± 0%     10.6kB ± 0%     ~     (all equal)
CmdGoSize       252kB ± 0%      252kB ± 0%     ~     (all equal)
[Geo mean]     51.5kB          51.5kB        +0.00%

name        old bss-bytes   new bss-bytes   delta
HelloSize       125kB ± 0%      125kB ± 0%     ~     (all equal)
CmdGoSize       145kB ± 0%      145kB ± 0%     ~     (all equal)
[Geo mean]      135kB           135kB        +0.00%

name        old exe-bytes   new exe-bytes   delta
HelloSize      1.60MB ± 0%     1.05MB ± 0%  -34.39%  (p=0.000 n=30+30)
CmdGoSize      16.5MB ± 0%     11.3MB ± 0%  -31.76%  (p=0.000 n=30+30)
[Geo mean]     5.14MB          3.44MB       -33.08%

Fixes #11799.
Updates #6853.

Change-Id: I64197afe4c01a237523a943088051ee056331c6f
Reviewed-on: https://go-review.googlesource.com/118276
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent b397f633
...@@ -80,6 +80,8 @@ var bootstrapDirs = []string{ ...@@ -80,6 +80,8 @@ var bootstrapDirs = []string{
"cmd/link/internal/s390x", "cmd/link/internal/s390x",
"cmd/link/internal/sym", "cmd/link/internal/sym",
"cmd/link/internal/x86", "cmd/link/internal/x86",
"compress/flate",
"compress/zlib",
"cmd/link/internal/wasm", "cmd/link/internal/wasm",
"container/heap", "container/heap",
"debug/dwarf", "debug/dwarf",
......
...@@ -32,10 +32,13 @@ ...@@ -32,10 +32,13 @@
package ld package ld
import ( import (
"bytes"
"cmd/internal/gcprog" "cmd/internal/gcprog"
"cmd/internal/objabi" "cmd/internal/objabi"
"cmd/internal/sys" "cmd/internal/sys"
"cmd/link/internal/sym" "cmd/link/internal/sym"
"compress/zlib"
"encoding/binary"
"fmt" "fmt"
"log" "log"
"os" "os"
...@@ -679,6 +682,10 @@ func blk(ctxt *Link, syms []*sym.Symbol, addr, size int64, pad []byte) { ...@@ -679,6 +682,10 @@ func blk(ctxt *Link, syms []*sym.Symbol, addr, size int64, pad []byte) {
} }
} }
// This doesn't distinguish the memory size from the file
// size, and it lays out the file based on Symbol.Value, which
// is the virtual address. DWARF compression changes file sizes,
// so dwarfcompress will fix this up later if necessary.
eaddr := addr + size eaddr := addr + size
for _, s := range syms { for _, s := range syms {
if s.Attr.SubSymbol() { if s.Attr.SubSymbol() {
...@@ -2154,3 +2161,44 @@ func (ctxt *Link) AddTramp(s *sym.Symbol) { ...@@ -2154,3 +2161,44 @@ func (ctxt *Link) AddTramp(s *sym.Symbol) {
ctxt.Logf("trampoline %s inserted\n", s) ctxt.Logf("trampoline %s inserted\n", s)
} }
} }
// compressSyms compresses syms and returns the contents of the
// compressed section. If the section would get larger, it returns nil.
func compressSyms(ctxt *Link, syms []*sym.Symbol) []byte {
var total int64
for _, sym := range syms {
total += sym.Size
}
var buf bytes.Buffer
buf.Write([]byte("ZLIB"))
var sizeBytes [8]byte
binary.BigEndian.PutUint64(sizeBytes[:], uint64(total))
buf.Write(sizeBytes[:])
z := zlib.NewWriter(&buf)
for _, sym := range syms {
if _, err := z.Write(sym.P); err != nil {
log.Fatalf("compression failed: %s", err)
}
for i := sym.Size - int64(len(sym.P)); i > 0; {
b := zeros[:]
if i < int64(len(b)) {
b = b[:i]
}
n, err := z.Write(b)
if err != nil {
log.Fatalf("compression failed: %s", err)
}
i -= int64(n)
}
}
if err := z.Close(); err != nil {
log.Fatalf("compression failed: %s", err)
}
if int64(buf.Len()) >= total {
// Compression didn't save any space.
return nil
}
return buf.Bytes()
}
...@@ -1908,23 +1908,14 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *sym.Symbol) { ...@@ -1908,23 +1908,14 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *sym.Symbol) {
return return
} }
Addstring(shstrtab, ".debug_abbrev") secs := []string{"abbrev", "frame", "info", "loc", "line", "pubnames", "pubtypes", "gdb_scripts", "ranges"}
Addstring(shstrtab, ".debug_frame") for _, sec := range secs {
Addstring(shstrtab, ".debug_info") Addstring(shstrtab, ".debug_"+sec)
Addstring(shstrtab, ".debug_loc") if ctxt.LinkMode == LinkExternal {
Addstring(shstrtab, ".debug_line") Addstring(shstrtab, elfRelType+".debug_"+sec)
Addstring(shstrtab, ".debug_pubnames") } else {
Addstring(shstrtab, ".debug_pubtypes") Addstring(shstrtab, ".zdebug_"+sec)
Addstring(shstrtab, ".debug_gdb_scripts") }
Addstring(shstrtab, ".debug_ranges")
if ctxt.LinkMode == LinkExternal {
Addstring(shstrtab, elfRelType+".debug_info")
Addstring(shstrtab, elfRelType+".debug_loc")
Addstring(shstrtab, elfRelType+".debug_line")
Addstring(shstrtab, elfRelType+".debug_frame")
Addstring(shstrtab, elfRelType+".debug_pubnames")
Addstring(shstrtab, elfRelType+".debug_pubtypes")
Addstring(shstrtab, elfRelType+".debug_ranges")
} }
} }
...@@ -1937,6 +1928,7 @@ func dwarfaddelfsectionsyms(ctxt *Link) { ...@@ -1937,6 +1928,7 @@ func dwarfaddelfsectionsyms(ctxt *Link) {
if ctxt.LinkMode != LinkExternal { if ctxt.LinkMode != LinkExternal {
return return
} }
s := ctxt.Syms.Lookup(".debug_info", 0) s := ctxt.Syms.Lookup(".debug_info", 0)
putelfsectionsym(ctxt.Out, s, s.Sect.Elfsect.(*ElfShdr).shnum) putelfsectionsym(ctxt.Out, s, s.Sect.Elfsect.(*ElfShdr).shnum)
s = ctxt.Syms.Lookup(".debug_abbrev", 0) s = ctxt.Syms.Lookup(".debug_abbrev", 0)
...@@ -1954,3 +1946,58 @@ func dwarfaddelfsectionsyms(ctxt *Link) { ...@@ -1954,3 +1946,58 @@ func dwarfaddelfsectionsyms(ctxt *Link) {
putelfsectionsym(ctxt.Out, s, s.Sect.Elfsect.(*ElfShdr).shnum) putelfsectionsym(ctxt.Out, s, s.Sect.Elfsect.(*ElfShdr).shnum)
} }
} }
// dwarfcompress compresses the DWARF sections. This must happen after
// relocations are applied. After this, dwarfp will contain a
// different (new) set of symbols, and sections may have been replaced.
func dwarfcompress(ctxt *Link) {
if !ctxt.IsELF || ctxt.LinkMode == LinkExternal {
return
}
var start int
var newDwarfp []*sym.Symbol
Segdwarf.Sections = Segdwarf.Sections[:0]
for i, s := range dwarfp {
// Find the boundaries between sections and compress
// the whole section once we've found the last of its
// symbols.
if i+1 >= len(dwarfp) || s.Sect != dwarfp[i+1].Sect {
s1 := compressSyms(ctxt, dwarfp[start:i+1])
if s1 == nil {
// Compression didn't help.
newDwarfp = append(newDwarfp, dwarfp[start:i+1]...)
Segdwarf.Sections = append(Segdwarf.Sections, s.Sect)
} else {
compressedSegName := ".zdebug_" + s.Sect.Name[len(".debug_"):]
sect := addsection(ctxt.Arch, &Segdwarf, compressedSegName, 04)
sect.Length = uint64(len(s1))
newSym := ctxt.Syms.Lookup(compressedSegName, 0)
newSym.P = s1
newSym.Size = int64(len(s1))
newSym.Sect = sect
newDwarfp = append(newDwarfp, newSym)
}
start = i + 1
}
}
dwarfp = newDwarfp
// Re-compute the locations of the compressed DWARF symbols
// and sections, since the layout of these within the file is
// based on Section.Vaddr and Symbol.Value.
pos := Segdwarf.Vaddr
var prevSect *sym.Section
for _, s := range dwarfp {
s.Value = int64(pos)
if s.Sect != prevSect {
s.Sect.Vaddr = uint64(s.Value)
prevSect = s.Sect
}
if s.Sub != nil {
log.Fatalf("%s: unexpected sub-symbols", s)
}
pos += uint64(s.Size)
}
Segdwarf.Length = pos - Segdwarf.Vaddr
}
...@@ -1261,7 +1261,7 @@ func elfshbits(linkmode LinkMode, sect *sym.Section) *ElfShdr { ...@@ -1261,7 +1261,7 @@ func elfshbits(linkmode LinkMode, sect *sym.Section) *ElfShdr {
sh.flags |= SHF_TLS sh.flags |= SHF_TLS
sh.type_ = SHT_NOBITS sh.type_ = SHT_NOBITS
} }
if strings.HasPrefix(sect.Name, ".debug") { if strings.HasPrefix(sect.Name, ".debug") || strings.HasPrefix(sect.Name, ".zdebug") {
sh.flags = 0 sh.flags = 0
} }
......
...@@ -226,6 +226,7 @@ func Main(arch *sys.Arch, theArch Arch) { ...@@ -226,6 +226,7 @@ func Main(arch *sys.Arch, theArch Arch) {
ctxt.dodata() ctxt.dodata()
order := ctxt.address() order := ctxt.address()
ctxt.reloc() ctxt.reloc()
dwarfcompress(ctxt)
ctxt.layout(order) ctxt.layout(order)
thearch.Asmb(ctxt) thearch.Asmb(ctxt)
ctxt.undef() ctxt.undef()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment