| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "debug/dwarf" |
| "flag" |
| "fmt" |
| "net/url" |
| "os" |
| "regexp" |
| "strings" |
| "sync" |
| |
| "cmd/internal/objfile" |
| "cmd/pprof/internal/commands" |
| "cmd/pprof/internal/driver" |
| "cmd/pprof/internal/fetch" |
| "cmd/pprof/internal/plugin" |
| "cmd/pprof/internal/symbolizer" |
| "cmd/pprof/internal/symbolz" |
| "internal/pprof/profile" |
| ) |
| |
| func main() { |
| var extraCommands map[string]*commands.Command // no added Go-specific commands |
| if err := driver.PProf(flags{}, fetch.Fetcher, symbolize, new(objTool), plugin.StandardUI(), extraCommands); err != nil { |
| fmt.Fprintf(os.Stderr, "%v\n", err) |
| os.Exit(2) |
| } |
| } |
| |
| // symbolize attempts to symbolize profile p. |
| // If the source is a local binary, it tries using symbolizer and obj. |
| // If the source is a URL, it fetches symbol information using symbolz. |
| func symbolize(mode, source string, p *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { |
| remote, local := true, true |
| for _, o := range strings.Split(strings.ToLower(mode), ":") { |
| switch o { |
| case "none", "no": |
| return nil |
| case "local": |
| remote, local = false, true |
| case "remote": |
| remote, local = true, false |
| default: |
| ui.PrintErr("ignoring unrecognized symbolization option: " + mode) |
| ui.PrintErr("expecting -symbolize=[local|remote|none][:force]") |
| fallthrough |
| case "", "force": |
| // -force is recognized by symbolizer.Symbolize. |
| // If the source is remote, and the mapping file |
| // does not exist, don't use local symbolization. |
| if isRemote(source) { |
| if len(p.Mapping) == 0 { |
| local = false |
| } else if _, err := os.Stat(p.Mapping[0].File); err != nil { |
| local = false |
| } |
| } |
| } |
| } |
| |
| var err error |
| if local { |
| // Symbolize using binutils. |
| if err = symbolizer.Symbolize(mode, p, obj, ui); err == nil { |
| return nil |
| } |
| } |
| if remote { |
| err = symbolz.Symbolize(source, fetch.PostURL, p) |
| } |
| return err |
| } |
| |
| // isRemote returns whether source is a URL for a remote source. |
| func isRemote(source string) bool { |
| url, err := url.Parse(source) |
| if err != nil { |
| url, err = url.Parse("http://" + source) |
| if err != nil { |
| return false |
| } |
| } |
| if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" { |
| return false |
| } |
| return true |
| } |
| |
| // flags implements the driver.FlagPackage interface using the builtin flag package. |
| type flags struct { |
| } |
| |
| func (flags) Bool(o string, d bool, c string) *bool { |
| return flag.Bool(o, d, c) |
| } |
| |
| func (flags) Int(o string, d int, c string) *int { |
| return flag.Int(o, d, c) |
| } |
| |
| func (flags) Float64(o string, d float64, c string) *float64 { |
| return flag.Float64(o, d, c) |
| } |
| |
| func (flags) String(o, d, c string) *string { |
| return flag.String(o, d, c) |
| } |
| |
| func (flags) Parse(usage func()) []string { |
| flag.Usage = usage |
| flag.Parse() |
| args := flag.Args() |
| if len(args) == 0 { |
| usage() |
| } |
| return args |
| } |
| |
| func (flags) ExtraUsage() string { |
| return "" |
| } |
| |
| // objTool implements plugin.ObjTool using Go libraries |
| // (instead of invoking GNU binutils). |
| type objTool struct { |
| mu sync.Mutex |
| disasmCache map[string]*objfile.Disasm |
| } |
| |
| func (*objTool) Open(name string, start uint64) (plugin.ObjFile, error) { |
| of, err := objfile.Open(name) |
| if err != nil { |
| return nil, err |
| } |
| f := &file{ |
| name: name, |
| file: of, |
| } |
| if start != 0 { |
| if load, err := of.LoadAddress(); err == nil { |
| f.offset = start - load |
| } |
| } |
| return f, nil |
| } |
| |
| func (*objTool) Demangle(names []string) (map[string]string, error) { |
| // No C++, nothing to demangle. |
| return make(map[string]string), nil |
| } |
| |
| func (t *objTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { |
| d, err := t.cachedDisasm(file) |
| if err != nil { |
| return nil, err |
| } |
| var asm []plugin.Inst |
| d.Decode(start, end, nil, func(pc, size uint64, file string, line int, text string) { |
| asm = append(asm, plugin.Inst{Addr: pc, File: file, Line: line, Text: text}) |
| }) |
| return asm, nil |
| } |
| |
| func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) { |
| t.mu.Lock() |
| defer t.mu.Unlock() |
| if t.disasmCache == nil { |
| t.disasmCache = make(map[string]*objfile.Disasm) |
| } |
| d := t.disasmCache[file] |
| if d != nil { |
| return d, nil |
| } |
| f, err := objfile.Open(file) |
| if err != nil { |
| return nil, err |
| } |
| d, err = f.Disasm() |
| f.Close() |
| if err != nil { |
| return nil, err |
| } |
| t.disasmCache[file] = d |
| return d, nil |
| } |
| |
| func (*objTool) SetConfig(config string) { |
| // config is usually used to say what binaries to invoke. |
| // Ignore entirely. |
| } |
| |
| // file implements plugin.ObjFile using Go libraries |
| // (instead of invoking GNU binutils). |
| // A file represents a single executable being analyzed. |
| type file struct { |
| name string |
| offset uint64 |
| sym []objfile.Sym |
| file *objfile.File |
| pcln objfile.Liner |
| |
| triedDwarf bool |
| dwarf *dwarf.Data |
| } |
| |
| func (f *file) Name() string { |
| return f.name |
| } |
| |
| func (f *file) Base() uint64 { |
| // No support for shared libraries. |
| return 0 |
| } |
| |
| func (f *file) BuildID() string { |
| // No support for build ID. |
| return "" |
| } |
| |
| func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) { |
| if f.pcln == nil { |
| pcln, err := f.file.PCLineTable() |
| if err != nil { |
| return nil, err |
| } |
| f.pcln = pcln |
| } |
| addr -= f.offset |
| file, line, fn := f.pcln.PCToLine(addr) |
| if fn != nil { |
| frame := []plugin.Frame{ |
| { |
| Func: fn.Name, |
| File: file, |
| Line: line, |
| }, |
| } |
| return frame, nil |
| } |
| |
| frames := f.dwarfSourceLine(addr) |
| if frames != nil { |
| return frames, nil |
| } |
| |
| return nil, fmt.Errorf("no line information for PC=%#x", addr) |
| } |
| |
| // dwarfSourceLine tries to get file/line information using DWARF. |
| // This is for C functions that appear in the profile. |
| // Returns nil if there is no information available. |
| func (f *file) dwarfSourceLine(addr uint64) []plugin.Frame { |
| if f.dwarf == nil && !f.triedDwarf { |
| // Ignore any error--we don't care exactly why there |
| // is no DWARF info. |
| f.dwarf, _ = f.file.DWARF() |
| f.triedDwarf = true |
| } |
| |
| if f.dwarf != nil { |
| r := f.dwarf.Reader() |
| unit, err := r.SeekPC(addr) |
| if err == nil { |
| if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil { |
| return frames |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // dwarfSourceLineEntry tries to get file/line information from a |
| // DWARF compilation unit. Returns nil if it doesn't find anything. |
| func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []plugin.Frame { |
| lines, err := f.dwarf.LineReader(entry) |
| if err != nil { |
| return nil |
| } |
| var lentry dwarf.LineEntry |
| if err := lines.SeekPC(addr, &lentry); err != nil { |
| return nil |
| } |
| |
| // Try to find the function name. |
| name := "" |
| FindName: |
| for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() { |
| if entry.Tag == dwarf.TagSubprogram { |
| ranges, err := f.dwarf.Ranges(entry) |
| if err != nil { |
| return nil |
| } |
| for _, pcs := range ranges { |
| if pcs[0] <= addr && addr < pcs[1] { |
| var ok bool |
| // TODO: AT_linkage_name, AT_MIPS_linkage_name. |
| name, ok = entry.Val(dwarf.AttrName).(string) |
| if ok { |
| break FindName |
| } |
| } |
| } |
| } |
| } |
| |
| // TODO: Report inlined functions. |
| |
| frames := []plugin.Frame{ |
| { |
| Func: name, |
| File: lentry.File.Name, |
| Line: lentry.Line, |
| }, |
| } |
| |
| return frames |
| } |
| |
| func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { |
| if f.sym == nil { |
| sym, err := f.file.Symbols() |
| if err != nil { |
| return nil, err |
| } |
| f.sym = sym |
| } |
| var out []*plugin.Sym |
| for _, s := range f.sym { |
| // Ignore a symbol with address 0 and size 0. |
| // An ELF STT_FILE symbol will look like that. |
| if s.Addr == 0 && s.Size == 0 { |
| continue |
| } |
| if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) { |
| out = append(out, &plugin.Sym{ |
| Name: []string{s.Name}, |
| File: f.name, |
| Start: s.Addr, |
| End: s.Addr + uint64(s.Size) - 1, |
| }) |
| } |
| } |
| return out, nil |
| } |
| |
| func (f *file) Close() error { |
| f.file.Close() |
| return nil |
| } |