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