123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- // 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"
- "sync"
-
- "github.com/google/pprof/pkg/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 {
- mu sync.Mutex
- rw lineReaderWriter
- base uint64
-
- // nm holds an addr2Liner using nm tool. Certain versions of addr2line
- // produce incomplete names due to
- // https://sourceware.org/bugzilla/show_bug.cgi?id=17541. As a workaround,
- // the names from nm are used when they look more complete. See addrInfo()
- // code below for the exact heuristic.
- 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{
- Func: funcname,
- File: fileline,
- Line: linenumber}, false
- }
-
- func (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) {
- d.mu.Lock()
- defer d.mu.Unlock()
-
- 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)
- }
- }
- return stack, err
- }
-
- // 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) {
- stack, err := d.rawAddrInfo(addr)
- if err != nil {
- return nil, err
- }
-
- // Certain versions of addr2line produce incomplete names due to
- // https://sourceware.org/bugzilla/show_bug.cgi?id=17541. Attempt to replace
- // the name with a better one from nm.
- 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
- // by 2 or more characters. We consider nm names that are longer by 1
- // character insignificant to avoid replacing foo with _foo on MacOS (for
- // unknown reasons read2line produces the former and nm produces the
- // latter on MacOS even though both tools are asked to produce mangled
- // names).
- nmName := nm[len(nm)-1].Func
- a2lName := stack[len(stack)-1].Func
- if len(nmName) > len(a2lName)+1 {
- stack[len(stack)-1].Func = nmName
- }
- }
- }
-
- return stack, nil
- }
|