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