dwbug/elf: support old-style compressed DWARF

GCC and LLVM support zlib-compressing DWARF debug sections (and
there's some evidence that this may be happening by default in some
circumstances now).

Add support for reading compressed DWARF sections. Since ELF
relocations apply to the decompressed data, decompression is done
before applying relocations. Since relcations are applied by
debug/elf, decompression must also be handled there.

Note that this is different from compressed ELF sections, which is a
more general mechanism used by very recent versions of GCC.

Updates #11773.

Change-Id: I3f4bf1b04d0802cc1e8fcb7c2a5fcf6c467c5089
Reviewed-on: https://go-review.googlesource.com/17340
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/src/cmd/dist/deps.go b/src/cmd/dist/deps.go
index c15ed8d..9fd9817 100644
--- a/src/cmd/dist/deps.go
+++ b/src/cmd/dist/deps.go
@@ -5,11 +5,13 @@
 var builddeps = map[string][]string{
 	"bufio":                             {"bytes", "errors", "internal/race", "io", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sync", "sync/atomic", "unicode", "unicode/utf8"},
 	"bytes":                             {"errors", "internal/race", "io", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sync", "sync/atomic", "unicode", "unicode/utf8"},
+	"compress/flate":                    {"bufio", "bytes", "errors", "fmt", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
+	"compress/zlib":                     {"bufio", "bytes", "compress/flate", "errors", "fmt", "hash", "hash/adler32", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
 	"container/heap":                    {"runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort"},
 	"crypto":                            {"errors", "hash", "internal/race", "io", "math", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "strconv", "sync", "sync/atomic", "unicode/utf8"},
 	"crypto/sha1":                       {"crypto", "errors", "hash", "internal/race", "io", "math", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "strconv", "sync", "sync/atomic", "unicode/utf8"},
 	"debug/dwarf":                       {"encoding/binary", "errors", "fmt", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
-	"debug/elf":                         {"bytes", "debug/dwarf", "encoding/binary", "errors", "fmt", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
+	"debug/elf":                         {"bufio", "bytes", "compress/flate", "compress/zlib", "debug/dwarf", "encoding/binary", "errors", "fmt", "hash", "hash/adler32", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
 	"debug/macho":                       {"bytes", "debug/dwarf", "encoding/binary", "errors", "fmt", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
 	"encoding":                          {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
 	"encoding/base64":                   {"errors", "internal/race", "io", "math", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "strconv", "sync", "sync/atomic", "unicode/utf8"},
@@ -25,6 +27,7 @@
 	"go/scanner":                        {"bytes", "errors", "fmt", "go/token", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path/filepath", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
 	"go/token":                          {"errors", "fmt", "internal/race", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "sync", "sync/atomic", "syscall", "time", "unicode/utf16", "unicode/utf8"},
 	"hash":                              {"errors", "internal/race", "io", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sync", "sync/atomic"},
+	"hash/adler32":                      {"errors", "hash", "internal/race", "io", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sync", "sync/atomic"},
 	"internal/race":                     {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
 	"internal/singleflight":             {"internal/race", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sync", "sync/atomic"},
 	"internal/syscall/windows":          {"errors", "internal/race", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sync", "sync/atomic", "syscall", "unicode/utf16"},
@@ -57,5 +60,5 @@
 	"unicode":                 {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
 	"unicode/utf16":           {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
 	"unicode/utf8":            {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
-	"cmd/go":                  {"bufio", "bytes", "container/heap", "crypto", "crypto/sha1", "debug/dwarf", "debug/elf", "debug/macho", "encoding", "encoding/base64", "encoding/binary", "encoding/json", "errors", "flag", "fmt", "go/ast", "go/build", "go/doc", "go/parser", "go/scanner", "go/token", "hash", "internal/race", "internal/singleflight", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "io/ioutil", "log", "math", "net/url", "os", "os/exec", "os/signal", "path", "path/filepath", "reflect", "regexp", "regexp/syntax", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "text/template", "text/template/parse", "time", "unicode", "unicode/utf16", "unicode/utf8"},
+	"cmd/go":                  {"bufio", "bytes", "compress/flate", "compress/zlib", "container/heap", "crypto", "crypto/sha1", "debug/dwarf", "debug/elf", "debug/macho", "encoding", "encoding/base64", "encoding/binary", "encoding/json", "errors", "flag", "fmt", "go/ast", "go/build", "go/doc", "go/parser", "go/scanner", "go/token", "hash", "hash/adler32", "internal/race", "internal/singleflight", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "io/ioutil", "log", "math", "net/url", "os", "os/exec", "os/signal", "path", "path/filepath", "reflect", "regexp", "regexp/syntax", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "text/template", "text/template/parse", "time", "unicode", "unicode/utf16", "unicode/utf8"},
 }
diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go
index 3e766af..a42bde9 100644
--- a/src/debug/elf/file.go
+++ b/src/debug/elf/file.go
@@ -7,6 +7,7 @@
 
 import (
 	"bytes"
+	"compress/zlib"
 	"debug/dwarf"
 	"encoding/binary"
 	"errors"
@@ -863,6 +864,22 @@
 			return nil, err
 		}
 
+		if len(b) >= 12 && string(b[:4]) == "ZLIB" {
+			dlen := binary.BigEndian.Uint64(b[4:12])
+			dbuf := make([]byte, dlen)
+			r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
+			if err != nil {
+				return nil, err
+			}
+			if _, err := io.ReadFull(r, dbuf); err != nil {
+				return nil, err
+			}
+			if err := r.Close(); err != nil {
+				return nil, err
+			}
+			b = dbuf
+		}
+
 		for _, r := range f.Sections {
 			if r.Type != SHT_RELA && r.Type != SHT_REL {
 				continue
@@ -887,17 +904,23 @@
 	// Don't bother loading others.
 	var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil}
 	for i, s := range f.Sections {
-		if !strings.HasPrefix(s.Name, ".debug_") {
+		suffix := ""
+		switch {
+		case strings.HasPrefix(s.Name, ".debug_"):
+			suffix = s.Name[7:]
+		case strings.HasPrefix(s.Name, ".zdebug_"):
+			suffix = s.Name[8:]
+		default:
 			continue
 		}
-		if _, ok := dat[s.Name[7:]]; !ok {
+		if _, ok := dat[suffix]; !ok {
 			continue
 		}
 		b, err := sectionData(i, s)
 		if err != nil {
 			return nil, err
 		}
-		dat[s.Name[7:]] = b
+		dat[suffix] = b
 	}
 
 	d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, nil, dat["str"])
diff --git a/src/debug/elf/file_test.go b/src/debug/elf/file_test.go
index cd1a457..6864b6d 100644
--- a/src/debug/elf/file_test.go
+++ b/src/debug/elf/file_test.go
@@ -514,6 +514,34 @@
 	}
 }
 
+func TestCompressedDWARF(t *testing.T) {
+	// Test file built with GCC 4.8.4 and as 2.24 using:
+	// gcc -Wa,--compress-debug-sections -g -c -o zdebug-test-gcc484-x86-64.obj hello.c
+	f, err := Open("testdata/zdebug-test-gcc484-x86-64.obj")
+	if err != nil {
+		t.Fatal(err)
+	}
+	dwarf, err := f.DWARF()
+	if err != nil {
+		t.Fatal(err)
+	}
+	reader := dwarf.Reader()
+	n := 0
+	for {
+		entry, err := reader.Next()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if entry == nil {
+			break
+		}
+		n++
+	}
+	if n != 18 {
+		t.Fatalf("want %d DWARF entries, got %d", 18, n)
+	}
+}
+
 func TestNoSectionOverlaps(t *testing.T) {
 	// Ensure 6l outputs sections without overlaps.
 	if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
diff --git a/src/debug/elf/testdata/zdebug-test-gcc484-x86-64.obj b/src/debug/elf/testdata/zdebug-test-gcc484-x86-64.obj
new file mode 100644
index 0000000..a595a01
--- /dev/null
+++ b/src/debug/elf/testdata/zdebug-test-gcc484-x86-64.obj
Binary files differ
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 45144a2..b168938 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -218,7 +218,7 @@
 	"database/sql":             {"L4", "container/list", "database/sql/driver"},
 	"database/sql/driver":      {"L4", "time"},
 	"debug/dwarf":              {"L4"},
-	"debug/elf":                {"L4", "OS", "debug/dwarf"},
+	"debug/elf":                {"L4", "OS", "debug/dwarf", "compress/zlib"},
 	"debug/gosym":              {"L4"},
 	"debug/macho":              {"L4", "OS", "debug/dwarf"},
 	"debug/pe":                 {"L4", "OS", "debug/dwarf"},