diff options
| author | Russ Cox <rsc@golang.org> | 2017-03-06 20:39:57 -0500 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2017-03-08 01:09:18 +0000 |
| commit | c797256a8f7dcf24fdc798b1e0845d58aeeef7a2 (patch) | |
| tree | fb7f4097aa447976c6fb2076b888ec9854fafb17 /src/runtime | |
| parent | 752d7bad4fc6cb4c70edbe0b735ca89d7da16732 (diff) | |
| download | go-c797256a8f7dcf24fdc798b1e0845d58aeeef7a2.tar.xz | |
runtime/pprof: add GNU build IDs to Mappings recorded from /proc/self/maps
This helps systems that maintain an external database mapping
build ID to symbol information for the given binary, especially
in the case where /proc/self/maps lists many different files
(for example, many shared libraries).
Avoid importing debug/elf to avoid dragging in that whole
package (and its dependencies like debug/dwarf) into the
build of every program that generates a profile.
Fixes #19431.
Change-Id: I6d4362a79fe23e4f1726dffb0661d20bb57f766f
Reviewed-on: https://go-review.googlesource.com/37855
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/pprof/elf.go | 109 | ||||
| -rw-r--r-- | src/runtime/pprof/proto.go | 10 | ||||
| -rw-r--r-- | src/runtime/pprof/proto_test.go | 2 | ||||
| -rw-r--r-- | src/runtime/pprof/testdata/README | 9 | ||||
| -rwxr-xr-x | src/runtime/pprof/testdata/test32 | bin | 0 -> 528 bytes | |||
| -rwxr-xr-x | src/runtime/pprof/testdata/test32be | bin | 0 -> 520 bytes | |||
| -rwxr-xr-x | src/runtime/pprof/testdata/test64 | bin | 0 -> 760 bytes | |||
| -rwxr-xr-x | src/runtime/pprof/testdata/test64be | bin | 0 -> 856 bytes |
8 files changed, 126 insertions, 4 deletions
diff --git a/src/runtime/pprof/elf.go b/src/runtime/pprof/elf.go new file mode 100644 index 0000000000..a8b5ea6817 --- /dev/null +++ b/src/runtime/pprof/elf.go @@ -0,0 +1,109 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pprof + +import ( + "encoding/binary" + "errors" + "fmt" + "os" +) + +var ( + errBadELF = errors.New("malformed ELF binary") + errNoBuildID = errors.New("no NT_GNU_BUILD_ID found in ELF binary") +) + +// elfBuildID returns the GNU build ID of the named ELF binary, +// without introducing a dependency on debug/elf and its dependencies. +func elfBuildID(file string) (string, error) { + buf := make([]byte, 256) + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + + if _, err := f.ReadAt(buf[:64], 0); err != nil { + return "", err + } + + // ELF file begins with \x7F E L F. + if buf[0] != 0x7F || buf[1] != 'E' || buf[2] != 'L' || buf[3] != 'F' { + return "", errBadELF + } + + var byteOrder binary.ByteOrder + switch buf[5] { + default: + return "", errBadELF + case 1: // little-endian + byteOrder = binary.LittleEndian + case 2: // big-endian + byteOrder = binary.BigEndian + } + + var shnum int + var shoff, shentsize int64 + switch buf[4] { + default: + return "", errBadELF + case 1: // 32-bit file header + shoff = int64(byteOrder.Uint32(buf[32:])) + shentsize = int64(byteOrder.Uint16(buf[46:])) + if shentsize != 40 { + return "", errBadELF + } + shnum = int(byteOrder.Uint16(buf[48:])) + case 2: // 64-bit file header + shoff = int64(byteOrder.Uint64(buf[40:])) + shentsize = int64(byteOrder.Uint16(buf[58:])) + if shentsize != 64 { + return "", errBadELF + } + shnum = int(byteOrder.Uint16(buf[60:])) + } + + for i := 0; i < shnum; i++ { + if _, err := f.ReadAt(buf[:shentsize], shoff+int64(i)*shentsize); err != nil { + return "", err + } + if typ := byteOrder.Uint32(buf[4:]); typ != 7 { // SHT_NOTE + continue + } + var off, size int64 + if shentsize == 40 { + // 32-bit section header + off = int64(byteOrder.Uint32(buf[16:])) + size = int64(byteOrder.Uint32(buf[20:])) + } else { + // 64-bit section header + off = int64(byteOrder.Uint64(buf[24:])) + size = int64(byteOrder.Uint64(buf[32:])) + } + size += off + for off < size { + if _, err := f.ReadAt(buf[:16], off); err != nil { // room for header + name GNU\x00 + return "", err + } + nameSize := int(byteOrder.Uint32(buf[0:])) + descSize := int(byteOrder.Uint32(buf[4:])) + noteType := int(byteOrder.Uint32(buf[8:])) + descOff := off + int64(12+(nameSize+3)&^3) + off = descOff + int64((descSize+3)&^3) + if nameSize != 4 || noteType != 3 || buf[12] != 'G' || buf[13] != 'N' || buf[14] != 'U' || buf[15] != '\x00' { // want name GNU\x00 type 3 (NT_GNU_BUILD_ID) + continue + } + if descSize > len(buf) { + return "", errBadELF + } + if _, err := f.ReadAt(buf[:descSize], descOff); err != nil { + return "", err + } + return fmt.Sprintf("%x", buf[:descSize]), nil + } + } + return "", errNoBuildID +} diff --git a/src/runtime/pprof/proto.go b/src/runtime/pprof/proto.go index dd3d5c3b0b..0f74e119b6 100644 --- a/src/runtime/pprof/proto.go +++ b/src/runtime/pprof/proto.go @@ -169,13 +169,14 @@ func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) { } // pbMapping encodes a Mapping message to b.pb. -func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file string) { +func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string) { start := b.pb.startMessage() b.pb.uint64Opt(tagMapping_ID, id) b.pb.uint64Opt(tagMapping_Start, base) b.pb.uint64Opt(tagMapping_Limit, limit) b.pb.uint64Opt(tagMapping_Offset, offset) b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file)) + b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID)) // TODO: Set any of HasInlineFrames, HasFunctions, HasFilenames, HasLineNumbers? // It seems like they should all be true, but they've never been set. b.pb.endMessage(tag, start) @@ -438,10 +439,10 @@ func (b *profileBuilder) readMapping() { } next() // dev next() // inode - file := line - if file == nil { + if line == nil { continue } + file := string(line) // TODO: pprof's remapMappingIDs makes two adjustments: // 1. If there is an /anon_hugepage mapping first and it is @@ -452,7 +453,8 @@ func (b *profileBuilder) readMapping() { // If we do need them, they would go here, before we // enter the mappings into b.mem in the first place. + buildID, _ := elfBuildID(file) b.mem = append(b.mem, memMap{uintptr(lo), uintptr(hi)}) - b.pbMapping(tagProfile_Mapping, uint64(len(b.mem)), lo, hi, offset, string(file)) + b.pbMapping(tagProfile_Mapping, uint64(len(b.mem)), lo, hi, offset, file, buildID) } } diff --git a/src/runtime/pprof/proto_test.go b/src/runtime/pprof/proto_test.go index 98f217583b..7b0fa95d71 100644 --- a/src/runtime/pprof/proto_test.go +++ b/src/runtime/pprof/proto_test.go @@ -87,8 +87,10 @@ func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) { } addr1 = mprof.Mapping[0].Start map1 = mprof.Mapping[0] + map1.BuildID, _ = elfBuildID(map1.File) addr2 = mprof.Mapping[1].Start map2 = mprof.Mapping[1] + map2.BuildID, _ = elfBuildID(map2.File) } else { addr1 = uint64(funcPC(f1)) addr2 = uint64(funcPC(f2)) diff --git a/src/runtime/pprof/testdata/README b/src/runtime/pprof/testdata/README new file mode 100644 index 0000000000..876538e5d5 --- /dev/null +++ b/src/runtime/pprof/testdata/README @@ -0,0 +1,9 @@ +These binaries were generated by: + +$ cat empty.s +.global _start +_start: +$ as --32 -o empty.o empty.s && ld --build-id -m elf_i386 -o test32 empty.o +$ as --64 -o empty.o empty.s && ld --build-id -o test64 empty.o +$ powerpc-linux-gnu-as -o empty.o empty.s && powerpc-linux-gnu-ld --build-id -o test32be empty.o +$ powerpc64-linux-gnu-as -o empty.o empty.s && powerpc64-linux-gnu-ld --build-id -o test64be empty.o diff --git a/src/runtime/pprof/testdata/test32 b/src/runtime/pprof/testdata/test32 Binary files differnew file mode 100755 index 0000000000..ce594720db --- /dev/null +++ b/src/runtime/pprof/testdata/test32 diff --git a/src/runtime/pprof/testdata/test32be b/src/runtime/pprof/testdata/test32be Binary files differnew file mode 100755 index 0000000000..f13a732203 --- /dev/null +++ b/src/runtime/pprof/testdata/test32be diff --git a/src/runtime/pprof/testdata/test64 b/src/runtime/pprof/testdata/test64 Binary files differnew file mode 100755 index 0000000000..3fb42fb3bf --- /dev/null +++ b/src/runtime/pprof/testdata/test64 diff --git a/src/runtime/pprof/testdata/test64be b/src/runtime/pprof/testdata/test64be Binary files differnew file mode 100755 index 0000000000..09b4b01894 --- /dev/null +++ b/src/runtime/pprof/testdata/test64be |
