golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go (about)

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