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 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
148 continue
149 }
150
151 stack, err := segment.SourceLine(l.Address)
152 if err != nil || len(stack) == 0 {
153
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
196
197
198 func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
199 if force {
200
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 "":
217 return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams}
218 case "templates":
219 return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams}
220 case "full":
221 return []demangle.Option{demangle.NoClones}
222 case "none":
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
232 }
233
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
241
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
257
258
259 func looksLikeDemangledCPlusPlus(demangled string) bool {
260
261 if strings.Contains(demangled, ".<") {
262 return false
263 }
264
265 if strings.Contains(demangled, "]).") {
266 return false
267 }
268 return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
269 }
270
271
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
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
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
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
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
331 if m.Unsymbolizable() {
332 continue
333 }
334
335
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
368
369 type mappingTable struct {
370 prof *profile.Profile
371 segments map[*profile.Mapping]plugin.ObjFile
372 }
373
374
375 func (mt *mappingTable) close() {
376 for _, segment := range mt.segments {
377 segment.Close()
378 }
379 }
380
View as plain text