// 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/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 { 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) { s, err := a.out.ReadString('\n') if err != nil { return "", err } return strings.TrimSpace(s), nil } // 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 } // 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.rw.readLine() 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.rw.readLine() d.rw.readLine() return plugin.Frame{}, true } fileline, err := d.rw.readLine() 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.rw.readLine() 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 }