Home | History | Annotate | Download | only in symbolizer
      1 // Copyright 2014 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package symbolizer provides a routine to populate a profile with
      6 // symbol, file and line number information. It relies on the
      7 // addr2liner and demangler packages to do the actual work.
      8 package symbolizer
      9 
     10 import (
     11 	"fmt"
     12 	"os"
     13 	"path/filepath"
     14 	"strings"
     15 
     16 	"cmd/pprof/internal/plugin"
     17 	"cmd/pprof/internal/profile"
     18 )
     19 
     20 // Symbolize adds symbol and line number information to all locations
     21 // in a profile. mode enables some options to control
     22 // symbolization. Currently only recognizes "force", which causes it
     23 // to overwrite any existing data.
     24 func Symbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error {
     25 	force := false
     26 	// Disable some mechanisms based on mode string.
     27 	for _, o := range strings.Split(strings.ToLower(mode), ":") {
     28 		switch o {
     29 		case "force":
     30 			force = true
     31 		default:
     32 		}
     33 	}
     34 
     35 	if len(prof.Mapping) == 0 {
     36 		return fmt.Errorf("no known mappings")
     37 	}
     38 
     39 	mt, err := newMapping(prof, obj, ui, force)
     40 	if err != nil {
     41 		return err
     42 	}
     43 	defer mt.close()
     44 
     45 	functions := make(map[profile.Function]*profile.Function)
     46 	for _, l := range mt.prof.Location {
     47 		m := l.Mapping
     48 		segment := mt.segments[m]
     49 		if segment == nil {
     50 			// Nothing to do
     51 			continue
     52 		}
     53 
     54 		stack, err := segment.SourceLine(l.Address)
     55 		if err != nil || len(stack) == 0 {
     56 			// No answers from addr2line
     57 			continue
     58 		}
     59 
     60 		l.Line = make([]profile.Line, len(stack))
     61 		for i, frame := range stack {
     62 			if frame.Func != "" {
     63 				m.HasFunctions = true
     64 			}
     65 			if frame.File != "" {
     66 				m.HasFilenames = true
     67 			}
     68 			if frame.Line != 0 {
     69 				m.HasLineNumbers = true
     70 			}
     71 			f := &profile.Function{
     72 				Name:       frame.Func,
     73 				SystemName: frame.Func,
     74 				Filename:   frame.File,
     75 			}
     76 			if fp := functions[*f]; fp != nil {
     77 				f = fp
     78 			} else {
     79 				functions[*f] = f
     80 				f.ID = uint64(len(mt.prof.Function)) + 1
     81 				mt.prof.Function = append(mt.prof.Function, f)
     82 			}
     83 			l.Line[i] = profile.Line{
     84 				Function: f,
     85 				Line:     int64(frame.Line),
     86 			}
     87 		}
     88 
     89 		if len(stack) > 0 {
     90 			m.HasInlineFrames = true
     91 		}
     92 	}
     93 	return nil
     94 }
     95 
     96 // newMapping creates a mappingTable for a profile.
     97 func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
     98 	mt := &mappingTable{
     99 		prof:     prof,
    100 		segments: make(map[*profile.Mapping]plugin.ObjFile),
    101 	}
    102 
    103 	// Identify used mappings
    104 	mappings := make(map[*profile.Mapping]bool)
    105 	for _, l := range prof.Location {
    106 		mappings[l.Mapping] = true
    107 	}
    108 
    109 	for _, m := range prof.Mapping {
    110 		if !mappings[m] {
    111 			continue
    112 		}
    113 		// Do not attempt to re-symbolize a mapping that has already been symbolized.
    114 		if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
    115 			continue
    116 		}
    117 
    118 		f, err := locateFile(obj, m.File, m.BuildID, m.Start)
    119 		if err != nil {
    120 			ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err)
    121 			// Move on to other mappings
    122 			continue
    123 		}
    124 
    125 		if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
    126 			// Build ID mismatch - ignore.
    127 			f.Close()
    128 			continue
    129 		}
    130 
    131 		mt.segments[m] = f
    132 	}
    133 
    134 	return mt, nil
    135 }
    136 
    137 // locateFile opens a local file for symbolization on the search path
    138 // at $PPROF_BINARY_PATH. Looks inside these directories for files
    139 // named $BUILDID/$BASENAME and $BASENAME (if build id is available).
    140 func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) {
    141 	// Construct search path to examine
    142 	searchPath := os.Getenv("PPROF_BINARY_PATH")
    143 	if searchPath == "" {
    144 		// Use $HOME/pprof/binaries as default directory for local symbolization binaries
    145 		searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries")
    146 	}
    147 
    148 	// Collect names to search: {buildid/basename, basename}
    149 	var fileNames []string
    150 	if baseName := filepath.Base(file); buildID != "" {
    151 		fileNames = []string{filepath.Join(buildID, baseName), baseName}
    152 	} else {
    153 		fileNames = []string{baseName}
    154 	}
    155 	for _, path := range filepath.SplitList(searchPath) {
    156 		for nameIndex, name := range fileNames {
    157 			file := filepath.Join(path, name)
    158 			if f, err := obj.Open(file, start); err == nil {
    159 				fileBuildID := f.BuildID()
    160 				if buildID == "" || buildID == fileBuildID {
    161 					return f, nil
    162 				}
    163 				f.Close()
    164 				if nameIndex == 0 {
    165 					// If this is the first name, the path includes the build id. Report inconsistency.
    166 					return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID)
    167 				}
    168 			}
    169 		}
    170 	}
    171 	// Try original file name
    172 	f, err := obj.Open(file, start)
    173 	if err == nil && buildID != "" {
    174 		if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID {
    175 			// Mismatched build IDs, ignore
    176 			f.Close()
    177 			return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID)
    178 		}
    179 	}
    180 	return f, err
    181 }
    182 
    183 // mappingTable contains the mechanisms for symbolization of a
    184 // profile.
    185 type mappingTable struct {
    186 	prof     *profile.Profile
    187 	segments map[*profile.Mapping]plugin.ObjFile
    188 }
    189 
    190 // Close releases any external processes being used for the mapping.
    191 func (mt *mappingTable) close() {
    192 	for _, segment := range mt.segments {
    193 		segment.Close()
    194 	}
    195 }
    196