Source file src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package binutils
    16  
    17  import (
    18  	"bufio"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"os/exec"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  
    27  	"github.com/google/pprof/internal/plugin"
    28  )
    29  
    30  const (
    31  	defaultLLVMSymbolizer = "llvm-symbolizer"
    32  )
    33  
    34  // llvmSymbolizer is a connection to an llvm-symbolizer command for
    35  // obtaining address and line number information from a binary.
    36  type llvmSymbolizer struct {
    37  	sync.Mutex
    38  	filename string
    39  	rw       lineReaderWriter
    40  	base     uint64
    41  	isData   bool
    42  }
    43  
    44  type llvmSymbolizerJob struct {
    45  	cmd *exec.Cmd
    46  	in  io.WriteCloser
    47  	out *bufio.Reader
    48  	// llvm-symbolizer requires the symbol type, CODE or DATA, for symbolization.
    49  	symType string
    50  }
    51  
    52  func (a *llvmSymbolizerJob) write(s string) error {
    53  	_, err := fmt.Fprintln(a.in, a.symType, s)
    54  	return err
    55  }
    56  
    57  func (a *llvmSymbolizerJob) readLine() (string, error) {
    58  	s, err := a.out.ReadString('\n')
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  	return strings.TrimSpace(s), nil
    63  }
    64  
    65  // close releases any resources used by the llvmSymbolizer object.
    66  func (a *llvmSymbolizerJob) close() {
    67  	a.in.Close()
    68  	a.cmd.Wait()
    69  }
    70  
    71  // newLLVMSymbolizer starts the given llvmSymbolizer command reporting
    72  // information about the given executable file. If file is a shared
    73  // library, base should be the address at which it was mapped in the
    74  // program under consideration.
    75  func newLLVMSymbolizer(cmd, file string, base uint64, isData bool) (*llvmSymbolizer, error) {
    76  	if cmd == "" {
    77  		cmd = defaultLLVMSymbolizer
    78  	}
    79  
    80  	j := &llvmSymbolizerJob{
    81  		cmd:     exec.Command(cmd, "--inlining", "-demangle=false", "--output-style=JSON"),
    82  		symType: "CODE",
    83  	}
    84  	if isData {
    85  		j.symType = "DATA"
    86  	}
    87  
    88  	var err error
    89  	if j.in, err = j.cmd.StdinPipe(); err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	outPipe, err := j.cmd.StdoutPipe()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	j.out = bufio.NewReader(outPipe)
    99  	if err := j.cmd.Start(); err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	a := &llvmSymbolizer{
   104  		filename: file,
   105  		rw:       j,
   106  		base:     base,
   107  		isData:   isData,
   108  	}
   109  
   110  	return a, nil
   111  }
   112  
   113  // readDataFrames parses the llvm-symbolizer DATA output for a single address. It
   114  // returns a populated plugin.Frame array with a single entry.
   115  func (d *llvmSymbolizer) readDataFrames() ([]plugin.Frame, error) {
   116  	line, err := d.rw.readLine()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	var frame struct {
   121  		Address    string `json:"Address"`
   122  		ModuleName string `json:"ModuleName"`
   123  		Data       struct {
   124  			Start string `json:"Start"`
   125  			Size  string `json:"Size"`
   126  			Name  string `json:"Name"`
   127  		} `json:"Data"`
   128  	}
   129  	if err := json.Unmarshal([]byte(line), &frame); err != nil {
   130  		return nil, err
   131  	}
   132  	// Match non-JSON output behaviour of stuffing the start/size into the filename of a single frame,
   133  	// with the size being a decimal value.
   134  	size, err := strconv.ParseInt(frame.Data.Size, 0, 0)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	var stack []plugin.Frame
   139  	stack = append(stack, plugin.Frame{Func: frame.Data.Name, File: fmt.Sprintf("%s %d", frame.Data.Start, size)})
   140  	return stack, nil
   141  }
   142  
   143  // readCodeFrames parses the llvm-symbolizer CODE output for a single address. It
   144  // returns a populated plugin.Frame array.
   145  func (d *llvmSymbolizer) readCodeFrames() ([]plugin.Frame, error) {
   146  	line, err := d.rw.readLine()
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	var frame struct {
   151  		Address    string `json:"Address"`
   152  		ModuleName string `json:"ModuleName"`
   153  		Symbol     []struct {
   154  			Line          int    `json:"Line"`
   155  			Column        int    `json:"Column"`
   156  			FunctionName  string `json:"FunctionName"`
   157  			FileName      string `json:"FileName"`
   158  			StartLine     int    `json:"StartLine"`
   159  		} `json:"Symbol"`
   160  	}
   161  	if err := json.Unmarshal([]byte(line), &frame); err != nil {
   162  		return nil, err
   163  	}
   164  	var stack []plugin.Frame
   165  	for _, s := range frame.Symbol {
   166  		stack = append(stack, plugin.Frame{Func: s.FunctionName, File: s.FileName, Line: s.Line, Column: s.Column, StartLine: s.StartLine})
   167  	}
   168  	return stack, nil
   169  }
   170  
   171  // addrInfo returns the stack frame information for a specific program
   172  // address. It returns nil if the address could not be identified.
   173  func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) {
   174  	d.Lock()
   175  	defer d.Unlock()
   176  
   177  	if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil {
   178  		return nil, err
   179  	}
   180  	if d.isData {
   181  		return d.readDataFrames()
   182  	}
   183  	return d.readCodeFrames()
   184  }
   185  

View as plain text