github.com/4ad/go@v0.0.0-20161219182952-69a12818b605/src/cmd/pprof/pprof.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"debug/dwarf"
     9  	"debug/gosym"
    10  	"flag"
    11  	"fmt"
    12  	"net/url"
    13  	"os"
    14  	"regexp"
    15  	"strings"
    16  	"sync"
    17  
    18  	"cmd/internal/objfile"
    19  	"cmd/internal/pprof/commands"
    20  	"cmd/internal/pprof/driver"
    21  	"cmd/internal/pprof/fetch"
    22  	"cmd/internal/pprof/plugin"
    23  	"cmd/internal/pprof/profile"
    24  	"cmd/internal/pprof/symbolizer"
    25  	"cmd/internal/pprof/symbolz"
    26  )
    27  
    28  func main() {
    29  	var extraCommands map[string]*commands.Command // no added Go-specific commands
    30  	if err := driver.PProf(flags{}, fetch.Fetcher, symbolize, new(objTool), plugin.StandardUI(), extraCommands); err != nil {
    31  		fmt.Fprintf(os.Stderr, "%v\n", err)
    32  		os.Exit(2)
    33  	}
    34  }
    35  
    36  // symbolize attempts to symbolize profile p.
    37  // If the source is a local binary, it tries using symbolizer and obj.
    38  // If the source is a URL, it fetches symbol information using symbolz.
    39  func symbolize(mode, source string, p *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error {
    40  	remote, local := true, true
    41  	for _, o := range strings.Split(strings.ToLower(mode), ":") {
    42  		switch o {
    43  		case "none", "no":
    44  			return nil
    45  		case "local":
    46  			remote, local = false, true
    47  		case "remote":
    48  			remote, local = true, false
    49  		default:
    50  			ui.PrintErr("ignoring unrecognized symbolization option: " + mode)
    51  			ui.PrintErr("expecting -symbolize=[local|remote|none][:force]")
    52  			fallthrough
    53  		case "", "force":
    54  			// -force is recognized by symbolizer.Symbolize.
    55  			// If the source is remote, and the mapping file
    56  			// does not exist, don't use local symbolization.
    57  			if isRemote(source) {
    58  				if len(p.Mapping) == 0 {
    59  					local = false
    60  				} else if _, err := os.Stat(p.Mapping[0].File); err != nil {
    61  					local = false
    62  				}
    63  			}
    64  		}
    65  	}
    66  
    67  	var err error
    68  	if local {
    69  		// Symbolize using binutils.
    70  		if err = symbolizer.Symbolize(mode, p, obj, ui); err == nil {
    71  			return nil
    72  		}
    73  	}
    74  	if remote {
    75  		err = symbolz.Symbolize(source, fetch.PostURL, p)
    76  	}
    77  	return err
    78  }
    79  
    80  // isRemote returns whether source is a URL for a remote source.
    81  func isRemote(source string) bool {
    82  	url, err := url.Parse(source)
    83  	if err != nil {
    84  		url, err = url.Parse("http://" + source)
    85  		if err != nil {
    86  			return false
    87  		}
    88  	}
    89  	if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" {
    90  		return false
    91  	}
    92  	return true
    93  }
    94  
    95  // flags implements the driver.FlagPackage interface using the builtin flag package.
    96  type flags struct {
    97  }
    98  
    99  func (flags) Bool(o string, d bool, c string) *bool {
   100  	return flag.Bool(o, d, c)
   101  }
   102  
   103  func (flags) Int(o string, d int, c string) *int {
   104  	return flag.Int(o, d, c)
   105  }
   106  
   107  func (flags) Float64(o string, d float64, c string) *float64 {
   108  	return flag.Float64(o, d, c)
   109  }
   110  
   111  func (flags) String(o, d, c string) *string {
   112  	return flag.String(o, d, c)
   113  }
   114  
   115  func (flags) Parse(usage func()) []string {
   116  	flag.Usage = usage
   117  	flag.Parse()
   118  	args := flag.Args()
   119  	if len(args) == 0 {
   120  		usage()
   121  	}
   122  	return args
   123  }
   124  
   125  func (flags) ExtraUsage() string {
   126  	return ""
   127  }
   128  
   129  // objTool implements plugin.ObjTool using Go libraries
   130  // (instead of invoking GNU binutils).
   131  type objTool struct {
   132  	mu          sync.Mutex
   133  	disasmCache map[string]*objfile.Disasm
   134  }
   135  
   136  func (*objTool) Open(name string, start uint64) (plugin.ObjFile, error) {
   137  	of, err := objfile.Open(name)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	f := &file{
   142  		name: name,
   143  		file: of,
   144  	}
   145  	if start != 0 {
   146  		if load, err := of.LoadAddress(); err == nil {
   147  			f.offset = start - load
   148  		}
   149  	}
   150  	return f, nil
   151  }
   152  
   153  func (*objTool) Demangle(names []string) (map[string]string, error) {
   154  	// No C++, nothing to demangle.
   155  	return make(map[string]string), nil
   156  }
   157  
   158  func (t *objTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
   159  	d, err := t.cachedDisasm(file)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	var asm []plugin.Inst
   164  	d.Decode(start, end, func(pc, size uint64, file string, line int, text string) {
   165  		asm = append(asm, plugin.Inst{Addr: pc, File: file, Line: line, Text: text})
   166  	})
   167  	return asm, nil
   168  }
   169  
   170  func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) {
   171  	t.mu.Lock()
   172  	defer t.mu.Unlock()
   173  	if t.disasmCache == nil {
   174  		t.disasmCache = make(map[string]*objfile.Disasm)
   175  	}
   176  	d := t.disasmCache[file]
   177  	if d != nil {
   178  		return d, nil
   179  	}
   180  	f, err := objfile.Open(file)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	d, err = f.Disasm()
   185  	f.Close()
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	t.disasmCache[file] = d
   190  	return d, nil
   191  }
   192  
   193  func (*objTool) SetConfig(config string) {
   194  	// config is usually used to say what binaries to invoke.
   195  	// Ignore entirely.
   196  }
   197  
   198  // file implements plugin.ObjFile using Go libraries
   199  // (instead of invoking GNU binutils).
   200  // A file represents a single executable being analyzed.
   201  type file struct {
   202  	name   string
   203  	offset uint64
   204  	sym    []objfile.Sym
   205  	file   *objfile.File
   206  	pcln   *gosym.Table
   207  
   208  	triedDwarf bool
   209  	dwarf      *dwarf.Data
   210  }
   211  
   212  func (f *file) Name() string {
   213  	return f.name
   214  }
   215  
   216  func (f *file) Base() uint64 {
   217  	// No support for shared libraries.
   218  	return 0
   219  }
   220  
   221  func (f *file) BuildID() string {
   222  	// No support for build ID.
   223  	return ""
   224  }
   225  
   226  func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
   227  	if f.pcln == nil {
   228  		pcln, err := f.file.PCLineTable()
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		f.pcln = pcln
   233  	}
   234  	addr -= f.offset
   235  	file, line, fn := f.pcln.PCToLine(addr)
   236  	if fn != nil {
   237  		frame := []plugin.Frame{
   238  			{
   239  				Func: fn.Name,
   240  				File: file,
   241  				Line: line,
   242  			},
   243  		}
   244  		return frame, nil
   245  	}
   246  
   247  	frames := f.dwarfSourceLine(addr)
   248  	if frames != nil {
   249  		return frames, nil
   250  	}
   251  
   252  	return nil, fmt.Errorf("no line information for PC=%#x", addr)
   253  }
   254  
   255  // dwarfSourceLine tries to get file/line information using DWARF.
   256  // This is for C functions that appear in the profile.
   257  // Returns nil if there is no information available.
   258  func (f *file) dwarfSourceLine(addr uint64) []plugin.Frame {
   259  	if f.dwarf == nil && !f.triedDwarf {
   260  		// Ignore any error--we don't care exactly why there
   261  		// is no DWARF info.
   262  		f.dwarf, _ = f.file.DWARF()
   263  		f.triedDwarf = true
   264  	}
   265  
   266  	if f.dwarf != nil {
   267  		r := f.dwarf.Reader()
   268  		unit, err := r.SeekPC(addr)
   269  		if err == nil {
   270  			if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {
   271  				return frames
   272  			}
   273  		}
   274  	}
   275  
   276  	return nil
   277  }
   278  
   279  // dwarfSourceLineEntry tries to get file/line information from a
   280  // DWARF compilation unit. Returns nil if it doesn't find anything.
   281  func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []plugin.Frame {
   282  	lines, err := f.dwarf.LineReader(entry)
   283  	if err != nil {
   284  		return nil
   285  	}
   286  	var lentry dwarf.LineEntry
   287  	if err := lines.SeekPC(addr, &lentry); err != nil {
   288  		return nil
   289  	}
   290  
   291  	// Try to find the function name.
   292  	name := ""
   293  FindName:
   294  	for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
   295  		if entry.Tag == dwarf.TagSubprogram {
   296  			ranges, err := f.dwarf.Ranges(entry)
   297  			if err != nil {
   298  				return nil
   299  			}
   300  			for _, pcs := range ranges {
   301  				if pcs[0] <= addr && addr < pcs[1] {
   302  					var ok bool
   303  					// TODO: AT_linkage_name, AT_MIPS_linkage_name.
   304  					name, ok = entry.Val(dwarf.AttrName).(string)
   305  					if ok {
   306  						break FindName
   307  					}
   308  				}
   309  			}
   310  		}
   311  	}
   312  
   313  	// TODO: Report inlined functions.
   314  
   315  	frames := []plugin.Frame{
   316  		{
   317  			Func: name,
   318  			File: lentry.File.Name,
   319  			Line: lentry.Line,
   320  		},
   321  	}
   322  
   323  	return frames
   324  }
   325  
   326  func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
   327  	if f.sym == nil {
   328  		sym, err := f.file.Symbols()
   329  		if err != nil {
   330  			return nil, err
   331  		}
   332  		f.sym = sym
   333  	}
   334  	var out []*plugin.Sym
   335  	for _, s := range f.sym {
   336  		// Ignore a symbol with address 0 and size 0.
   337  		// An ELF STT_FILE symbol will look like that.
   338  		if s.Addr == 0 && s.Size == 0 {
   339  			continue
   340  		}
   341  		if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
   342  			out = append(out, &plugin.Sym{
   343  				Name:  []string{s.Name},
   344  				File:  f.name,
   345  				Start: s.Addr,
   346  				End:   s.Addr + uint64(s.Size) - 1,
   347  			})
   348  		}
   349  	}
   350  	return out, nil
   351  }
   352  
   353  func (f *file) Close() error {
   354  	f.file.Close()
   355  	return nil
   356  }