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

View as plain text