Browse Source

Merge pull request #4 from rauls5382/master

Add local symbolization using llvm-symbolizer
Hyoun Kyu Cho 9 years ago
parent
commit
3714b718c9

+ 1
- 1
internal/binutils/addr2liner.go View File

@@ -77,7 +77,7 @@ func (a *addr2LinerJob) close() {
77 77
 
78 78
 // newAddr2liner starts the given addr2liner command reporting
79 79
 // information about the given executable file. If file is a shared
80
-// library, base should be the address at which is was mapped in the
80
+// library, base should be the address at which it was mapped in the
81 81
 // program under consideration.
82 82
 func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) {
83 83
 	if cmd == "" {

+ 170
- 0
internal/binutils/addr2liner_llvm.go View File

@@ -0,0 +1,170 @@
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 it 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 0x%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
+}

+ 3
- 2
internal/binutils/addr2liner_nm.go View File

@@ -42,7 +42,7 @@ type symbolInfo struct {
42 42
 
43 43
 //  newAddr2LinerNM starts the given nm command reporting information about the
44 44
 // given executable file. If file is a shared library, base should be
45
-// the address at which is was mapped in the program under
45
+// the address at which it was mapped in the program under
46 46
 // consideration.
47 47
 func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
48 48
 	if cmd == "" {
@@ -61,7 +61,7 @@ func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
61 61
 		return nil, err
62 62
 	}
63 63
 
64
-	// Parse addr2line output and populate symbol map.
64
+	// Parse nm output and populate symbol map.
65 65
 	// Skip lines we fail to parse.
66 66
 	buf := bufio.NewReader(&b)
67 67
 	for {
@@ -72,6 +72,7 @@ func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
72 72
 			}
73 73
 			return nil, err
74 74
 		}
75
+		line = strings.TrimSpace(line)
75 76
 		fields := strings.SplitN(line, " ", 3)
76 77
 		if len(fields) != 3 {
77 78
 			continue

+ 23
- 10
internal/binutils/binutils.go View File

@@ -32,9 +32,10 @@ import (
32 32
 // SetConfig must be called before any of the other methods.
33 33
 type Binutils struct {
34 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 40
 	// if fast, perform symbolization using nm (symbol names only),
40 41
 	// instead of file-line detail from the slower addr2line.
@@ -65,6 +66,7 @@ func (b *Binutils) SetTools(config string) {
65 66
 	}
66 67
 
67 68
 	defaultPath := paths[""]
69
+	b.llvmSymbolizer = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...))
68 70
 	b.addr2line = findExe("addr2line", append(paths["addr2line"], defaultPath...))
69 71
 	b.nm = findExe("nm", append(paths["nm"], defaultPath...))
70 72
 	b.objdump = findExe("objdump", append(paths["objdump"], defaultPath...))
@@ -235,15 +237,24 @@ func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
235 237
 // information.
236 238
 type fileAddr2Line struct {
237 239
 	file
238
-	addr2liner *addr2Liner
240
+	addr2liner     *addr2Liner
241
+	llvmSymbolizer *llvmSymbolizer
239 242
 }
240 243
 
241 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 258
 		f.addr2liner = addr2liner
248 259
 
249 260
 		// When addr2line encounters some gcc compiled binaries, it
@@ -252,8 +263,10 @@ func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
252 263
 		if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
253 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 272
 func (f *fileAddr2Line) Close() error {