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