// assemblyPerSourceLine disassembles the binary containing a symbol // and classifies the assembly instructions according to its // corresponding source line, annotating them with a set of samples. func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes { assembly := make(map[int]nodes) // Identify symbol to use for this collection of samples. o := findMatchingSymbol(objSyms, rs) if o == nil { return assembly } // Extract assembly for matched symbol insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End) if err != nil { return assembly } srcBase := filepath.Base(src) anodes := annotateAssembly(insns, rs, o.base) var lineno = 0 for _, an := range anodes { if filepath.Base(an.info.file) == srcBase { lineno = an.info.lineno } if lineno != 0 { assembly[lineno] = append(assembly[lineno], an) } } return assembly }
// printAssembly prints an annotated assembly listing. func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error { g, err := newGraph(rpt) if err != nil { return err } o := rpt.options prof := rpt.prof // If the regexp source can be parsed as an address, also match // functions that land on that address. var address *uint64 if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { address = &hex } fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total)) symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj) symNodes := nodesPerSymbol(g.ns, symbols) // Sort function names for printing. var syms objSymbols for s := range symNodes { syms = append(syms, s) } sort.Sort(syms) // Correlate the symbols from the binary with the profile samples. for _, s := range syms { sns := symNodes[s] // Gather samples for this symbol. flatSum, cumSum := sumNodes(sns) // Get the function assembly. insns, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End) if err != nil { return err } ns := annotateAssembly(insns, sns, s.base) fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0]) for _, name := range s.sym.Name[1:] { fmt.Fprintf(w, " AKA ======================== %s\n", name) } fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", rpt.formatValue(flatSum), rpt.formatValue(cumSum), percentage(cumSum, rpt.total)) for _, n := range ns { fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.flat, rpt), valueOrDot(n.cum, rpt), n.info.address, n.info.name) } } return nil }
// symbolsFromBinaries examines the binaries listed on the profile // that have associated samples, and identifies symbols matching rx. func symbolsFromBinaries(prof *profile.Profile, g graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol { hasSamples := make(map[string]bool) // Only examine mappings that have samples that match the // regexp. This is an optimization to speed up pprof. for _, n := range g.ns { if name := n.info.prettyName(); rx.MatchString(name) && n.info.objfile != "" { hasSamples[n.info.objfile] = true } } // Walk all mappings looking for matching functions with samples. var objSyms []*objSymbol for _, m := range prof.Mapping { if !hasSamples[filepath.Base(m.File)] { if address == nil || !(m.Start <= *address && *address <= m.Limit) { continue } } f, err := obj.Open(m.File, m.Start) if err != nil { fmt.Printf("%v\n", err) continue } // Find symbols in this binary matching the user regexp. var addr uint64 if address != nil { addr = *address } msyms, err := f.Symbols(rx, addr) base := f.Base() f.Close() if err != nil { continue } for _, ms := range msyms { objSyms = append(objSyms, &objSymbol{ sym: ms, base: base, }, ) } } return objSyms }
// locateFile opens a local file for symbolization on the search path // at $PPROF_BINARY_PATH. Looks inside these directories for files // named $BUILDID/$BASENAME and $BASENAME (if build id is available). func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) { // Construct search path to examine searchPath := os.Getenv("PPROF_BINARY_PATH") if searchPath == "" { // Use $HOME/pprof/binaries as default directory for local symbolization binaries searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries") } // Collect names to search: {buildid/basename, basename} var fileNames []string if baseName := filepath.Base(file); buildID != "" { fileNames = []string{filepath.Join(buildID, baseName), baseName} } else { fileNames = []string{baseName} } for _, path := range filepath.SplitList(searchPath) { for nameIndex, name := range fileNames { file := filepath.Join(path, name) if f, err := obj.Open(file, start); err == nil { fileBuildID := f.BuildID() if buildID == "" || buildID == fileBuildID { return f, nil } f.Close() if nameIndex == 0 { // If this is the first name, the path includes the build id. Report inconsistency. return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID) } } } } // Try original file name f, err := obj.Open(file, start) if err == nil && buildID != "" { if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID { // Mismatched build IDs, ignore f.Close() return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID) } } return f, err }
// PProf acquires a profile, and symbolizes it using a profile // manager. Then it generates a report formatted according to the // options selected through the flags package. func PProf(flagset plugin.FlagSet, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, overrides commands.Commands) error { // Remove any temporary files created during pprof processing. defer tempfile.Cleanup() f, err := getFlags(flagset, overrides, ui) if err != nil { return err } obj.SetConfig(*f.flagTools) sources := f.profileSource if len(sources) > 1 { source := sources[0] // If the first argument is a supported object file, treat as executable. if file, err := obj.Open(source, 0); err == nil { file.Close() f.profileExecName = source sources = sources[1:] } else if *f.flagBuildID == "" && isBuildID(source) { f.flagBuildID = &source sources = sources[1:] } } // errMu protects concurrent accesses to errset and err. errset is set if an // error is encountered by one of the goroutines grabbing a profile. errMu, errset := sync.Mutex{}, false // Fetch profiles. wg := sync.WaitGroup{} profs := make([]*profile.Profile, len(sources)) for i, source := range sources { wg.Add(1) go func(i int, src string) { defer wg.Done() p, grabErr := grabProfile(src, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) if grabErr != nil { errMu.Lock() defer errMu.Unlock() errset, err = true, grabErr return } profs[i] = p }(i, source) } wg.Wait() if errset { return err } // Merge profiles. prof := profs[0] for _, p := range profs[1:] { if err = prof.Merge(p, 1); err != nil { return err } } if *f.flagBase != "" { // Fetch base profile and subtract from current profile. base, err := grabProfile(*f.flagBase, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) if err != nil { return err } if err = prof.Merge(base, -1); err != nil { return err } } if err := processFlags(prof, ui, f); err != nil { return err } if !*f.flagRuntime { prof.RemoveUninteresting() } if *f.flagInteractive { return interactive(prof, obj, ui, f) } return generate(false, prof, obj, ui, f) }