-
Notifications
You must be signed in to change notification settings - Fork 615
/
addr2liner_nm.go
144 lines (130 loc) · 3.99 KB
/
addr2liner_nm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package binutils
import (
"bufio"
"bytes"
"io"
"os/exec"
"strconv"
"strings"
"github.com/google/pprof/internal/plugin"
)
const (
defaultNM = "nm"
)
// addr2LinerNM is a connection to an nm command for obtaining symbol
// information from a binary.
type addr2LinerNM struct {
m []symbolInfo // Sorted list of symbol addresses from binary.
}
type symbolInfo struct {
address uint64
size uint64
name string
symType string
}
// isData returns if the symbol has a known data object symbol type.
func (s *symbolInfo) isData() bool {
// The following symbol types are taken from https://linux.die.net/man/1/nm:
// Lowercase letter means local symbol, uppercase denotes a global symbol.
// - b or B: the symbol is in the uninitialized data section, e.g. .bss;
// - d or D: the symbol is in the initialized data section;
// - r or R: the symbol is in a read only data section;
// - v or V: the symbol is a weak object;
// - W: the symbol is a weak symbol that has not been specifically tagged as a
// weak object symbol. Experiments with some binaries, showed these to be
// mostly data objects.
return strings.ContainsAny(s.symType, "bBdDrRvVW")
}
// newAddr2LinerNM starts the given nm command reporting information about the
// given executable file. If file is a shared library, base should be the
// address at which it was mapped in the program under consideration.
func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
if cmd == "" {
cmd = defaultNM
}
var b bytes.Buffer
c := exec.Command(cmd, "--numeric-sort", "--print-size", "--format=posix", file)
c.Stdout = &b
if err := c.Run(); err != nil {
return nil, err
}
return parseAddr2LinerNM(base, &b)
}
func parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) {
a := &addr2LinerNM{
m: []symbolInfo{},
}
// Parse nm output and populate symbol map.
// Skip lines we fail to parse.
buf := bufio.NewReader(nm)
for {
line, err := buf.ReadString('\n')
if line == "" && err != nil {
if err == io.EOF {
break
}
return nil, err
}
line = strings.TrimSpace(line)
fields := strings.Split(line, " ")
if len(fields) != 4 {
continue
}
address, err := strconv.ParseUint(fields[2], 16, 64)
if err != nil {
continue
}
size, err := strconv.ParseUint(fields[3], 16, 64)
if err != nil {
continue
}
a.m = append(a.m, symbolInfo{
address: address + base,
size: size,
name: fields[0],
symType: fields[1],
})
}
return a, nil
}
// addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified.
func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) {
if len(a.m) == 0 || addr < a.m[0].address || addr >= (a.m[len(a.m)-1].address+a.m[len(a.m)-1].size) {
return nil, nil
}
// Binary search. Search until low, high are separated by 1.
low, high := 0, len(a.m)
for low+1 < high {
mid := (low + high) / 2
v := a.m[mid].address
if addr == v {
low = mid
break
} else if addr > v {
low = mid
} else {
high = mid
}
}
// Address is between a.m[low] and a.m[high]. Pick low, as it represents
// [low, high). For data symbols, we use a strict check that the address is in
// the [start, start + size) range of a.m[low].
if a.m[low].isData() && addr >= (a.m[low].address+a.m[low].size) {
return nil, nil
}
return []plugin.Frame{{Func: a.m[low].name}}, nil
}