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"
    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 io.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 := io.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  	options := demanglerModeToOptions(demanglerMode)
   209  	for _, fn := range prof.Function {
   210  		demangleSingleFunction(fn, options)
   211  	}
   212  }
   213  
   214  func demanglerModeToOptions(demanglerMode string) []demangle.Option {
   215  	switch demanglerMode {
   216  	case "": // demangled, simplified: no parameters, no templates, no return type
   217  		return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams}
   218  	case "templates": // demangled, simplified: no parameters, no return type
   219  		return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams}
   220  	case "full":
   221  		return []demangle.Option{demangle.NoClones}
   222  	case "none": // no demangling
   223  		return []demangle.Option{}
   224  	}
   225  
   226  	panic(fmt.Sprintf("unknown demanglerMode %s", demanglerMode))
   227  }
   228  
   229  func demangleSingleFunction(fn *profile.Function, options []demangle.Option) {
   230  	if fn.Name != "" && fn.SystemName != fn.Name {
   231  		return // Already demangled.
   232  	}
   233  	// Copy the options because they may be updated by the call.
   234  	o := make([]demangle.Option, len(options))
   235  	copy(o, options)
   236  	if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
   237  		fn.Name = demangled
   238  		return
   239  	}
   240  	// Could not demangle. Apply heuristics in case the name is
   241  	// already demangled.
   242  	name := fn.SystemName
   243  	if looksLikeDemangledCPlusPlus(name) {
   244  		for _, o := range options {
   245  			switch o {
   246  			case demangle.NoParams:
   247  				name = removeMatching(name, '(', ')')
   248  			case demangle.NoTemplateParams:
   249  				name = removeMatching(name, '<', '>')
   250  			}
   251  		}
   252  	}
   253  	fn.Name = name
   254  }
   255  
   256  // looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
   257  // the result of demangling C++. If so, further heuristics will be
   258  // applied to simplify the name.
   259  func looksLikeDemangledCPlusPlus(demangled string) bool {
   260  	// Skip java names of the form "class.<init>".
   261  	if strings.Contains(demangled, ".<") {
   262  		return false
   263  	}
   264  	// Skip Go names of the form "foo.(*Bar[...]).Method".
   265  	if strings.Contains(demangled, "]).") {
   266  		return false
   267  	}
   268  	return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
   269  }
   270  
   271  // removeMatching removes nested instances of start..end from name.
   272  func removeMatching(name string, start, end byte) string {
   273  	s := string(start) + string(end)
   274  	var nesting, first, current int
   275  	for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
   276  		switch current += index; name[current] {
   277  		case start:
   278  			nesting++
   279  			if nesting == 1 {
   280  				first = current
   281  			}
   282  		case end:
   283  			nesting--
   284  			switch {
   285  			case nesting < 0:
   286  				return name // Mismatch, abort
   287  			case nesting == 0:
   288  				name = name[:first] + name[current+1:]
   289  				current = first - 1
   290  			}
   291  		}
   292  		current++
   293  	}
   294  	return name
   295  }
   296  
   297  // newMapping creates a mappingTable for a profile.
   298  func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
   299  	mt := &mappingTable{
   300  		prof:     prof,
   301  		segments: make(map[*profile.Mapping]plugin.ObjFile),
   302  	}
   303  
   304  	// Identify used mappings
   305  	mappings := make(map[*profile.Mapping]bool)
   306  	for _, l := range prof.Location {
   307  		mappings[l.Mapping] = true
   308  	}
   309  
   310  	missingBinaries := false
   311  	for midx, m := range prof.Mapping {
   312  		if !mappings[m] {
   313  			continue
   314  		}
   315  
   316  		// Do not attempt to re-symbolize a mapping that has already been symbolized.
   317  		if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
   318  			continue
   319  		}
   320  
   321  		if m.File == "" {
   322  			if midx == 0 {
   323  				ui.PrintErr("Main binary filename not available.")
   324  				continue
   325  			}
   326  			missingBinaries = true
   327  			continue
   328  		}
   329  
   330  		// Skip well-known system mappings
   331  		if m.Unsymbolizable() {
   332  			continue
   333  		}
   334  
   335  		// Skip mappings pointing to a source URL
   336  		if m.BuildID == "" {
   337  			if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
   338  				continue
   339  			}
   340  		}
   341  
   342  		name := filepath.Base(m.File)
   343  		if m.BuildID != "" {
   344  			name += fmt.Sprintf(" (build ID %s)", m.BuildID)
   345  		}
   346  		f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
   347  		if err != nil {
   348  			ui.PrintErr("Local symbolization failed for ", name, ": ", err)
   349  			missingBinaries = true
   350  			continue
   351  		}
   352  		if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
   353  			ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
   354  			f.Close()
   355  			continue
   356  		}
   357  
   358  		mt.segments[m] = f
   359  	}
   360  	if missingBinaries {
   361  		ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
   362  			"Try setting PPROF_BINARY_PATH to the search path for local binaries.")
   363  	}
   364  	return mt, nil
   365  }
   366  
   367  // mappingTable contains the mechanisms for symbolization of a
   368  // profile.
   369  type mappingTable struct {
   370  	prof     *profile.Profile
   371  	segments map[*profile.Mapping]plugin.ObjFile
   372  }
   373  
   374  // close releases any external processes being used for the mapping.
   375  func (mt *mappingTable) close() {
   376  	for _, segment := range mt.segments {
   377  		segment.Close()
   378  	}
   379  }
   380  

View as plain text