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