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.
17package symbolz
18
19import (
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
33var (
34	symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`)
35)
36
37// Symbolize symbolizes profile p by parsing data returned by a symbolz
38// handler. syms receives the symbolz query (hex addresses separated by '+')
39// and returns the symbolz output in a string. If force is false, it will only
40// symbolize locations from mappings not already marked as HasFunctions. Never
41// attempts symbolization of addresses from unsymbolizable system
42// mappings as those may look negative - e.g. "[vsyscall]".
43func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {
44	for _, m := range p.Mapping {
45		if !force && m.HasFunctions {
46			// Only check for HasFunctions as symbolz only populates function names.
47			continue
48		}
49		// Skip well-known system mappings.
50		if m.Unsymbolizable() {
51			continue
52		}
53		mappingSources := sources[m.File]
54		if m.BuildID != "" {
55			mappingSources = append(mappingSources, sources[m.BuildID]...)
56		}
57		for _, source := range mappingSources {
58			if symz := symbolz(source.Source); symz != "" {
59				if err := symbolizeMapping(symz, int64(source.Start)-int64(m.Start), syms, m, p); err != nil {
60					return err
61				}
62				m.HasFunctions = true
63				break
64			}
65		}
66	}
67
68	return nil
69}
70
71// hasGperftoolsSuffix checks whether path ends with one of the suffixes listed in
72// pprof_remote_servers.html from the gperftools distribution
73func hasGperftoolsSuffix(path string) bool {
74	suffixes := []string{
75		"/pprof/heap",
76		"/pprof/growth",
77		"/pprof/profile",
78		"/pprof/pmuprofile",
79		"/pprof/contention",
80	}
81	for _, s := range suffixes {
82		if strings.HasSuffix(path, s) {
83			return true
84		}
85	}
86	return false
87}
88
89// symbolz returns the corresponding symbolz source for a profile URL.
90func symbolz(source string) string {
91	if url, err := url.Parse(source); err == nil && url.Host != "" {
92		// All paths in the net/http/pprof Go package contain /debug/pprof/
93		if strings.Contains(url.Path, "/debug/pprof/") || hasGperftoolsSuffix(url.Path) {
94			url.Path = path.Clean(url.Path + "/../symbol")
95		} else {
96			url.Path = "/symbolz"
97		}
98		url.RawQuery = ""
99		return url.String()
100	}
101
102	return ""
103}
104
105// symbolizeMapping symbolizes locations belonging to a Mapping by querying
106// a symbolz handler. An offset is applied to all addresses to take care of
107// normalization occurred for merged Mappings.
108func symbolizeMapping(source string, offset int64, syms func(string, string) ([]byte, error), m *profile.Mapping, p *profile.Profile) error {
109	// Construct query of addresses to symbolize.
110	var a []string
111	for _, l := range p.Location {
112		if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 {
113			// Compensate for normalization.
114			addr, overflow := adjust(l.Address, offset)
115			if overflow {
116				return fmt.Errorf("cannot adjust address %d by %d, it would overflow (mapping %v)", l.Address, offset, l.Mapping)
117			}
118			a = append(a, fmt.Sprintf("%#x", addr))
119		}
120	}
121
122	if len(a) == 0 {
123		// No addresses to symbolize.
124		return nil
125	}
126
127	lines := make(map[uint64]profile.Line)
128	functions := make(map[string]*profile.Function)
129
130	b, err := syms(source, strings.Join(a, "+"))
131	if err != nil {
132		return err
133	}
134
135	buf := bytes.NewBuffer(b)
136	for {
137		l, err := buf.ReadString('\n')
138
139		if err != nil {
140			if err == io.EOF {
141				break
142			}
143			return err
144		}
145
146		if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
147			origAddr, err := strconv.ParseUint(symbol[1], 0, 64)
148			if err != nil {
149				return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
150			}
151			// Reapply offset expected by the profile.
152			addr, overflow := adjust(origAddr, -offset)
153			if overflow {
154				return fmt.Errorf("cannot adjust symbolz address %d by %d, it would overflow", origAddr, -offset)
155			}
156
157			name := symbol[2]
158			fn := functions[name]
159			if fn == nil {
160				fn = &profile.Function{
161					ID:         uint64(len(p.Function) + 1),
162					Name:       name,
163					SystemName: name,
164				}
165				functions[name] = fn
166				p.Function = append(p.Function, fn)
167			}
168
169			lines[addr] = profile.Line{Function: fn}
170		}
171	}
172
173	for _, l := range p.Location {
174		if l.Mapping != m {
175			continue
176		}
177		if line, ok := lines[l.Address]; ok {
178			l.Line = []profile.Line{line}
179		}
180	}
181
182	return nil
183}
184
185// adjust shifts the specified address by the signed offset. It returns the
186// adjusted address. It signals that the address cannot be adjusted without an
187// overflow by returning true in the second return value.
188func adjust(addr uint64, offset int64) (uint64, bool) {
189	adj := uint64(int64(addr) + offset)
190	if offset < 0 {
191		if adj >= addr {
192			return 0, true
193		}
194	} else {
195		if adj < addr {
196			return 0, true
197		}
198	}
199	return adj, false
200}
201