Home | History | Annotate | Download | only in symbolz
      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 symbolz symbolizes a profile using the output from the symbolz
     16 // service.
     17 package symbolz
     18 
     19 import (
     20 	"bytes"
     21 	"fmt"
     22 	"io"
     23 	"net/url"
     24 	"path"
     25 	"regexp"
     26 	"strconv"
     27 	"strings"
     28 
     29 	"github.com/google/pprof/internal/plugin"
     30 	"github.com/google/pprof/profile"
     31 )
     32 
     33 var (
     34 	symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`)
     35 )
     36 
     37 // Symbolize symbolizes profile p by parsing data returned by a
     38 // symbolz handler. syms receives the symbolz query (hex addresses
     39 // separated by '+') and returns the symbolz output in a string. If
     40 // force is false, it will only symbolize locations from mappings
     41 // not already marked as HasFunctions.
     42 func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {
     43 	for _, m := range p.Mapping {
     44 		if !force && m.HasFunctions {
     45 			// Only check for HasFunctions as symbolz only populates function names.
     46 			continue
     47 		}
     48 		mappingSources := sources[m.File]
     49 		if m.BuildID != "" {
     50 			mappingSources = append(mappingSources, sources[m.BuildID]...)
     51 		}
     52 		for _, source := range mappingSources {
     53 			if symz := symbolz(source.Source); symz != "" {
     54 				if err := symbolizeMapping(symz, int64(source.Start)-int64(m.Start), syms, m, p); err != nil {
     55 					return err
     56 				}
     57 				m.HasFunctions = true
     58 				break
     59 			}
     60 		}
     61 	}
     62 
     63 	return nil
     64 }
     65 
     66 // symbolz returns the corresponding symbolz source for a profile URL.
     67 func symbolz(source string) string {
     68 	if url, err := url.Parse(source); err == nil && url.Host != "" {
     69 		if strings.Contains(url.Path, "/debug/pprof/") {
     70 			url.Path = path.Clean(url.Path + "/../symbol")
     71 		} else {
     72 			url.Path = "/symbolz"
     73 		}
     74 		url.RawQuery = ""
     75 		return url.String()
     76 	}
     77 
     78 	return ""
     79 }
     80 
     81 // symbolizeMapping symbolizes locations belonging to a Mapping by querying
     82 // a symbolz handler. An offset is applied to all addresses to take care of
     83 // normalization occurred for merged Mappings.
     84 func symbolizeMapping(source string, offset int64, syms func(string, string) ([]byte, error), m *profile.Mapping, p *profile.Profile) error {
     85 	// Construct query of addresses to symbolize.
     86 	var a []string
     87 	for _, l := range p.Location {
     88 		if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 {
     89 			// Compensate for normalization.
     90 			addr := int64(l.Address) + offset
     91 			if addr < 0 {
     92 				return fmt.Errorf("unexpected negative adjusted address, mapping %v source %d, offset %d", l.Mapping, l.Address, offset)
     93 			}
     94 			a = append(a, fmt.Sprintf("%#x", addr))
     95 		}
     96 	}
     97 
     98 	if len(a) == 0 {
     99 		// No addresses to symbolize.
    100 		return nil
    101 	}
    102 
    103 	lines := make(map[uint64]profile.Line)
    104 	functions := make(map[string]*profile.Function)
    105 
    106 	b, err := syms(source, strings.Join(a, "+"))
    107 	if err != nil {
    108 		return err
    109 	}
    110 
    111 	buf := bytes.NewBuffer(b)
    112 	for {
    113 		l, err := buf.ReadString('\n')
    114 
    115 		if err != nil {
    116 			if err == io.EOF {
    117 				break
    118 			}
    119 			return err
    120 		}
    121 
    122 		if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
    123 			addr, err := strconv.ParseInt(symbol[1], 0, 64)
    124 			if err != nil {
    125 				return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
    126 			}
    127 			if addr < 0 {
    128 				return fmt.Errorf("unexpected negative adjusted address, source %s, offset %d", symbol[1], offset)
    129 			}
    130 			// Reapply offset expected by the profile.
    131 			addr -= offset
    132 
    133 			name := symbol[2]
    134 			fn := functions[name]
    135 			if fn == nil {
    136 				fn = &profile.Function{
    137 					ID:         uint64(len(p.Function) + 1),
    138 					Name:       name,
    139 					SystemName: name,
    140 				}
    141 				functions[name] = fn
    142 				p.Function = append(p.Function, fn)
    143 			}
    144 
    145 			lines[uint64(addr)] = profile.Line{Function: fn}
    146 		}
    147 	}
    148 
    149 	for _, l := range p.Location {
    150 		if l.Mapping != m {
    151 			continue
    152 		}
    153 		if line, ok := lines[l.Address]; ok {
    154 			l.Line = []profile.Line{line}
    155 		}
    156 	}
    157 
    158 	return nil
    159 }
    160