1 // Copyright 2014 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package symbolizer provides a routine to populate a profile with 16 // symbol, file and line number information. It relies on the 17 // addr2liner and demangle packages to do the actual work. 18 package symbolizer 19 20 import ( 21 "crypto/tls" 22 "fmt" 23 "io/ioutil" 24 "net/http" 25 "net/url" 26 "path/filepath" 27 "strings" 28 29 "github.com/google/pprof/internal/binutils" 30 "github.com/google/pprof/internal/plugin" 31 "github.com/google/pprof/internal/symbolz" 32 "github.com/google/pprof/profile" 33 "github.com/ianlancetaylor/demangle" 34 ) 35 36 // Symbolizer implements the plugin.Symbolize interface. 37 type Symbolizer struct { 38 Obj plugin.ObjTool 39 UI plugin.UI 40 } 41 42 // test taps for dependency injection 43 var symbolzSymbolize = symbolz.Symbolize 44 var localSymbolize = doLocalSymbolize 45 var demangleFunction = Demangle 46 47 // Symbolize attempts to symbolize profile p. First uses binutils on 48 // local binaries; if the source is a URL it attempts to get any 49 // missed entries using symbolz. 50 func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error { 51 remote, local, fast, force, demanglerMode := true, true, false, false, "" 52 for _, o := range strings.Split(strings.ToLower(mode), ":") { 53 switch o { 54 case "": 55 continue 56 case "none", "no": 57 return nil 58 case "local": 59 remote, local = false, true 60 case "fastlocal": 61 remote, local, fast = false, true, true 62 case "remote": 63 remote, local = true, false 64 case "force": 65 force = true 66 default: 67 switch d := strings.TrimPrefix(o, "demangle="); d { 68 case "full", "none", "templates": 69 demanglerMode = d 70 force = true 71 continue 72 case "default": 73 continue 74 } 75 s.UI.PrintErr("ignoring unrecognized symbolization option: " + mode) 76 s.UI.PrintErr("expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]") 77 } 78 } 79 80 var err error 81 if local { 82 // Symbolize locally using binutils. 83 if err = localSymbolize(p, fast, force, s.Obj, s.UI); err != nil { 84 s.UI.PrintErr("local symbolization: " + err.Error()) 85 } 86 } 87 if remote { 88 if err = symbolzSymbolize(p, force, sources, postURL, s.UI); err != nil { 89 return err // Ran out of options. 90 } 91 } 92 93 demangleFunction(p, force, demanglerMode) 94 return nil 95 } 96 97 // postURL issues a POST to a URL over HTTP. 98 func postURL(source, post string) ([]byte, error) { 99 url, err := url.Parse(source) 100 if err != nil { 101 return nil, err 102 } 103 104 var tlsConfig *tls.Config 105 if url.Scheme == "https+insecure" { 106 tlsConfig = &tls.Config{ 107 InsecureSkipVerify: true, 108 } 109 url.Scheme = "https" 110 source = url.String() 111 } 112 113 client := &http.Client{ 114 Transport: &http.Transport{ 115 TLSClientConfig: tlsConfig, 116 }, 117 } 118 resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post)) 119 if err != nil { 120 return nil, fmt.Errorf("http post %s: %v", source, err) 121 } 122 defer resp.Body.Close() 123 if resp.StatusCode != http.StatusOK { 124 return nil, fmt.Errorf("http post %s: %v", source, statusCodeError(resp)) 125 } 126 return ioutil.ReadAll(resp.Body) 127 } 128 129 func statusCodeError(resp *http.Response) error { 130 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") { 131 // error is from pprof endpoint 132 if body, err := ioutil.ReadAll(resp.Body); err == nil { 133 return fmt.Errorf("server response: %s - %s", resp.Status, body) 134 } 135 } 136 return fmt.Errorf("server response: %s", resp.Status) 137 } 138 139 // doLocalSymbolize adds symbol and line number information to all locations 140 // in a profile. mode enables some options to control 141 // symbolization. 142 func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error { 143 if fast { 144 if bu, ok := obj.(*binutils.Binutils); ok { 145 bu.SetFastSymbolization(true) 146 } 147 } 148 149 mt, err := newMapping(prof, obj, ui, force) 150 if err != nil { 151 return err 152 } 153 defer mt.close() 154 155 functions := make(map[profile.Function]*profile.Function) 156 for _, l := range mt.prof.Location { 157 m := l.Mapping 158 segment := mt.segments[m] 159 if segment == nil { 160 // Nothing to do. 161 continue 162 } 163 164 stack, err := segment.SourceLine(l.Address) 165 if err != nil || len(stack) == 0 { 166 // No answers from addr2line. 167 continue 168 } 169 170 l.Line = make([]profile.Line, len(stack)) 171 for i, frame := range stack { 172 if frame.Func != "" { 173 m.HasFunctions = true 174 } 175 if frame.File != "" { 176 m.HasFilenames = true 177 } 178 if frame.Line != 0 { 179 m.HasLineNumbers = true 180 } 181 f := &profile.Function{ 182 Name: frame.Func, 183 SystemName: frame.Func, 184 Filename: frame.File, 185 } 186 if fp := functions[*f]; fp != nil { 187 f = fp 188 } else { 189 functions[*f] = f 190 f.ID = uint64(len(mt.prof.Function)) + 1 191 mt.prof.Function = append(mt.prof.Function, f) 192 } 193 l.Line[i] = profile.Line{ 194 Function: f, 195 Line: int64(frame.Line), 196 } 197 } 198 199 if len(stack) > 0 { 200 m.HasInlineFrames = true 201 } 202 } 203 204 return nil 205 } 206 207 // Demangle updates the function names in a profile with demangled C++ 208 // names, simplified according to demanglerMode. If force is set, 209 // overwrite any names that appear already demangled. 210 func Demangle(prof *profile.Profile, force bool, demanglerMode string) { 211 if force { 212 // Remove the current demangled names to force demangling 213 for _, f := range prof.Function { 214 if f.Name != "" && f.SystemName != "" { 215 f.Name = f.SystemName 216 } 217 } 218 } 219 220 var options []demangle.Option 221 switch demanglerMode { 222 case "": // demangled, simplified: no parameters, no templates, no return type 223 options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams} 224 case "templates": // demangled, simplified: no parameters, no return type 225 options = []demangle.Option{demangle.NoParams} 226 case "full": 227 options = []demangle.Option{demangle.NoClones} 228 case "none": // no demangling 229 return 230 } 231 232 // Copy the options because they may be updated by the call. 233 o := make([]demangle.Option, len(options)) 234 for _, fn := range prof.Function { 235 if fn.Name != "" && fn.SystemName != fn.Name { 236 continue // Already demangled. 237 } 238 copy(o, options) 239 if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName { 240 fn.Name = demangled 241 continue 242 } 243 // Could not demangle. Apply heuristics in case the name is 244 // already demangled. 245 name := fn.SystemName 246 if looksLikeDemangledCPlusPlus(name) { 247 if demanglerMode == "" || demanglerMode == "templates" { 248 name = removeMatching(name, '(', ')') 249 } 250 if demanglerMode == "" { 251 name = removeMatching(name, '<', '>') 252 } 253 } 254 fn.Name = name 255 } 256 } 257 258 // looksLikeDemangledCPlusPlus is a heuristic to decide if a name is 259 // the result of demangling C++. If so, further heuristics will be 260 // applied to simplify the name. 261 func looksLikeDemangledCPlusPlus(demangled string) bool { 262 if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>" 263 return false 264 } 265 return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::") 266 } 267 268 // removeMatching removes nested instances of start..end from name. 269 func removeMatching(name string, start, end byte) string { 270 s := string(start) + string(end) 271 var nesting, first, current int 272 for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) { 273 switch current += index; name[current] { 274 case start: 275 nesting++ 276 if nesting == 1 { 277 first = current 278 } 279 case end: 280 nesting-- 281 switch { 282 case nesting < 0: 283 return name // Mismatch, abort 284 case nesting == 0: 285 name = name[:first] + name[current+1:] 286 current = first - 1 287 } 288 } 289 current++ 290 } 291 return name 292 } 293 294 // newMapping creates a mappingTable for a profile. 295 func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) { 296 mt := &mappingTable{ 297 prof: prof, 298 segments: make(map[*profile.Mapping]plugin.ObjFile), 299 } 300 301 // Identify used mappings 302 mappings := make(map[*profile.Mapping]bool) 303 for _, l := range prof.Location { 304 mappings[l.Mapping] = true 305 } 306 307 missingBinaries := false 308 for midx, m := range prof.Mapping { 309 if !mappings[m] { 310 continue 311 } 312 313 // Do not attempt to re-symbolize a mapping that has already been symbolized. 314 if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) { 315 continue 316 } 317 318 if m.File == "" { 319 if midx == 0 { 320 ui.PrintErr("Main binary filename not available.") 321 continue 322 } 323 missingBinaries = true 324 continue 325 } 326 327 // Skip well-known system mappings 328 if m.Unsymbolizable() { 329 continue 330 } 331 332 // Skip mappings pointing to a source URL 333 if m.BuildID == "" { 334 if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") { 335 continue 336 } 337 } 338 339 name := filepath.Base(m.File) 340 f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset) 341 if err != nil { 342 ui.PrintErr("Local symbolization failed for ", name, ": ", err) 343 missingBinaries = true 344 continue 345 } 346 if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { 347 ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch") 348 f.Close() 349 continue 350 } 351 352 mt.segments[m] = f 353 } 354 if missingBinaries { 355 ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" + 356 "Try setting PPROF_BINARY_PATH to the search path for local binaries.") 357 } 358 return mt, nil 359 } 360 361 // mappingTable contains the mechanisms for symbolization of a 362 // profile. 363 type mappingTable struct { 364 prof *profile.Profile 365 segments map[*profile.Mapping]plugin.ObjFile 366 } 367 368 // Close releases any external processes being used for the mapping. 369 func (mt *mappingTable) close() { 370 for _, segment := range mt.segments { 371 segment.Close() 372 } 373 } 374