Home | History | Annotate | Download | only in binutils
      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 	"fmt"
     20 	"io"
     21 	"os/exec"
     22 	"strconv"
     23 	"strings"
     24 	"sync"
     25 
     26 	"github.com/google/pprof/internal/plugin"
     27 )
     28 
     29 const (
     30 	defaultLLVMSymbolizer = "llvm-symbolizer"
     31 )
     32 
     33 // llvmSymbolizer is a connection to an llvm-symbolizer command for
     34 // obtaining address and line number information from a binary.
     35 type llvmSymbolizer struct {
     36 	sync.Mutex
     37 	filename string
     38 	rw       lineReaderWriter
     39 	base     uint64
     40 }
     41 
     42 type llvmSymbolizerJob struct {
     43 	cmd *exec.Cmd
     44 	in  io.WriteCloser
     45 	out *bufio.Reader
     46 }
     47 
     48 func (a *llvmSymbolizerJob) write(s string) error {
     49 	_, err := fmt.Fprint(a.in, s+"\n")
     50 	return err
     51 }
     52 
     53 func (a *llvmSymbolizerJob) readLine() (string, error) {
     54 	return a.out.ReadString('\n')
     55 }
     56 
     57 // close releases any resources used by the llvmSymbolizer object.
     58 func (a *llvmSymbolizerJob) close() {
     59 	a.in.Close()
     60 	a.cmd.Wait()
     61 }
     62 
     63 // newLlvmSymbolizer starts the given llvmSymbolizer command reporting
     64 // information about the given executable file. If file is a shared
     65 // library, base should be the address at which it was mapped in the
     66 // program under consideration.
     67 func newLLVMSymbolizer(cmd, file string, base uint64) (*llvmSymbolizer, error) {
     68 	if cmd == "" {
     69 		cmd = defaultLLVMSymbolizer
     70 	}
     71 
     72 	j := &llvmSymbolizerJob{
     73 		cmd: exec.Command(cmd, "-inlining", "-demangle=false"),
     74 	}
     75 
     76 	var err error
     77 	if j.in, err = j.cmd.StdinPipe(); err != nil {
     78 		return nil, err
     79 	}
     80 
     81 	outPipe, err := j.cmd.StdoutPipe()
     82 	if err != nil {
     83 		return nil, err
     84 	}
     85 
     86 	j.out = bufio.NewReader(outPipe)
     87 	if err := j.cmd.Start(); err != nil {
     88 		return nil, err
     89 	}
     90 
     91 	a := &llvmSymbolizer{
     92 		filename: file,
     93 		rw:       j,
     94 		base:     base,
     95 	}
     96 
     97 	return a, nil
     98 }
     99 
    100 func (d *llvmSymbolizer) readString() (string, error) {
    101 	s, err := d.rw.readLine()
    102 	if err != nil {
    103 		return "", err
    104 	}
    105 	return strings.TrimSpace(s), nil
    106 }
    107 
    108 // readFrame parses the llvm-symbolizer output for a single address. It
    109 // returns a populated plugin.Frame and whether it has reached the end of the
    110 // data.
    111 func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
    112 	funcname, err := d.readString()
    113 	if err != nil {
    114 		return plugin.Frame{}, true
    115 	}
    116 
    117 	switch funcname {
    118 	case "":
    119 		return plugin.Frame{}, true
    120 	case "??":
    121 		funcname = ""
    122 	}
    123 
    124 	fileline, err := d.readString()
    125 	if err != nil {
    126 		return plugin.Frame{Func: funcname}, true
    127 	}
    128 
    129 	linenumber := 0
    130 	if fileline == "??:0" {
    131 		fileline = ""
    132 	} else {
    133 		switch split := strings.Split(fileline, ":"); len(split) {
    134 		case 1:
    135 			// filename
    136 			fileline = split[0]
    137 		case 2, 3:
    138 			// filename:line , or
    139 			// filename:line:disc , or
    140 			fileline = split[0]
    141 			if line, err := strconv.Atoi(split[1]); err == nil {
    142 				linenumber = line
    143 			}
    144 		default:
    145 			// Unrecognized, ignore
    146 		}
    147 	}
    148 
    149 	return plugin.Frame{Func: funcname, File: fileline, Line: linenumber}, false
    150 }
    151 
    152 // addrInfo returns the stack frame information for a specific program
    153 // address. It returns nil if the address could not be identified.
    154 func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) {
    155 	d.Lock()
    156 	defer d.Unlock()
    157 
    158 	if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil {
    159 		return nil, err
    160 	}
    161 
    162 	var stack []plugin.Frame
    163 	for {
    164 		frame, end := d.readFrame()
    165 		if end {
    166 			break
    167 		}
    168 
    169 		if frame != (plugin.Frame{}) {
    170 			stack = append(stack, frame)
    171 		}
    172 	}
    173 
    174 	return stack, nil
    175 }
    176