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 "fmt" 20 "io" 21 "os/exec" 22 "strconv" 23 "strings" 24 "sync" 25 26 "github.com/google/pprof/internal/plugin" 27 ) 28 29 const ( 30 defaultAddr2line = "addr2line" 31 32 // addr2line may produce multiple lines of output. We 33 // use this sentinel to identify the end of the output. 34 sentinel = ^uint64(0) 35 ) 36 37 // addr2Liner is a connection to an addr2line command for obtaining 38 // address and line number information from a binary. 39 type addr2Liner struct { 40 mu sync.Mutex 41 rw lineReaderWriter 42 base uint64 43 44 // nm holds an NM based addr2Liner which can provide 45 // better full names compared to addr2line, which often drops 46 // namespaces etc. from the names it returns. 47 nm *addr2LinerNM 48 } 49 50 // lineReaderWriter is an interface to abstract the I/O to an addr2line 51 // process. It writes a line of input to the job, and reads its output 52 // one line at a time. 53 type lineReaderWriter interface { 54 write(string) error 55 readLine() (string, error) 56 close() 57 } 58 59 type addr2LinerJob struct { 60 cmd *exec.Cmd 61 in io.WriteCloser 62 out *bufio.Reader 63 } 64 65 func (a *addr2LinerJob) write(s string) error { 66 _, err := fmt.Fprint(a.in, s+"\n") 67 return err 68 } 69 70 func (a *addr2LinerJob) readLine() (string, error) { 71 return a.out.ReadString('\n') 72 } 73 74 // close releases any resources used by the addr2liner object. 75 func (a *addr2LinerJob) close() { 76 a.in.Close() 77 a.cmd.Wait() 78 } 79 80 // newAddr2liner starts the given addr2liner command reporting 81 // information about the given executable file. If file is a shared 82 // library, base should be the address at which it was mapped in the 83 // program under consideration. 84 func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) { 85 if cmd == "" { 86 cmd = defaultAddr2line 87 } 88 89 j := &addr2LinerJob{ 90 cmd: exec.Command(cmd, "-aif", "-e", file), 91 } 92 93 var err error 94 if j.in, err = j.cmd.StdinPipe(); err != nil { 95 return nil, err 96 } 97 98 outPipe, err := j.cmd.StdoutPipe() 99 if err != nil { 100 return nil, err 101 } 102 103 j.out = bufio.NewReader(outPipe) 104 if err := j.cmd.Start(); err != nil { 105 return nil, err 106 } 107 108 a := &addr2Liner{ 109 rw: j, 110 base: base, 111 } 112 113 return a, nil 114 } 115 116 func (d *addr2Liner) readString() (string, error) { 117 s, err := d.rw.readLine() 118 if err != nil { 119 return "", err 120 } 121 return strings.TrimSpace(s), nil 122 } 123 124 // readFrame parses the addr2line output for a single address. It 125 // returns a populated plugin.Frame and whether it has reached the end of the 126 // data. 127 func (d *addr2Liner) readFrame() (plugin.Frame, bool) { 128 funcname, err := d.readString() 129 if err != nil { 130 return plugin.Frame{}, true 131 } 132 if strings.HasPrefix(funcname, "0x") { 133 // If addr2line returns a hex address we can assume it is the 134 // sentinel. Read and ignore next two lines of output from 135 // addr2line 136 d.readString() 137 d.readString() 138 return plugin.Frame{}, true 139 } 140 141 fileline, err := d.readString() 142 if err != nil { 143 return plugin.Frame{}, true 144 } 145 146 linenumber := 0 147 148 if funcname == "??" { 149 funcname = "" 150 } 151 152 if fileline == "??:0" { 153 fileline = "" 154 } else { 155 if i := strings.LastIndex(fileline, ":"); i >= 0 { 156 // Remove discriminator, if present 157 if disc := strings.Index(fileline, " (discriminator"); disc > 0 { 158 fileline = fileline[:disc] 159 } 160 // If we cannot parse a number after the last ":", keep it as 161 // part of the filename. 162 if line, err := strconv.Atoi(fileline[i+1:]); err == nil { 163 linenumber = line 164 fileline = fileline[:i] 165 } 166 } 167 } 168 169 return plugin.Frame{ 170 Func: funcname, 171 File: fileline, 172 Line: linenumber}, false 173 } 174 175 func (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) { 176 d.mu.Lock() 177 defer d.mu.Unlock() 178 179 if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil { 180 return nil, err 181 } 182 183 if err := d.rw.write(fmt.Sprintf("%x", sentinel)); err != nil { 184 return nil, err 185 } 186 187 resp, err := d.readString() 188 if err != nil { 189 return nil, err 190 } 191 192 if !strings.HasPrefix(resp, "0x") { 193 return nil, fmt.Errorf("unexpected addr2line output: %s", resp) 194 } 195 196 var stack []plugin.Frame 197 for { 198 frame, end := d.readFrame() 199 if end { 200 break 201 } 202 203 if frame != (plugin.Frame{}) { 204 stack = append(stack, frame) 205 } 206 } 207 return stack, err 208 } 209 210 // addrInfo returns the stack frame information for a specific program 211 // address. It returns nil if the address could not be identified. 212 func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) { 213 stack, err := d.rawAddrInfo(addr) 214 if err != nil { 215 return nil, err 216 } 217 218 // Get better name from nm if possible. 219 if len(stack) > 0 && d.nm != nil { 220 nm, err := d.nm.addrInfo(addr) 221 if err == nil && len(nm) > 0 { 222 // Last entry in frame list should match since 223 // it is non-inlined. As a simple heuristic, 224 // we only switch to the nm-based name if it 225 // is longer. 226 nmName := nm[len(nm)-1].Func 227 a2lName := stack[len(stack)-1].Func 228 if len(nmName) > len(a2lName) { 229 stack[len(stack)-1].Func = nmName 230 } 231 } 232 } 233 234 return stack, nil 235 } 236