// 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 ( 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 { sync.Mutex filename string rw lineReaderWriter base uint64 } type llvmSymbolizerJob struct { cmd *exec.Cmd in io.WriteCloser out *bufio.Reader // llvm-symbolizer requires the symbol type, CODE or DATA, for symbolization. symType string } func (a *llvmSymbolizerJob) write(s string) error { _, err := fmt.Fprintln(a.in, a.symType, s) return err } func (a *llvmSymbolizerJob) 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 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, isData bool) (*llvmSymbolizer, error) { if cmd == "" { cmd = defaultLLVMSymbolizer } j := &llvmSymbolizerJob{ cmd: exec.Command(cmd, "--inlining", "-demangle=false"), symType: "CODE", } if isData { j.symType = "DATA" } 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 } // 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.rw.readLine() if err != nil { return plugin.Frame{}, true } switch funcname { case "": return plugin.Frame{}, true case "??": funcname = "" } fileline, err := d.rw.readLine() if err != nil { return plugin.Frame{Func: funcname}, true } linenumber := 0 // The llvm-symbolizer outputs the ::. // When it cannot identify the source code location, it outputs "??:0:0". // Older versions output just the filename and line number, so we check for // both conditions here. if fileline == "??:0" || fileline == "??:0: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{Func: funcname, File: fileline, Line: 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) { d.Lock() defer d.Unlock() 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 }