mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2020-11-18 19:53:40 -08:00
362 lines
9.9 KiB
Go
362 lines
9.9 KiB
Go
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package symbolizer provides a routine to populate a profile with
|
|
// symbol, file and line number information. It relies on the
|
|
// addr2liner and demangle packages to do the actual work.
|
|
package symbolizer
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/google/pprof/internal/binutils"
|
|
"github.com/google/pprof/internal/plugin"
|
|
"github.com/google/pprof/internal/symbolz"
|
|
"github.com/google/pprof/profile"
|
|
"github.com/ianlancetaylor/demangle"
|
|
)
|
|
|
|
// Symbolizer implements the plugin.Symbolize interface.
|
|
type Symbolizer struct {
|
|
Obj plugin.ObjTool
|
|
UI plugin.UI
|
|
Transport http.RoundTripper
|
|
}
|
|
|
|
// test taps for dependency injection
|
|
var symbolzSymbolize = symbolz.Symbolize
|
|
var localSymbolize = doLocalSymbolize
|
|
var demangleFunction = Demangle
|
|
|
|
// Symbolize attempts to symbolize profile p. First uses binutils on
|
|
// local binaries; if the source is a URL it attempts to get any
|
|
// missed entries using symbolz.
|
|
func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error {
|
|
remote, local, fast, force, demanglerMode := true, true, false, false, ""
|
|
for _, o := range strings.Split(strings.ToLower(mode), ":") {
|
|
switch o {
|
|
case "":
|
|
continue
|
|
case "none", "no":
|
|
return nil
|
|
case "local":
|
|
remote, local = false, true
|
|
case "fastlocal":
|
|
remote, local, fast = false, true, true
|
|
case "remote":
|
|
remote, local = true, false
|
|
case "force":
|
|
force = true
|
|
default:
|
|
switch d := strings.TrimPrefix(o, "demangle="); d {
|
|
case "full", "none", "templates":
|
|
demanglerMode = d
|
|
force = true
|
|
continue
|
|
case "default":
|
|
continue
|
|
}
|
|
s.UI.PrintErr("ignoring unrecognized symbolization option: " + mode)
|
|
s.UI.PrintErr("expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]")
|
|
}
|
|
}
|
|
|
|
var err error
|
|
if local {
|
|
// Symbolize locally using binutils.
|
|
if err = localSymbolize(p, fast, force, s.Obj, s.UI); err != nil {
|
|
s.UI.PrintErr("local symbolization: " + err.Error())
|
|
}
|
|
}
|
|
if remote {
|
|
post := func(source, post string) ([]byte, error) {
|
|
return postURL(source, post, s.Transport)
|
|
}
|
|
if err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {
|
|
return err // Ran out of options.
|
|
}
|
|
}
|
|
|
|
demangleFunction(p, force, demanglerMode)
|
|
return nil
|
|
}
|
|
|
|
// postURL issues a POST to a URL over HTTP.
|
|
func postURL(source, post string, tr http.RoundTripper) ([]byte, error) {
|
|
client := &http.Client{
|
|
Transport: tr,
|
|
}
|
|
resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("http post %s: %v", source, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("http post %s: %v", source, statusCodeError(resp))
|
|
}
|
|
return ioutil.ReadAll(resp.Body)
|
|
}
|
|
|
|
func statusCodeError(resp *http.Response) error {
|
|
if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
|
|
// error is from pprof endpoint
|
|
if body, err := ioutil.ReadAll(resp.Body); err == nil {
|
|
return fmt.Errorf("server response: %s - %s", resp.Status, body)
|
|
}
|
|
}
|
|
return fmt.Errorf("server response: %s", resp.Status)
|
|
}
|
|
|
|
// doLocalSymbolize adds symbol and line number information to all locations
|
|
// in a profile. mode enables some options to control
|
|
// symbolization.
|
|
func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
|
|
if fast {
|
|
if bu, ok := obj.(*binutils.Binutils); ok {
|
|
bu.SetFastSymbolization(true)
|
|
}
|
|
}
|
|
|
|
mt, err := newMapping(prof, obj, ui, force)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer mt.close()
|
|
|
|
functions := make(map[profile.Function]*profile.Function)
|
|
for _, l := range mt.prof.Location {
|
|
m := l.Mapping
|
|
segment := mt.segments[m]
|
|
if segment == nil {
|
|
// Nothing to do.
|
|
continue
|
|
}
|
|
|
|
stack, err := segment.SourceLine(l.Address)
|
|
if err != nil || len(stack) == 0 {
|
|
// No answers from addr2line.
|
|
continue
|
|
}
|
|
|
|
l.Line = make([]profile.Line, len(stack))
|
|
l.IsFolded = false
|
|
for i, frame := range stack {
|
|
if frame.Func != "" {
|
|
m.HasFunctions = true
|
|
}
|
|
if frame.File != "" {
|
|
m.HasFilenames = true
|
|
}
|
|
if frame.Line != 0 {
|
|
m.HasLineNumbers = true
|
|
}
|
|
f := &profile.Function{
|
|
Name: frame.Func,
|
|
SystemName: frame.Func,
|
|
Filename: frame.File,
|
|
}
|
|
if fp := functions[*f]; fp != nil {
|
|
f = fp
|
|
} else {
|
|
functions[*f] = f
|
|
f.ID = uint64(len(mt.prof.Function)) + 1
|
|
mt.prof.Function = append(mt.prof.Function, f)
|
|
}
|
|
l.Line[i] = profile.Line{
|
|
Function: f,
|
|
Line: int64(frame.Line),
|
|
}
|
|
}
|
|
|
|
if len(stack) > 0 {
|
|
m.HasInlineFrames = true
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Demangle updates the function names in a profile with demangled C++
|
|
// names, simplified according to demanglerMode. If force is set,
|
|
// overwrite any names that appear already demangled.
|
|
func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
|
|
if force {
|
|
// Remove the current demangled names to force demangling
|
|
for _, f := range prof.Function {
|
|
if f.Name != "" && f.SystemName != "" {
|
|
f.Name = f.SystemName
|
|
}
|
|
}
|
|
}
|
|
|
|
var options []demangle.Option
|
|
switch demanglerMode {
|
|
case "": // demangled, simplified: no parameters, no templates, no return type
|
|
options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
|
|
case "templates": // demangled, simplified: no parameters, no return type
|
|
options = []demangle.Option{demangle.NoParams}
|
|
case "full":
|
|
options = []demangle.Option{demangle.NoClones}
|
|
case "none": // no demangling
|
|
return
|
|
}
|
|
|
|
// Copy the options because they may be updated by the call.
|
|
o := make([]demangle.Option, len(options))
|
|
for _, fn := range prof.Function {
|
|
if fn.Name != "" && fn.SystemName != fn.Name {
|
|
continue // Already demangled.
|
|
}
|
|
copy(o, options)
|
|
if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
|
|
fn.Name = demangled
|
|
continue
|
|
}
|
|
// Could not demangle. Apply heuristics in case the name is
|
|
// already demangled.
|
|
name := fn.SystemName
|
|
if looksLikeDemangledCPlusPlus(name) {
|
|
if demanglerMode == "" || demanglerMode == "templates" {
|
|
name = removeMatching(name, '(', ')')
|
|
}
|
|
if demanglerMode == "" {
|
|
name = removeMatching(name, '<', '>')
|
|
}
|
|
}
|
|
fn.Name = name
|
|
}
|
|
}
|
|
|
|
// looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
|
|
// the result of demangling C++. If so, further heuristics will be
|
|
// applied to simplify the name.
|
|
func looksLikeDemangledCPlusPlus(demangled string) bool {
|
|
if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>"
|
|
return false
|
|
}
|
|
return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
|
|
}
|
|
|
|
// removeMatching removes nested instances of start..end from name.
|
|
func removeMatching(name string, start, end byte) string {
|
|
s := string(start) + string(end)
|
|
var nesting, first, current int
|
|
for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
|
|
switch current += index; name[current] {
|
|
case start:
|
|
nesting++
|
|
if nesting == 1 {
|
|
first = current
|
|
}
|
|
case end:
|
|
nesting--
|
|
switch {
|
|
case nesting < 0:
|
|
return name // Mismatch, abort
|
|
case nesting == 0:
|
|
name = name[:first] + name[current+1:]
|
|
current = first - 1
|
|
}
|
|
}
|
|
current++
|
|
}
|
|
return name
|
|
}
|
|
|
|
// newMapping creates a mappingTable for a profile.
|
|
func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
|
|
mt := &mappingTable{
|
|
prof: prof,
|
|
segments: make(map[*profile.Mapping]plugin.ObjFile),
|
|
}
|
|
|
|
// Identify used mappings
|
|
mappings := make(map[*profile.Mapping]bool)
|
|
for _, l := range prof.Location {
|
|
mappings[l.Mapping] = true
|
|
}
|
|
|
|
missingBinaries := false
|
|
for midx, m := range prof.Mapping {
|
|
if !mappings[m] {
|
|
continue
|
|
}
|
|
|
|
// Do not attempt to re-symbolize a mapping that has already been symbolized.
|
|
if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
|
|
continue
|
|
}
|
|
|
|
if m.File == "" {
|
|
if midx == 0 {
|
|
ui.PrintErr("Main binary filename not available.")
|
|
continue
|
|
}
|
|
missingBinaries = true
|
|
continue
|
|
}
|
|
|
|
// Skip well-known system mappings
|
|
if m.Unsymbolizable() {
|
|
continue
|
|
}
|
|
|
|
// Skip mappings pointing to a source URL
|
|
if m.BuildID == "" {
|
|
if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
|
|
continue
|
|
}
|
|
}
|
|
|
|
name := filepath.Base(m.File)
|
|
f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
|
|
if err != nil {
|
|
ui.PrintErr("Local symbolization failed for ", name, ": ", err)
|
|
missingBinaries = true
|
|
continue
|
|
}
|
|
if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
|
|
ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
|
|
f.Close()
|
|
continue
|
|
}
|
|
|
|
mt.segments[m] = f
|
|
}
|
|
if missingBinaries {
|
|
ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
|
|
"Try setting PPROF_BINARY_PATH to the search path for local binaries.")
|
|
}
|
|
return mt, nil
|
|
}
|
|
|
|
// mappingTable contains the mechanisms for symbolization of a
|
|
// profile.
|
|
type mappingTable struct {
|
|
prof *profile.Profile
|
|
segments map[*profile.Mapping]plugin.ObjFile
|
|
}
|
|
|
|
// Close releases any external processes being used for the mapping.
|
|
func (mt *mappingTable) close() {
|
|
for _, segment := range mt.segments {
|
|
segment.Close()
|
|
}
|
|
}
|