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
}
|