diff options
| author | Raul Silvera <rsilvera@google.com> | 2017-02-10 14:52:02 -0800 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2017-02-24 19:18:53 +0000 |
| commit | 7844ef427a61bd68db81912a254a3f99633a9354 (patch) | |
| tree | 6d2be738f8c2cecdfc90ee43fba835ae7ce79d52 /src/cmd/vendor/github.com/google/pprof/internal/binutils | |
| parent | 2818cb5c9e183aed539d6a539a821e229671fe56 (diff) | |
| download | go-7844ef427a61bd68db81912a254a3f99633a9354.tar.xz | |
cmd/pprof: vendor pprof from github.com/google/pprof
Import the github.com/google/pprof and github.com/ianlancetaylor/demangle
packages, without modification.
Build the golang version of pprof from cmd/pprof/pprof.go
by importing the packages from src/cmd/vendot/github.com/google/pprof
The versions upstreamed are:
github.com/ianlancetaylor/demangle 4883227f66371e02c4948937d3e2be1664d9be38
github.com/google/pprof 7eb5ba977f28f2ad8dd5f6bb82cc9b454e123cdc
Update misc/nacl/testzip.proto for new tests.
Change-Id: I076584856491353607a3b98b67d0ca6838be50d6
Reviewed-on: https://go-review.googlesource.com/36798
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Diffstat (limited to 'src/cmd/vendor/github.com/google/pprof/internal/binutils')
7 files changed, 1270 insertions, 0 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go new file mode 100644 index 0000000000..6b9e6abb22 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go @@ -0,0 +1,219 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binutils + +import ( + "bufio" + "fmt" + "io" + "os/exec" + "strconv" + "strings" + + "github.com/google/pprof/internal/plugin" +) + +const ( + defaultAddr2line = "addr2line" + + // addr2line may produce multiple lines of output. We + // use this sentinel to identify the end of the output. + sentinel = ^uint64(0) +) + +// addr2Liner is a connection to an addr2line command for obtaining +// address and line number information from a binary. +type addr2Liner struct { + rw lineReaderWriter + base uint64 + + // nm holds an NM based addr2Liner which can provide + // better full names compared to addr2line, which often drops + // namespaces etc. from the names it returns. + nm *addr2LinerNM +} + +// lineReaderWriter is an interface to abstract the I/O to an addr2line +// process. It writes a line of input to the job, and reads its output +// one line at a time. +type lineReaderWriter interface { + write(string) error + readLine() (string, error) + close() +} + +type addr2LinerJob struct { + cmd *exec.Cmd + in io.WriteCloser + out *bufio.Reader +} + +func (a *addr2LinerJob) write(s string) error { + _, err := fmt.Fprint(a.in, s+"\n") + return err +} + +func (a *addr2LinerJob) readLine() (string, error) { + return a.out.ReadString('\n') +} + +// close releases any resources used by the addr2liner object. +func (a *addr2LinerJob) close() { + a.in.Close() + a.cmd.Wait() +} + +// newAddr2liner starts the given addr2liner command reporting +// information about the given executable file. If file is a shared +// library, base should be the address at which it was mapped in the +// program under consideration. +func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) { + if cmd == "" { + cmd = defaultAddr2line + } + + j := &addr2LinerJob{ + cmd: exec.Command(cmd, "-aif", "-e", file), + } + + var err error + if j.in, err = j.cmd.StdinPipe(); err != nil { + return nil, err + } + + outPipe, err := j.cmd.StdoutPipe() + if err != nil { + return nil, err + } + + j.out = bufio.NewReader(outPipe) + if err := j.cmd.Start(); err != nil { + return nil, err + } + + a := &addr2Liner{ + rw: j, + base: base, + } + + return a, nil +} + +func (d *addr2Liner) readString() (string, error) { + s, err := d.rw.readLine() + if err != nil { + return "", err + } + return strings.TrimSpace(s), nil +} + +// readFrame parses the addr2line output for a single address. It +// returns a populated plugin.Frame and whether it has reached the end of the +// data. +func (d *addr2Liner) readFrame() (plugin.Frame, bool) { + funcname, err := d.readString() + if err != nil { + return plugin.Frame{}, true + } + if strings.HasPrefix(funcname, "0x") { + // If addr2line returns a hex address we can assume it is the + // sentinel. Read and ignore next two lines of output from + // addr2line + d.readString() + d.readString() + return plugin.Frame{}, true + } + + fileline, err := d.readString() + if err != nil { + return plugin.Frame{}, true + } + + linenumber := 0 + + if funcname == "??" { + funcname = "" + } + + if fileline == "??:0" { + fileline = "" + } else { + if i := strings.LastIndex(fileline, ":"); i >= 0 { + // Remove discriminator, if present + if disc := strings.Index(fileline, " (discriminator"); disc > 0 { + fileline = fileline[:disc] + } + // If we cannot parse a number after the last ":", keep it as + // part of the filename. + if line, err := strconv.Atoi(fileline[i+1:]); err == nil { + linenumber = line + fileline = fileline[:i] + } + } + } + + return plugin.Frame{funcname, fileline, linenumber}, false +} + +// addrInfo returns the stack frame information for a specific program +// address. It returns nil if the address could not be identified. +func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) { + if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil { + return nil, err + } + + if err := d.rw.write(fmt.Sprintf("%x", sentinel)); err != nil { + return nil, err + } + + resp, err := d.readString() + if err != nil { + return nil, err + } + + if !strings.HasPrefix(resp, "0x") { + return nil, fmt.Errorf("unexpected addr2line output: %s", resp) + } + + var stack []plugin.Frame + for { + frame, end := d.readFrame() + if end { + break + } + + if frame != (plugin.Frame{}) { + stack = append(stack, frame) + } + } + + // Get better name from nm if possible. + if len(stack) > 0 && d.nm != nil { + nm, err := d.nm.addrInfo(addr) + if err == nil && len(nm) > 0 { + // Last entry in frame list should match since + // it is non-inlined. As a simple heuristic, + // we only switch to the nm-based name if it + // is longer. + nmName := nm[len(nm)-1].Func + a2lName := stack[len(stack)-1].Func + if len(nmName) > len(a2lName) { + stack[len(stack)-1].Func = nmName + } + } + } + + return stack, nil +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go new file mode 100644 index 0000000000..17ff5fd836 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go @@ -0,0 +1,170 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binutils + +import ( + "bufio" + "fmt" + "io" + "os/exec" + "strconv" + "strings" + + "github.com/google/pprof/internal/plugin" +) + +const ( + defaultLLVMSymbolizer = "llvm-symbolizer" +) + +// llvmSymbolizer is a connection to an llvm-symbolizer command for +// obtaining address and line number information from a binary. +type llvmSymbolizer struct { + filename string + rw lineReaderWriter + base uint64 +} + +type llvmSymbolizerJob struct { + cmd *exec.Cmd + in io.WriteCloser + out *bufio.Reader +} + +func (a *llvmSymbolizerJob) write(s string) error { + _, err := fmt.Fprint(a.in, s+"\n") + return err +} + +func (a *llvmSymbolizerJob) readLine() (string, error) { + return a.out.ReadString('\n') +} + +// close releases any resources used by the llvmSymbolizer object. +func (a *llvmSymbolizerJob) close() { + a.in.Close() + a.cmd.Wait() +} + +// newLlvmSymbolizer starts the given llvmSymbolizer command reporting +// information about the given executable file. If file is a shared +// library, base should be the address at which it was mapped in the +// program under consideration. +func newLLVMSymbolizer(cmd, file string, base uint64) (*llvmSymbolizer, error) { + if cmd == "" { + cmd = defaultLLVMSymbolizer + } + + j := &llvmSymbolizerJob{ + cmd: exec.Command(cmd, "-inlining", "-demangle=false"), + } + + var err error + if j.in, err = j.cmd.StdinPipe(); err != nil { + return nil, err + } + + outPipe, err := j.cmd.StdoutPipe() + if err != nil { + return nil, err + } + + j.out = bufio.NewReader(outPipe) + if err := j.cmd.Start(); err != nil { + return nil, err + } + + a := &llvmSymbolizer{ + filename: file, + rw: j, + base: base, + } + + return a, nil +} + +func (d *llvmSymbolizer) readString() (string, error) { + s, err := d.rw.readLine() + if err != nil { + return "", err + } + return strings.TrimSpace(s), nil +} + +// readFrame parses the llvm-symbolizer output for a single address. It +// returns a populated plugin.Frame and whether it has reached the end of the +// data. +func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) { + funcname, err := d.readString() + if err != nil { + return plugin.Frame{}, true + } + + switch funcname { + case "": + return plugin.Frame{}, true + case "??": + funcname = "" + } + + fileline, err := d.readString() + if err != nil { + return plugin.Frame{funcname, "", 0}, true + } + + linenumber := 0 + if fileline == "??:0" { + fileline = "" + } else { + switch split := strings.Split(fileline, ":"); len(split) { + case 1: + // filename + fileline = split[0] + case 2, 3: + // filename:line , or + // filename:line:disc , or + fileline = split[0] + if line, err := strconv.Atoi(split[1]); err == nil { + linenumber = line + } + default: + // Unrecognized, ignore + } + } + + return plugin.Frame{funcname, fileline, linenumber}, false +} + +// addrInfo returns the stack frame information for a specific program +// address. It returns nil if the address could not be identified. +func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) { + if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil { + return nil, err + } + + var stack []plugin.Frame + for { + frame, end := d.readFrame() + if end { + break + } + + if frame != (plugin.Frame{}) { + stack = append(stack, frame) + } + } + + return stack, nil +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go new file mode 100644 index 0000000000..e7a8e10b34 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_nm.go @@ -0,0 +1,123 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binutils + +import ( + "bufio" + "bytes" + "io" + "os/exec" + "strconv" + "strings" + + "github.com/google/pprof/internal/plugin" +) + +const ( + defaultNM = "nm" +) + +// addr2LinerNM is a connection to an nm command for obtaining address +// information from a binary. +type addr2LinerNM struct { + m []symbolInfo // Sorted list of addresses from binary. +} + +type symbolInfo struct { + address uint64 + name string +} + +// newAddr2LinerNM starts the given nm command reporting information about the +// given executable file. If file is a shared library, base should be +// the address at which it was mapped in the program under +// consideration. +func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) { + if cmd == "" { + cmd = defaultNM + } + + a := &addr2LinerNM{ + m: []symbolInfo{}, + } + + var b bytes.Buffer + c := exec.Command(cmd, "-n", file) + c.Stdout = &b + + if err := c.Run(); err != nil { + return nil, err + } + + // Parse nm output and populate symbol map. + // Skip lines we fail to parse. + buf := bufio.NewReader(&b) + for { + line, err := buf.ReadString('\n') + if line == "" && err != nil { + if err == io.EOF { + break + } + return nil, err + } + line = strings.TrimSpace(line) + fields := strings.SplitN(line, " ", 3) + if len(fields) != 3 { + continue + } + address, err := strconv.ParseUint(fields[0], 16, 64) + if err != nil { + continue + } + a.m = append(a.m, symbolInfo{ + address: address + base, + name: fields[2], + }) + } + + return a, nil +} + +// addrInfo returns the stack frame information for a specific program +// address. It returns nil if the address could not be identified. +func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) { + if len(a.m) == 0 || addr < a.m[0].address || addr > a.m[len(a.m)-1].address { + return nil, nil + } + + // Binary search. Search until low, high are separated by 1. + low, high := 0, len(a.m) + for low+1 < high { + mid := (low + high) / 2 + v := a.m[mid].address + if addr == v { + low = mid + break + } else if addr > v { + low = mid + } else { + high = mid + } + } + + // Address is between a.m[low] and a.m[high]. + // Pick low, as it represents [low, high). + f := []plugin.Frame{ + { + Func: a.m[low].name, + }, + } + return f, nil +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go new file mode 100644 index 0000000000..9854c9a262 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go @@ -0,0 +1,305 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package binutils provides access to the GNU binutils. +package binutils + +import ( + "debug/elf" + "debug/macho" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/google/pprof/internal/elfexec" + "github.com/google/pprof/internal/plugin" +) + +// A Binutils implements plugin.ObjTool by invoking the GNU binutils. +// SetConfig must be called before any of the other methods. +type Binutils struct { + // Commands to invoke. + llvmSymbolizer string + llvmSymbolizerFound bool + addr2line string + addr2lineFound bool + nm string + nmFound bool + objdump string + objdumpFound bool + + // if fast, perform symbolization using nm (symbol names only), + // instead of file-line detail from the slower addr2line. + fast bool +} + +// SetFastSymbolization sets a toggle that makes binutils use fast +// symbolization (using nm), which is much faster than addr2line but +// provides only symbol name information (no file/line). +func (b *Binutils) SetFastSymbolization(fast bool) { + b.fast = fast +} + +// SetTools processes the contents of the tools option. It +// expects a set of entries separated by commas; each entry is a pair +// of the form t:path, where cmd will be used to look only for the +// tool named t. If t is not specified, the path is searched for all +// tools. +func (b *Binutils) SetTools(config string) { + // paths collect paths per tool; Key "" contains the default. + paths := make(map[string][]string) + for _, t := range strings.Split(config, ",") { + name, path := "", t + if ct := strings.SplitN(t, ":", 2); len(ct) == 2 { + name, path = ct[0], ct[1] + } + paths[name] = append(paths[name], path) + } + + defaultPath := paths[""] + b.llvmSymbolizer, b.llvmSymbolizerFound = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...)) + b.addr2line, b.addr2lineFound = findExe("addr2line", append(paths["addr2line"], defaultPath...)) + b.nm, b.nmFound = findExe("nm", append(paths["nm"], defaultPath...)) + b.objdump, b.objdumpFound = findExe("objdump", append(paths["objdump"], defaultPath...)) +} + +// findExe looks for an executable command on a set of paths. +// If it cannot find it, returns cmd. +func findExe(cmd string, paths []string) (string, bool) { + for _, p := range paths { + cp := filepath.Join(p, cmd) + if c, err := exec.LookPath(cp); err == nil { + return c, true + } + } + return cmd, false +} + +// Disasm returns the assembly instructions for the specified address range +// of a binary. +func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { + if b.addr2line == "" { + // Update the command invocations if not initialized. + b.SetTools("") + } + cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l", + fmt.Sprintf("--start-address=%#x", start), + fmt.Sprintf("--stop-address=%#x", end), + file) + out, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("%v: %v", cmd.Args, err) + } + + return disassemble(out) +} + +// Open satisfies the plugin.ObjTool interface. +func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) { + if b.addr2line == "" { + // Update the command invocations if not initialized. + b.SetTools("") + } + + // Make sure file is a supported executable. + // The pprof driver uses Open to sniff the difference + // between an executable and a profile. + // For now, only ELF is supported. + // Could read the first few bytes of the file and + // use a table of prefixes if we need to support other + // systems at some point. + + if _, err := os.Stat(name); err != nil { + // For testing, do not require file name to exist. + if strings.Contains(b.addr2line, "testdata/") { + return &fileAddr2Line{file: file{b: b, name: name}}, nil + } + return nil, err + } + + if f, err := b.openELF(name, start, limit, offset); err == nil { + return f, nil + } + if f, err := b.openMachO(name, start, limit, offset); err == nil { + return f, nil + } + return nil, fmt.Errorf("unrecognized binary: %s", name) +} + +func (b *Binutils) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) { + of, err := macho.Open(name) + if err != nil { + return nil, fmt.Errorf("Parsing %s: %v", name, err) + } + defer of.Close() + + if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { + return &fileNM{file: file{b: b, name: name}}, nil + } + return &fileAddr2Line{file: file{b: b, name: name}}, nil +} + +func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) { + ef, err := elf.Open(name) + if err != nil { + return nil, fmt.Errorf("Parsing %s: %v", name, err) + } + defer ef.Close() + + var stextOffset *uint64 + var pageAligned = func(addr uint64) bool { return addr%4096 == 0 } + if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) { + // Reading all Symbols is expensive, and we only rarely need it so + // we don't want to do it every time. But if _stext happens to be + // page-aligned but isn't the same as Vaddr, we would symbolize + // wrong. So if the name the addresses aren't page aligned, or if + // the name is "vmlinux" we read _stext. We can be wrong if: (1) + // someone passes a kernel path that doesn't contain "vmlinux" AND + // (2) _stext is page-aligned AND (3) _stext is not at Vaddr + symbols, err := ef.Symbols() + if err != nil { + return nil, err + } + for _, s := range symbols { + if s.Name == "_stext" { + // The kernel may use _stext as the mapping start address. + stextOffset = &s.Value + break + } + } + } + + base, err := elfexec.GetBase(&ef.FileHeader, nil, stextOffset, start, limit, offset) + if err != nil { + return nil, fmt.Errorf("Could not identify base for %s: %v", name, err) + } + + buildID := "" + if f, err := os.Open(name); err == nil { + if id, err := elfexec.GetBuildID(f); err == nil { + buildID = fmt.Sprintf("%x", id) + } + } + if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { + return &fileNM{file: file{b, name, base, buildID}}, nil + } + return &fileAddr2Line{file: file{b, name, base, buildID}}, nil +} + +// file implements the binutils.ObjFile interface. +type file struct { + b *Binutils + name string + base uint64 + buildID string +} + +func (f *file) Name() string { + return f.name +} + +func (f *file) Base() uint64 { + return f.base +} + +func (f *file) BuildID() string { + return f.buildID +} + +func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) { + return []plugin.Frame{}, nil +} + +func (f *file) Close() error { + return nil +} + +func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { + // Get from nm a list of symbols sorted by address. + cmd := exec.Command(f.b.nm, "-n", f.name) + out, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("%v: %v", cmd.Args, err) + } + + return findSymbols(out, f.name, r, addr) +} + +// fileNM implements the binutils.ObjFile interface, using 'nm' to map +// addresses to symbols (without file/line number information). It is +// faster than fileAddr2Line. +type fileNM struct { + file + addr2linernm *addr2LinerNM +} + +func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) { + if f.addr2linernm == nil { + addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base) + if err != nil { + return nil, err + } + f.addr2linernm = addr2liner + } + return f.addr2linernm.addrInfo(addr) +} + +// fileAddr2Line implements the binutils.ObjFile interface, using +// 'addr2line' to map addresses to symbols (with file/line number +// information). It can be slow for large binaries with debug +// information. +type fileAddr2Line struct { + file + addr2liner *addr2Liner + llvmSymbolizer *llvmSymbolizer +} + +func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) { + if f.llvmSymbolizer != nil { + return f.llvmSymbolizer.addrInfo(addr) + } + if f.addr2liner != nil { + return f.addr2liner.addrInfo(addr) + } + + if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil { + f.llvmSymbolizer = llvmSymbolizer + return f.llvmSymbolizer.addrInfo(addr) + } + + if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil { + f.addr2liner = addr2liner + + // When addr2line encounters some gcc compiled binaries, it + // drops interesting parts of names in anonymous namespaces. + // Fallback to NM for better function names. + if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil { + f.addr2liner.nm = nm + } + return f.addr2liner.addrInfo(addr) + } + + return nil, fmt.Errorf("could not find local addr2liner") +} + +func (f *fileAddr2Line) Close() error { + if f.addr2liner != nil { + f.addr2liner.rw.close() + f.addr2liner = nil + } + return nil +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go new file mode 100644 index 0000000000..b7190e7ae2 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go @@ -0,0 +1,152 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binutils + +import ( + "fmt" + "testing" + + "github.com/google/pprof/internal/plugin" +) + +var testAddrMap = map[int]string{ + 1000: "_Z3fooid.clone2", + 2000: "_ZNSaIiEC1Ev.clone18", + 3000: "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", +} + +func functionName(level int) (name string) { + if name = testAddrMap[level]; name != "" { + return name + } + return fmt.Sprintf("fun%d", level) +} + +func TestAddr2Liner(t *testing.T) { + const offset = 0x500 + + a := addr2Liner{&mockAddr2liner{}, offset, nil} + for i := 1; i < 8; i++ { + addr := i*0x1000 + offset + s, err := a.addrInfo(uint64(addr)) + if err != nil { + t.Fatalf("addrInfo(%#x): %v", addr, err) + } + if len(s) != i { + t.Fatalf("addrInfo(%#x): got len==%d, want %d", addr, len(s), i) + } + for l, f := range s { + level := (len(s) - l) * 1000 + want := plugin.Frame{functionName(level), fmt.Sprintf("file%d", level), level} + + if f != want { + t.Errorf("AddrInfo(%#x)[%d]: = %+v, want %+v", addr, l, f, want) + } + } + } + s, err := a.addrInfo(0xFFFF) + if err != nil { + t.Fatalf("addrInfo(0xFFFF): %v", err) + } + if len(s) != 0 { + t.Fatalf("AddrInfo(0xFFFF): got len==%d, want 0", len(s)) + } + a.rw.close() +} + +type mockAddr2liner struct { + output []string +} + +func (a *mockAddr2liner) write(s string) error { + var lines []string + switch s { + case "1000": + lines = []string{"_Z3fooid.clone2", "file1000:1000"} + case "2000": + lines = []string{"_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "3000": + lines = []string{"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "4000": + lines = []string{"fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "5000": + lines = []string{"fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "6000": + lines = []string{"fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "7000": + lines = []string{"fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "8000": + lines = []string{"fun8000", "file8000:8000", "fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + case "9000": + lines = []string{"fun9000", "file9000:9000", "fun8000", "file8000:8000", "fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"} + default: + lines = []string{"??", "??:0"} + } + a.output = append(a.output, "0x"+s) + a.output = append(a.output, lines...) + return nil +} + +func (a *mockAddr2liner) readLine() (string, error) { + if len(a.output) == 0 { + return "", fmt.Errorf("end of file") + } + next := a.output[0] + a.output = a.output[1:] + return next, nil +} + +func (a *mockAddr2liner) close() { +} + +func TestAddr2LinerLookup(t *testing.T) { + oddSizedMap := addr2LinerNM{ + m: []symbolInfo{ + {0x1000, "0x1000"}, + {0x2000, "0x2000"}, + {0x3000, "0x3000"}, + }, + } + evenSizedMap := addr2LinerNM{ + m: []symbolInfo{ + {0x1000, "0x1000"}, + {0x2000, "0x2000"}, + {0x3000, "0x3000"}, + {0x4000, "0x4000"}, + }, + } + for _, a := range []*addr2LinerNM{ + &oddSizedMap, &evenSizedMap, + } { + for address, want := range map[uint64]string{ + 0x1000: "0x1000", + 0x1001: "0x1000", + 0x1FFF: "0x1000", + 0x2000: "0x2000", + 0x2001: "0x2000", + } { + if got, _ := a.addrInfo(address); !checkAddress(got, address, want) { + t.Errorf("%x: got %v, want %s", address, got, want) + } + } + } +} + +func checkAddress(got []plugin.Frame, address uint64, want string) bool { + if len(got) != 1 { + return false + } + return got[0].Func == want +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go new file mode 100644 index 0000000000..fcdc555dc1 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go @@ -0,0 +1,147 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binutils + +import ( + "bytes" + "io" + "regexp" + "strconv" + + "github.com/google/pprof/internal/plugin" + "github.com/ianlancetaylor/demangle" +) + +var ( + nmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+)\s+(.)\s+(.*)`) + objdumpAsmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+):\s+(.*)`) + objdumpOutputFileLine = regexp.MustCompile(`^(.*):([0-9]+)`) + objdumpOutputFunction = regexp.MustCompile(`^(\S.*)\(\):`) +) + +func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) { + // Collect all symbols from the nm output, grouping names mapped to + // the same address into a single symbol. + var symbols []*plugin.Sym + names, start := []string{}, uint64(0) + buf := bytes.NewBuffer(syms) + for symAddr, name, err := nextSymbol(buf); err == nil; symAddr, name, err = nextSymbol(buf) { + if err != nil { + return nil, err + } + if start == symAddr { + names = append(names, name) + continue + } + if match := matchSymbol(names, start, symAddr-1, r, address); match != nil { + symbols = append(symbols, &plugin.Sym{match, file, start, symAddr - 1}) + } + names, start = []string{name}, symAddr + } + + return symbols, nil +} + +// matchSymbol checks if a symbol is to be selected by checking its +// name to the regexp and optionally its address. It returns the name(s) +// to be used for the matched symbol, or nil if no match +func matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address uint64) []string { + if address != 0 && address >= start && address <= end { + return names + } + for _, name := range names { + if r.MatchString(name) { + return []string{name} + } + + // Match all possible demangled versions of the name. + for _, o := range [][]demangle.Option{ + {demangle.NoClones}, + {demangle.NoParams}, + {demangle.NoParams, demangle.NoTemplateParams}, + } { + if demangled, err := demangle.ToString(name, o...); err == nil && r.MatchString(demangled) { + return []string{demangled} + } + } + } + return nil +} + +// disassemble parses the output of the objdump command and returns +// the assembly instructions in a slice. +func disassemble(asm []byte) ([]plugin.Inst, error) { + buf := bytes.NewBuffer(asm) + function, file, line := "", "", 0 + var assembly []plugin.Inst + for { + input, err := buf.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, err + } + if input == "" { + break + } + } + + if fields := objdumpAsmOutputRE.FindStringSubmatch(input); len(fields) == 3 { + if address, err := strconv.ParseUint(fields[1], 16, 64); err == nil { + assembly = append(assembly, + plugin.Inst{ + Addr: address, + Text: fields[2], + Function: function, + File: file, + Line: line, + }) + continue + } + } + if fields := objdumpOutputFileLine.FindStringSubmatch(input); len(fields) == 3 { + if l, err := strconv.ParseUint(fields[2], 10, 32); err == nil { + file, line = fields[1], int(l) + } + continue + } + if fields := objdumpOutputFunction.FindStringSubmatch(input); len(fields) == 2 { + function = fields[1] + continue + } + // Reset on unrecognized lines. + function, file, line = "", "", 0 + } + + return assembly, nil +} + +// nextSymbol parses the nm output to find the next symbol listed. +// Skips over any output it cannot recognize. +func nextSymbol(buf *bytes.Buffer) (uint64, string, error) { + for { + line, err := buf.ReadString('\n') + if err != nil { + if err != io.EOF || line == "" { + return 0, "", err + } + } + + if fields := nmOutputRE.FindStringSubmatch(line); len(fields) == 4 { + if address, err := strconv.ParseUint(fields[1], 16, 64); err == nil { + return address, fields[3], nil + } + } + } +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm_test.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm_test.go new file mode 100644 index 0000000000..bb08023884 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm_test.go @@ -0,0 +1,154 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binutils + +import ( + "fmt" + "regexp" + "testing" + + "github.com/google/pprof/internal/plugin" +) + +// TestFindSymbols tests the FindSymbols routine using a hardcoded nm output. +func TestFindSymbols(t *testing.T) { + type testcase struct { + query, syms string + want []plugin.Sym + } + + testsyms := `0000000000001000 t lineA001 +0000000000001000 t lineA002 +0000000000001000 t line1000 +0000000000002000 t line200A +0000000000002000 t line2000 +0000000000002000 t line200B +0000000000003000 t line3000 +0000000000003000 t _ZNK4DumbclEPKc +0000000000003000 t lineB00C +0000000000003000 t line300D +0000000000004000 t _the_end + ` + testcases := []testcase{ + { + "line.*[AC]", + testsyms, + []plugin.Sym{ + {[]string{"lineA001"}, "object.o", 0x1000, 0x1FFF}, + {[]string{"line200A"}, "object.o", 0x2000, 0x2FFF}, + {[]string{"lineB00C"}, "object.o", 0x3000, 0x3FFF}, + }, + }, + { + "Dumb::operator", + testsyms, + []plugin.Sym{ + {[]string{"Dumb::operator()(char const*) const"}, "object.o", 0x3000, 0x3FFF}, + }, + }, + } + + for _, tc := range testcases { + syms, err := findSymbols([]byte(tc.syms), "object.o", regexp.MustCompile(tc.query), 0) + if err != nil { + t.Fatalf("%q: findSymbols: %v", tc.query, err) + } + if err := checkSymbol(syms, tc.want); err != nil { + t.Errorf("%q: %v", tc.query, err) + } + } +} + +func checkSymbol(got []*plugin.Sym, want []plugin.Sym) error { + if len(got) != len(want) { + return fmt.Errorf("unexpected number of symbols %d (want %d)\n", len(got), len(want)) + } + + for i, g := range got { + w := want[i] + if len(g.Name) != len(w.Name) { + return fmt.Errorf("names, got %d, want %d", len(g.Name), len(w.Name)) + } + for n := range g.Name { + if g.Name[n] != w.Name[n] { + return fmt.Errorf("name %d, got %q, want %q", n, g.Name[n], w.Name[n]) + } + } + if g.File != w.File { + return fmt.Errorf("filename, got %q, want %q", g.File, w.File) + } + if g.Start != w.Start { + return fmt.Errorf("start address, got %#x, want %#x", g.Start, w.Start) + } + if g.End != w.End { + return fmt.Errorf("end address, got %#x, want %#x", g.End, w.End) + } + } + return nil +} + +// TestFunctionAssembly tests the FunctionAssembly routine by using a +// fake objdump script. +func TestFunctionAssembly(t *testing.T) { + type testcase struct { + s plugin.Sym + asm string + want []plugin.Inst + } + testcases := []testcase{ + { + plugin.Sym{[]string{"symbol1"}, "", 0x1000, 0x1FFF}, + ` 1000: instruction one + 1001: instruction two + 1002: instruction three + 1003: instruction four +`, + []plugin.Inst{ + {Addr: 0x1000, Text: "instruction one"}, + {Addr: 0x1001, Text: "instruction two"}, + {Addr: 0x1002, Text: "instruction three"}, + {Addr: 0x1003, Text: "instruction four"}, + }, + }, + { + plugin.Sym{[]string{"symbol2"}, "", 0x2000, 0x2FFF}, + ` 2000: instruction one + 2001: instruction two +`, + []plugin.Inst{ + {Addr: 0x2000, Text: "instruction one"}, + {Addr: 0x2001, Text: "instruction two"}, + }, + }, + } + + const objdump = "testdata/wrapper/objdump" + + for _, tc := range testcases { + insts, err := disassemble([]byte(tc.asm)) + if err != nil { + t.Fatalf("FunctionAssembly: %v", err) + } + + if len(insts) != len(tc.want) { + t.Errorf("Unexpected number of assembly instructions %d (want %d)\n", len(insts), len(tc.want)) + } + for i := range insts { + if insts[i] != tc.want[i] { + t.Errorf("Expected symbol %v, got %v\n", tc.want[i], insts[i]) + } + } + } +} |
