// 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 symbolizer provides a routine to populate a profile with // symbol, file and line number information. It relies on the // addr2liner and demangler packages to do the actual work. package symbolizer import ( "fmt" "os" "path/filepath" "strings" "cmd/pprof/internal/plugin" "cmd/pprof/internal/profile" ) // Symbolize adds symbol and line number information to all locations // in a profile. mode enables some options to control // symbolization. Currently only recognizes "force", which causes it // to overwrite any existing data. func Symbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { force := false // Disable some mechanisms based on mode string. for _, o := range strings.Split(strings.ToLower(mode), ":") { switch o { case "force": force = true default: } } if len(prof.Mapping) == 0 { return fmt.Errorf("no known mappings") } mt, err := newMapping(prof, obj, ui, force) if err != nil { return err } defer mt.close() functions := make(map[profile.Function]*profile.Function) for _, l := range mt.prof.Location { m := l.Mapping segment := mt.segments[m] if segment == nil { // Nothing to do continue } stack, err := segment.SourceLine(l.Address) if err != nil || len(stack) == 0 { // No answers from addr2line continue } l.Line = make([]profile.Line, len(stack)) for i, frame := range stack { if frame.Func != "" { m.HasFunctions = true } if frame.File != "" { m.HasFilenames = true } if frame.Line != 0 { m.HasLineNumbers = true } f := &profile.Function{ Name: frame.Func, SystemName: frame.Func, Filename: frame.File, } if fp := functions[*f]; fp != nil { f = fp } else { functions[*f] = f f.ID = uint64(len(mt.prof.Function)) + 1 mt.prof.Function = append(mt.prof.Function, f) } l.Line[i] = profile.Line{ Function: f, Line: int64(frame.Line), } } if len(stack) > 0 { m.HasInlineFrames = true } } return nil } // newMapping creates a mappingTable for a profile. func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) { mt := &mappingTable{ prof: prof, segments: make(map[*profile.Mapping]plugin.ObjFile), } // Identify used mappings mappings := make(map[*profile.Mapping]bool) for _, l := range prof.Location { mappings[l.Mapping] = true } for _, m := range prof.Mapping { if !mappings[m] { continue } // Do not attempt to re-symbolize a mapping that has already been symbolized. if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) { continue } f, err := locateFile(obj, m.File, m.BuildID, m.Start) if err != nil { ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err) // Move on to other mappings continue } if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { // Build ID mismatch - ignore. f.Close() continue } mt.segments[m] = f } return mt, nil } // 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 } // mappingTable contains the mechanisms for symbolization of a // profile. type mappingTable struct { prof *profile.Profile segments map[*profile.Mapping]plugin.ObjFile } // Close releases any external processes being used for the mapping. func (mt *mappingTable) close() { for _, segment := range mt.segments { segment.Close() } }