浏览代码

Add support for local symbolization using llvm-symbolizer

Raul Silvera 9 年前
父节点
当前提交
e4f7c9c980
共有 2 个文件被更改,包括 193 次插入10 次删除
  1. 170
    0
      internal/binutils/addr2liner_llvm.go
  2. 23
    10
      internal/binutils/binutils.go

+ 170
- 0
internal/binutils/addr2liner_llvm.go 查看文件

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 binutils
16
+
17
+import (
18
+	"bufio"
19
+	"fmt"
20
+	"io"
21
+	"os/exec"
22
+	"strconv"
23
+	"strings"
24
+
25
+	"github.com/google/pprof/internal/plugin"
26
+)
27
+
28
+const (
29
+	defaultLLVMSymbolizer = "llvm-symbolizer"
30
+)
31
+
32
+// llvmSymbolizer is a connection to an llvm-symbolizer command for
33
+// obtaining address and line number information from a binary.
34
+type llvmSymbolizer struct {
35
+	filename string
36
+	rw       lineReaderWriter
37
+	base     uint64
38
+}
39
+
40
+type llvmSymbolizerJob struct {
41
+	cmd *exec.Cmd
42
+	in  io.WriteCloser
43
+	out *bufio.Reader
44
+}
45
+
46
+func (a *llvmSymbolizerJob) write(s string) error {
47
+	_, err := fmt.Fprint(a.in, s+"\n")
48
+	return err
49
+}
50
+
51
+func (a *llvmSymbolizerJob) readLine() (string, error) {
52
+	return a.out.ReadString('\n')
53
+}
54
+
55
+// close releases any resources used by the llvmSymbolizer object.
56
+func (a *llvmSymbolizerJob) close() {
57
+	a.in.Close()
58
+	a.cmd.Wait()
59
+}
60
+
61
+// newLlvmSymbolizer starts the given llvmSymbolizer command reporting
62
+// information about the given executable file. If file is a shared
63
+// library, base should be the address at which is was mapped in the
64
+// program under consideration.
65
+func newLLVMSymbolizer(cmd, file string, base uint64) (*llvmSymbolizer, error) {
66
+	if cmd == "" {
67
+		cmd = defaultLLVMSymbolizer
68
+	}
69
+
70
+	j := &llvmSymbolizerJob{
71
+		cmd: exec.Command(cmd, "-inlining", "-demangle=false"),
72
+	}
73
+
74
+	var err error
75
+	if j.in, err = j.cmd.StdinPipe(); err != nil {
76
+		return nil, err
77
+	}
78
+
79
+	outPipe, err := j.cmd.StdoutPipe()
80
+	if err != nil {
81
+		return nil, err
82
+	}
83
+
84
+	j.out = bufio.NewReader(outPipe)
85
+	if err := j.cmd.Start(); err != nil {
86
+		return nil, err
87
+	}
88
+
89
+	a := &llvmSymbolizer{
90
+		filename: file,
91
+		rw:       j,
92
+		base:     base,
93
+	}
94
+
95
+	return a, nil
96
+}
97
+
98
+func (d *llvmSymbolizer) readString() (string, error) {
99
+	s, err := d.rw.readLine()
100
+	if err != nil {
101
+		return "", err
102
+	}
103
+	return strings.TrimSpace(s), nil
104
+}
105
+
106
+// readFrame parses the llvm-symbolizer output for a single address. It
107
+// returns a populated plugin.Frame and whether it has reached the end of the
108
+// data.
109
+func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
110
+	funcname, err := d.readString()
111
+	if err != nil {
112
+		return plugin.Frame{}, true
113
+	}
114
+
115
+	switch funcname {
116
+	case "":
117
+		return plugin.Frame{}, true
118
+	case "??":
119
+		funcname = ""
120
+	}
121
+
122
+	fileline, err := d.readString()
123
+	if err != nil {
124
+		return plugin.Frame{funcname, "", 0}, true
125
+	}
126
+
127
+	linenumber := 0
128
+	if fileline == "??:0" {
129
+		fileline = ""
130
+	} else {
131
+		switch split := strings.Split(fileline, ":"); len(split) {
132
+		case 1:
133
+			// filename
134
+			fileline = split[0]
135
+		case 2, 3:
136
+			// filename:line , or
137
+			// filename:line:disc , or
138
+			fileline = split[0]
139
+			if line, err := strconv.Atoi(split[1]); err == nil {
140
+				linenumber = line
141
+			}
142
+		default:
143
+			// Unrecognized, ignore
144
+		}
145
+	}
146
+
147
+	return plugin.Frame{funcname, fileline, linenumber}, false
148
+}
149
+
150
+// addrInfo returns the stack frame information for a specific program
151
+// address. It returns nil if the address could not be identified.
152
+func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) {
153
+	if err := d.rw.write(fmt.Sprintf("%s %x", d.filename, addr-d.base)); err != nil {
154
+		return nil, err
155
+	}
156
+
157
+	var stack []plugin.Frame
158
+	for {
159
+		frame, end := d.readFrame()
160
+		if end {
161
+			break
162
+		}
163
+
164
+		if frame != (plugin.Frame{}) {
165
+			stack = append(stack, frame)
166
+		}
167
+	}
168
+
169
+	return stack, nil
170
+}

