Home | History | Annotate | Download | only in pprof
      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/pprof/internal/commands"
     19 	"cmd/pprof/internal/driver"
     20 	"cmd/pprof/internal/fetch"
     21 	"cmd/pprof/internal/plugin"
     22 	"cmd/pprof/internal/symbolizer"
     23 	"cmd/pprof/internal/symbolz"
     24 	"internal/pprof/profile"
     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 }
    356