...

Source file src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go

     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		"fmt"
    22		"io/ioutil"
    23		"net/http"
    24		"net/url"
    25		"path/filepath"
    26		"strings"
    27	
    28		"github.com/google/pprof/internal/binutils"
    29		"github.com/google/pprof/internal/plugin"
    30		"github.com/google/pprof/internal/symbolz"
    31		"github.com/google/pprof/profile"
    32		"github.com/ianlancetaylor/demangle"
    33	)
    34	
    35	// Symbolizer implements the plugin.Symbolize interface.
    36	type Symbolizer struct {
    37		Obj       plugin.ObjTool
    38		UI        plugin.UI
    39		Transport http.RoundTripper
    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			post := func(source, post string) ([]byte, error) {
    89				return postURL(source, post, s.Transport)
    90			}
    91			if err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {
    92				return err // Ran out of options.
    93			}
    94		}
    95	
    96		demangleFunction(p, force, demanglerMode)
    97		return nil
    98	}
    99	
   100	// postURL issues a POST to a URL over HTTP.
   101	func postURL(source, post string, tr http.RoundTripper) ([]byte, error) {
   102		client := &http.Client{
   103			Transport: tr,
   104		}
   105		resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
   106		if err != nil {
   107			return nil, fmt.Errorf("http post %s: %v", source, err)
   108		}
   109		defer resp.Body.Close()
   110		if resp.StatusCode != http.StatusOK {
   111			return nil, fmt.Errorf("http post %s: %v", source, statusCodeError(resp))
   112		}
   113		return ioutil.ReadAll(resp.Body)
   114	}
   115	
   116	func statusCodeError(resp *http.Response) error {
   117		if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
   118			// error is from pprof endpoint
   119			if body, err := ioutil.ReadAll(resp.Body); err == nil {
   120				return fmt.Errorf("server response: %s - %s", resp.Status, body)
   121			}
   122		}
   123		return fmt.Errorf("server response: %s", resp.Status)
   124	}
   125	
   126	// doLocalSymbolize adds symbol and line number information to all locations
   127	// in a profile. mode enables some options to control
   128	// symbolization.
   129	func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
   130		if fast {
   131			if bu, ok := obj.(*binutils.Binutils); ok {
   132				bu.SetFastSymbolization(true)
   133			}
   134		}
   135	
   136		mt, err := newMapping(prof, obj, ui, force)
   137		if err != nil {
   138			return err
   139		}
   140		defer mt.close()
   141	
   142		functions := make(map[profile.Function]*profile.Function)
   143		for _, l := range mt.prof.Location {
   144			m := l.Mapping
   145			segment := mt.segments[m]
   146			if segment == nil {
   147				// Nothing to do.
   148				continue
   149			}
   150	
   151			stack, err := segment.SourceLine(l.Address)
   152			if err != nil || len(stack) == 0 {
   153				// No answers from addr2line.
   154				continue
   155			}
   156	
   157			l.Line = make([]profile.Line, len(stack))
   158			l.IsFolded = false
   159			for i, frame := range stack {
   160				if frame.Func != "" {
   161					m.HasFunctions = true
   162				}
   163				if frame.File != "" {
   164					m.HasFilenames = true
   165				}
   166				if frame.Line != 0 {
   167					m.HasLineNumbers = true
   168				}
   169				f := &profile.Function{
   170					Name:       frame.Func,
   171					SystemName: frame.Func,
   172					Filename:   frame.File,
   173				}
   174				if fp := functions[*f]; fp != nil {
   175					f = fp
   176				} else {
   177					functions[*f] = f
   178					f.ID = uint64(len(mt.prof.Function)) + 1
   179					mt.prof.Function = append(mt.prof.Function, f)
   180				}
   181				l.Line[i] = profile.Line{
   182					Function: f,
   183					Line:     int64(frame.Line),
   184				}
   185			}
   186	
   187			if len(stack) > 0 {
   188				m.HasInlineFrames = true
   189			}
   190		}
   191	
   192		return nil
   193	}
   194	
   195	// Demangle updates the function names in a profile with demangled C++
   196	// names, simplified according to demanglerMode. If force is set,
   197	// overwrite any names that appear already demangled.
   198	func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
   199		if force {
   200			// Remove the current demangled names to force demangling
   201			for _, f := range prof.Function {
   202				if f.Name != "" && f.SystemName != "" {
   203					f.Name = f.SystemName
   204				}
   205			}
   206		}
   207	
   208		var options []demangle.Option
   209		switch demanglerMode {
   210		case "": // demangled, simplified: no parameters, no templates, no return type
   211			options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
   212		case "templates": // demangled, simplified: no parameters, no return type
   213			options = []demangle.Option{demangle.NoParams}
   214		case "full":
   215			options = []demangle.Option{demangle.NoClones}
   216		case "none": // no demangling
   217			return
   218		}
   219	
   220		// Copy the options because they may be updated by the call.
   221		o := make([]demangle.Option, len(options))
   222		for _, fn := range prof.Function {
   223			if fn.Name != "" && fn.SystemName != fn.Name {
   224				continue // Already demangled.
   225			}
   226			copy(o, options)
   227			if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
   228				fn.Name = demangled
   229				continue
   230			}
   231			// Could not demangle. Apply heuristics in case the name is
   232			// already demangled.
   233			name := fn.SystemName
   234			if looksLikeDemangledCPlusPlus(name) {
   235				if demanglerMode == "" || demanglerMode == "templates" {
   236					name = removeMatching(name, '(', ')')
   237				}
   238				if demanglerMode == "" {
   239					name = removeMatching(name, '<', '>')
   240				}
   241			}
   242			fn.Name = name
   243		}
   244	}
   245	
   246	// looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
   247	// the result of demangling C++. If so, further heuristics will be
   248	// applied to simplify the name.
   249	func looksLikeDemangledCPlusPlus(demangled string) bool {
   250		if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>"
   251			return false
   252		}
   253		return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
   254	}
   255	
   256	// removeMatching removes nested instances of start..end from name.
   257	func removeMatching(name string, start, end byte) string {
   258		s := string(start) + string(end)
   259		var nesting, first, current int
   260		for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
   261			switch current += index; name[current] {
   262			case start:
   263				nesting++
   264				if nesting == 1 {
   265					first = current
   266				}
   267			case end:
   268				nesting--
   269				switch {
   270				case nesting < 0:
   271					return name // Mismatch, abort
   272				case nesting == 0:
   273					name = name[:first] + name[current+1:]
   274					current = first - 1
   275				}
   276			}
   277			current++
   278		}
   279		return name
   280	}
   281	
   282	// newMapping creates a mappingTable for a profile.
   283	func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
   284		mt := &mappingTable{
   285			prof:     prof,
   286			segments: make(map[*profile.Mapping]plugin.ObjFile),
   287		}
   288	
   289		// Identify used mappings
   290		mappings := make(map[*profile.Mapping]bool)
   291		for _, l := range prof.Location {
   292			mappings[l.Mapping] = true
   293		}
   294	
   295		missingBinaries := false
   296		for midx, m := range prof.Mapping {
   297			if !mappings[m] {
   298				continue
   299			}
   300	
   301			// Do not attempt to re-symbolize a mapping that has already been symbolized.
   302			if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
   303				continue
   304			}
   305	
   306			if m.File == "" {
   307				if midx == 0 {
   308					ui.PrintErr("Main binary filename not available.")
   309					continue
   310				}
   311				missingBinaries = true
   312				continue
   313			}
   314	
   315			// Skip well-known system mappings
   316			if m.Unsymbolizable() {
   317				continue
   318			}
   319	
   320			// Skip mappings pointing to a source URL
   321			if m.BuildID == "" {
   322				if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
   323					continue
   324				}
   325			}
   326	
   327			name := filepath.Base(m.File)
   328			f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
   329			if err != nil {
   330				ui.PrintErr("Local symbolization failed for ", name, ": ", err)
   331				missingBinaries = true
   332				continue
   333			}
   334			if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
   335				ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
   336				f.Close()
   337				continue
   338			}
   339	
   340			mt.segments[m] = f
   341		}
   342		if missingBinaries {
   343			ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
   344				"Try setting PPROF_BINARY_PATH to the search path for local binaries.")
   345		}
   346		return mt, nil
   347	}
   348	
   349	// mappingTable contains the mechanisms for symbolization of a
   350	// profile.
   351	type mappingTable struct {
   352		prof     *profile.Profile
   353		segments map[*profile.Mapping]plugin.ObjFile
   354	}
   355	
   356	// Close releases any external processes being used for the mapping.
   357	func (mt *mappingTable) close() {
   358		for _, segment := range mt.segments {
   359			segment.Close()
   360		}
   361	}
   362	

View as plain text