golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.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 provides access to the GNU binutils. 16 package binutils 17 18 import ( 19 "debug/elf" 20 "debug/macho" 21 "fmt" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "regexp" 26 "strings" 27 28 "github.com/google/pprof/internal/elfexec" 29 "github.com/google/pprof/internal/plugin" 30 ) 31 32 // A Binutils implements plugin.ObjTool by invoking the GNU binutils. 33 // SetConfig must be called before any of the other methods. 34 type Binutils struct { 35 // Commands to invoke. 36 llvmSymbolizer string 37 llvmSymbolizerFound bool 38 addr2line string 39 addr2lineFound bool 40 nm string 41 nmFound bool 42 objdump string 43 objdumpFound bool 44 45 // if fast, perform symbolization using nm (symbol names only), 46 // instead of file-line detail from the slower addr2line. 47 fast bool 48 } 49 50 // SetFastSymbolization sets a toggle that makes binutils use fast 51 // symbolization (using nm), which is much faster than addr2line but 52 // provides only symbol name information (no file/line). 53 func (b *Binutils) SetFastSymbolization(fast bool) { 54 b.fast = fast 55 } 56 57 // SetTools processes the contents of the tools option. It 58 // expects a set of entries separated by commas; each entry is a pair 59 // of the form t:path, where cmd will be used to look only for the 60 // tool named t. If t is not specified, the path is searched for all 61 // tools. 62 func (b *Binutils) SetTools(config string) { 63 // paths collect paths per tool; Key "" contains the default. 64 paths := make(map[string][]string) 65 for _, t := range strings.Split(config, ",") { 66 name, path := "", t 67 if ct := strings.SplitN(t, ":", 2); len(ct) == 2 { 68 name, path = ct[0], ct[1] 69 } 70 paths[name] = append(paths[name], path) 71 } 72 73 defaultPath := paths[""] 74 b.llvmSymbolizer, b.llvmSymbolizerFound = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...)) 75 b.addr2line, b.addr2lineFound = findExe("addr2line", append(paths["addr2line"], defaultPath...)) 76 b.nm, b.nmFound = findExe("nm", append(paths["nm"], defaultPath...)) 77 b.objdump, b.objdumpFound = findExe("objdump", append(paths["objdump"], defaultPath...)) 78 } 79 80 // findExe looks for an executable command on a set of paths. 81 // If it cannot find it, returns cmd. 82 func findExe(cmd string, paths []string) (string, bool) { 83 for _, p := range paths { 84 cp := filepath.Join(p, cmd) 85 if c, err := exec.LookPath(cp); err == nil { 86 return c, true 87 } 88 } 89 return cmd, false 90 } 91 92 // Disasm returns the assembly instructions for the specified address range 93 // of a binary. 94 func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { 95 if b.addr2line == "" { 96 // Update the command invocations if not initialized. 97 b.SetTools("") 98 } 99 cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l", 100 fmt.Sprintf("--start-address=%#x", start), 101 fmt.Sprintf("--stop-address=%#x", end), 102 file) 103 out, err := cmd.Output() 104 if err != nil { 105 return nil, fmt.Errorf("%v: %v", cmd.Args, err) 106 } 107 108 return disassemble(out) 109 } 110 111 // Open satisfies the plugin.ObjTool interface. 112 func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) { 113 if b.addr2line == "" { 114 // Update the command invocations if not initialized. 115 b.SetTools("") 116 } 117 118 // Make sure file is a supported executable. 119 // The pprof driver uses Open to sniff the difference 120 // between an executable and a profile. 121 // For now, only ELF is supported. 122 // Could read the first few bytes of the file and 123 // use a table of prefixes if we need to support other 124 // systems at some point. 125 126 if _, err := os.Stat(name); err != nil { 127 // For testing, do not require file name to exist. 128 if strings.Contains(b.addr2line, "testdata/") { 129 return &fileAddr2Line{file: file{b: b, name: name}}, nil 130 } 131 return nil, err 132 } 133 134 if f, err := b.openELF(name, start, limit, offset); err == nil { 135 return f, nil 136 } 137 if f, err := b.openMachO(name, start, limit, offset); err == nil { 138 return f, nil 139 } 140 return nil, fmt.Errorf("unrecognized binary: %s", name) 141 } 142 143 func (b *Binutils) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) { 144 of, err := macho.Open(name) 145 if err != nil { 146 return nil, fmt.Errorf("Parsing %s: %v", name, err) 147 } 148 defer of.Close() 149 150 if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { 151 return &fileNM{file: file{b: b, name: name}}, nil 152 } 153 return &fileAddr2Line{file: file{b: b, name: name}}, nil 154 } 155 156 func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) { 157 ef, err := elf.Open(name) 158 if err != nil { 159 return nil, fmt.Errorf("Parsing %s: %v", name, err) 160 } 161 defer ef.Close() 162 163 var stextOffset *uint64 164 var pageAligned = func(addr uint64) bool { return addr%4096 == 0 } 165 if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) { 166 // Reading all Symbols is expensive, and we only rarely need it so 167 // we don't want to do it every time. But if _stext happens to be 168 // page-aligned but isn't the same as Vaddr, we would symbolize 169 // wrong. So if the name the addresses aren't page aligned, or if 170 // the name is "vmlinux" we read _stext. We can be wrong if: (1) 171 // someone passes a kernel path that doesn't contain "vmlinux" AND 172 // (2) _stext is page-aligned AND (3) _stext is not at Vaddr 173 symbols, err := ef.Symbols() 174 if err != nil { 175 return nil, err 176 } 177 for _, s := range symbols { 178 if s.Name == "_stext" { 179 // The kernel may use _stext as the mapping start address. 180 stextOffset = &s.Value 181 break 182 } 183 } 184 } 185 186 base, err := elfexec.GetBase(&ef.FileHeader, nil, stextOffset, start, limit, offset) 187 if err != nil { 188 return nil, fmt.Errorf("Could not identify base for %s: %v", name, err) 189 } 190 191 buildID := "" 192 if f, err := os.Open(name); err == nil { 193 if id, err := elfexec.GetBuildID(f); err == nil { 194 buildID = fmt.Sprintf("%x", id) 195 } 196 } 197 if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { 198 return &fileNM{file: file{b, name, base, buildID}}, nil 199 } 200 return &fileAddr2Line{file: file{b, name, base, buildID}}, nil 201 } 202 203 // file implements the binutils.ObjFile interface. 204 type file struct { 205 b *Binutils 206 name string 207 base uint64 208 buildID string 209 } 210 211 func (f *file) Name() string { 212 return f.name 213 } 214 215 func (f *file) Base() uint64 { 216 return f.base 217 } 218 219 func (f *file) BuildID() string { 220 return f.buildID 221 } 222 223 func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) { 224 return []plugin.Frame{}, nil 225 } 226 227 func (f *file) Close() error { 228 return nil 229 } 230 231 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { 232 // Get from nm a list of symbols sorted by address. 233 cmd := exec.Command(f.b.nm, "-n", f.name) 234 out, err := cmd.Output() 235 if err != nil { 236 return nil, fmt.Errorf("%v: %v", cmd.Args, err) 237 } 238 239 return findSymbols(out, f.name, r, addr) 240 } 241 242 // fileNM implements the binutils.ObjFile interface, using 'nm' to map 243 // addresses to symbols (without file/line number information). It is 244 // faster than fileAddr2Line. 245 type fileNM struct { 246 file 247 addr2linernm *addr2LinerNM 248 } 249 250 func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) { 251 if f.addr2linernm == nil { 252 addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base) 253 if err != nil { 254 return nil, err 255 } 256 f.addr2linernm = addr2liner 257 } 258 return f.addr2linernm.addrInfo(addr) 259 } 260 261 // fileAddr2Line implements the binutils.ObjFile interface, using 262 // 'addr2line' to map addresses to symbols (with file/line number 263 // information). It can be slow for large binaries with debug 264 // information. 265 type fileAddr2Line struct { 266 file 267 addr2liner *addr2Liner 268 llvmSymbolizer *llvmSymbolizer 269 } 270 271 func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) { 272 if f.llvmSymbolizer != nil { 273 return f.llvmSymbolizer.addrInfo(addr) 274 } 275 if f.addr2liner != nil { 276 return f.addr2liner.addrInfo(addr) 277 } 278 279 if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil { 280 f.llvmSymbolizer = llvmSymbolizer 281 return f.llvmSymbolizer.addrInfo(addr) 282 } 283 284 if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil { 285 f.addr2liner = addr2liner 286 287 // When addr2line encounters some gcc compiled binaries, it 288 // drops interesting parts of names in anonymous namespaces. 289 // Fallback to NM for better function names. 290 if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil { 291 f.addr2liner.nm = nm 292 } 293 return f.addr2liner.addrInfo(addr) 294 } 295 296 return nil, fmt.Errorf("could not find local addr2liner") 297 } 298 299 func (f *fileAddr2Line) Close() error { 300 if f.addr2liner != nil { 301 f.addr2liner.rw.close() 302 f.addr2liner = nil 303 } 304 return nil 305 }