Home | History | Annotate | Download | only in symbolizer
      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