| // 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() |
| } |
| } |