1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
36 type Symbolizer struct {
37 Obj plugin.ObjTool
38 UI plugin.UI
39 Transport http.RoundTripper
40 }
41
42
43 var symbolzSymbolize = symbolz.Symbolize
44 var localSymbolize = doLocalSymbolize
45 var demangleFunction = Demangle
46
47
48
49
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
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
93 }
94 }
95
96 demangleFunction(p, force, demanglerMode)
97 return nil
98 }
99
100
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
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
127
128
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 functions := map[profile.Function]*profile.Function{}
137 addFunction := func(f *profile.Function) *profile.Function {
138 if fp := functions[*f]; fp != nil {
139 return fp
140 }
141 functions[*f] = f
142 f.ID = uint64(len(prof.Function)) + 1
143 prof.Function = append(prof.Function, f)
144 return f
145 }
146
147 missingBinaries := false
148 mappingLocs := map[*profile.Mapping][]*profile.Location{}
149 for _, l := range prof.Location {
150 mappingLocs[l.Mapping] = append(mappingLocs[l.Mapping], l)
151 }
152 for midx, m := range prof.Mapping {
153 locs := mappingLocs[m]
154 if len(locs) == 0 {
155
156 continue
157 }
158
159 if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
160 continue
161 }
162 if m.File == "" {
163 if midx == 0 {
164 ui.PrintErr("Main binary filename not available.")
165 continue
166 }
167 missingBinaries = true
168 continue
169 }
170 if m.Unsymbolizable() {
171
172 continue
173 }
174 if m.BuildID == "" {
175 if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
176
177 continue
178 }
179 }
180
181 name := filepath.Base(m.File)
182 if m.BuildID != "" {
183 name += fmt.Sprintf(" (build ID %s)", m.BuildID)
184 }
185 f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
186 if err != nil {
187 ui.PrintErr("Local symbolization failed for ", name, ": ", err)
188 missingBinaries = true
189 continue
190 }
191 if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
192 ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
193 f.Close()
194 continue
195 }
196 symbolizeOneMapping(m, locs, f, addFunction)
197 f.Close()
198 }
199
200 if missingBinaries {
201 ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
202 "Try setting PPROF_BINARY_PATH to the search path for local binaries.")
203 }
204 return nil
205 }
206
207 func symbolizeOneMapping(m *profile.Mapping, locs []*profile.Location, obj plugin.ObjFile, addFunction func(*profile.Function) *profile.Function) {
208 for _, l := range locs {
209 stack, err := obj.SourceLine(l.Address)
210 if err != nil || len(stack) == 0 {
211
212 continue
213 }
214
215 l.Line = make([]profile.Line, len(stack))
216 l.IsFolded = false
217 for i, frame := range stack {
218 if frame.Func != "" {
219 m.HasFunctions = true
220 }
221 if frame.File != "" {
222 m.HasFilenames = true
223 }
224 if frame.Line != 0 {
225 m.HasLineNumbers = true
226 }
227 f := addFunction(&profile.Function{
228 Name: frame.Func,
229 SystemName: frame.Func,
230 Filename: frame.File,
231 StartLine: int64(frame.StartLine),
232 })
233 l.Line[i] = profile.Line{
234 Function: f,
235 Line: int64(frame.Line),
236 Column: int64(frame.Column),
237 }
238 }
239
240 if len(stack) > 0 {
241 m.HasInlineFrames = true
242 }
243 }
244 }
245
246
247
248
249 func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
250 if force {
251
252 for _, f := range prof.Function {
253 if f.Name != "" && f.SystemName != "" {
254 f.Name = f.SystemName
255 }
256 }
257 }
258
259 options := demanglerModeToOptions(demanglerMode)
260 for _, fn := range prof.Function {
261 demangleSingleFunction(fn, options)
262 }
263 }
264
265 func demanglerModeToOptions(demanglerMode string) []demangle.Option {
266 switch demanglerMode {
267 case "":
268 return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams}
269 case "templates":
270 return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams}
271 case "full":
272 return []demangle.Option{demangle.NoClones}
273 case "none":
274 return []demangle.Option{}
275 }
276
277 panic(fmt.Sprintf("unknown demanglerMode %s", demanglerMode))
278 }
279
280 func demangleSingleFunction(fn *profile.Function, options []demangle.Option) {
281 if fn.Name != "" && fn.SystemName != fn.Name {
282 return
283 }
284
285 o := make([]demangle.Option, len(options))
286 copy(o, options)
287 if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
288 fn.Name = demangled
289 return
290 }
291
292
293 name := fn.SystemName
294 if looksLikeDemangledCPlusPlus(name) {
295 for _, o := range options {
296 switch o {
297 case demangle.NoParams:
298 name = removeMatching(name, '(', ')')
299 case demangle.NoTemplateParams:
300 name = removeMatching(name, '<', '>')
301 }
302 }
303 }
304 fn.Name = name
305 }
306
307
308
309
310 func looksLikeDemangledCPlusPlus(demangled string) bool {
311
312 if strings.Contains(demangled, ".<") {
313 return false
314 }
315
316 if strings.Contains(demangled, "]).") {
317 return false
318 }
319 return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
320 }
321
322
323 func removeMatching(name string, start, end byte) string {
324 s := string(start) + string(end)
325 var nesting, first, current int
326 for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
327 switch current += index; name[current] {
328 case start:
329 nesting++
330 if nesting == 1 {
331 first = current
332 }
333 case end:
334 nesting--
335 switch {
336 case nesting < 0:
337 return name
338 case nesting == 0:
339 name = name[:first] + name[current+1:]
340 current = first - 1
341 }
342 }
343 current++
344 }
345 return name
346 }
347
View as plain text