+ 23
- 10
internal/binutils/binutils.go 查看文件

32
 // SetConfig must be called before any of the other methods.
32
 // SetConfig must be called before any of the other methods.
33
 type Binutils struct {
33
 type Binutils struct {
34
 	// Commands to invoke.
34
 	// Commands to invoke.
35
-	addr2line string
36
-	nm        string
37
-	objdump   string
35
+	llvmSymbolizer string
36
+	addr2line      string
37
+	nm             string
38
+	objdump        string
38
 
39
 
39
 	// if fast, perform symbolization using nm (symbol names only),
40
 	// if fast, perform symbolization using nm (symbol names only),
40
 	// instead of file-line detail from the slower addr2line.
41
 	// instead of file-line detail from the slower addr2line.
65
 	}
66
 	}
66
 
67
 
67
 	defaultPath := paths[""]
68
 	defaultPath := paths[""]
69
+	b.llvmSymbolizer = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...))
68
 	b.addr2line = findExe("addr2line", append(paths["addr2line"], defaultPath...))
70
 	b.addr2line = findExe("addr2line", append(paths["addr2line"], defaultPath...))
69
 	b.nm = findExe("nm", append(paths["nm"], defaultPath...))
71
 	b.nm = findExe("nm", append(paths["nm"], defaultPath...))
70
 	b.objdump = findExe("objdump", append(paths["objdump"], defaultPath...))
72
 	b.objdump = findExe("objdump", append(paths["objdump"], defaultPath...))
235
 // information.
237
 // information.
236
 type fileAddr2Line struct {
238
 type fileAddr2Line struct {
237
 	file
239
 	file
238
-	addr2liner *addr2Liner
240
+	addr2liner     *addr2Liner
241
+	llvmSymbolizer *llvmSymbolizer
239
 }
242
 }
240
 
243
 
241
 func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
244
 func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
242
-	if f.addr2liner == nil {
243
-		addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base)
244
-		if err != nil {
245
-			return nil, err
246
-		}
245
+	if f.llvmSymbolizer != nil {
246
+		return f.llvmSymbolizer.addrInfo(addr)
247
+	}
248
+	if f.addr2liner != nil {
249
+		return f.addr2liner.addrInfo(addr)
250
+	}
251
+
252
+	if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil {
253
+		f.llvmSymbolizer = llvmSymbolizer
254
+		return f.llvmSymbolizer.addrInfo(addr)
255
+	}
256
+
257
+	if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {
247
 		f.addr2liner = addr2liner
258
 		f.addr2liner = addr2liner
248
 
259
 
249
 		// When addr2line encounters some gcc compiled binaries, it
260
 		// When addr2line encounters some gcc compiled binaries, it
252
 		if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
263
 		if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
253
 			f.addr2liner.nm = nm
264
 			f.addr2liner.nm = nm
254
 		}
265
 		}
266
+		return f.addr2liner.addrInfo(addr)
255
 	}
267
 	}
256
-	return f.addr2liner.addrInfo(addr)
268
+
269
+	return nil, fmt.Errorf("could not find local addr2liner")
257
 }
270
 }
258
 
271
 
259
 func (f *fileAddr2Line) Close() error {
272
 func (f *fileAddr2Line) Close() error {