| // 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) { | |
| 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 | |
| } |