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