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 address 33 // information from a binary. 34 type addr2LinerNM struct { 35 m []symbolInfo // Sorted list of addresses from binary. 36 } 37 38 type symbolInfo struct { 39 address uint64 40 name string 41 } 42 43 // newAddr2LinerNM starts the given nm command reporting information about the 44 // given executable file. If file is a shared library, base should be 45 // the address at which it was mapped in the program under 46 // consideration. 47 func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) { 48 if cmd == "" { 49 cmd = defaultNM 50 } 51 var b bytes.Buffer 52 c := exec.Command(cmd, "-n", file) 53 c.Stdout = &b 54 if err := c.Run(); err != nil { 55 return nil, err 56 } 57 return parseAddr2LinerNM(base, &b) 58 } 59 60 func parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) { 61 a := &addr2LinerNM{ 62 m: []symbolInfo{}, 63 } 64 65 // Parse nm output and populate symbol map. 66 // Skip lines we fail to parse. 67 buf := bufio.NewReader(nm) 68 for { 69 line, err := buf.ReadString('\n') 70 if line == "" && err != nil { 71 if err == io.EOF { 72 break 73 } 74 return nil, err 75 } 76 line = strings.TrimSpace(line) 77 fields := strings.SplitN(line, " ", 3) 78 if len(fields) != 3 { 79 continue 80 } 81 address, err := strconv.ParseUint(fields[0], 16, 64) 82 if err != nil { 83 continue 84 } 85 a.m = append(a.m, symbolInfo{ 86 address: address + base, 87 name: fields[2], 88 }) 89 } 90 91 return a, nil 92 } 93 94 // addrInfo returns the stack frame information for a specific program 95 // address. It returns nil if the address could not be identified. 96 func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) { 97 if len(a.m) == 0 || addr < a.m[0].address || addr > a.m[len(a.m)-1].address { 98 return nil, nil 99 } 100 101 // Binary search. Search until low, high are separated by 1. 102 low, high := 0, len(a.m) 103 for low+1 < high { 104 mid := (low + high) / 2 105 v := a.m[mid].address 106 if addr == v { 107 low = mid 108 break 109 } else if addr > v { 110 low = mid 111 } else { 112 high = mid 113 } 114 } 115 116 // Address is between a.m[low] and a.m[high]. 117 // Pick low, as it represents [low, high). 118 f := []plugin.Frame{ 119 { 120 Func: a.m[low].name, 121 }, 122 } 123 return f, nil 124 } 125