github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/src/cmd/pprof/pprof.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "debug/dwarf" 9 "flag" 10 "fmt" 11 "net/url" 12 "os" 13 "regexp" 14 "strings" 15 "sync" 16 17 "cmd/internal/objfile" 18 "cmd/pprof/internal/commands" 19 "cmd/pprof/internal/driver" 20 "cmd/pprof/internal/fetch" 21 "cmd/pprof/internal/plugin" 22 "cmd/pprof/internal/symbolizer" 23 "cmd/pprof/internal/symbolz" 24 "internal/pprof/profile" 25 ) 26 27 func main() { 28 var extraCommands map[string]*commands.Command // no added Go-specific commands 29 if err := driver.PProf(flags{}, fetch.Fetcher, symbolize, new(objTool), plugin.StandardUI(), extraCommands); err != nil { 30 fmt.Fprintf(os.Stderr, "%v\n", err) 31 os.Exit(2) 32 } 33 } 34 35 // symbolize attempts to symbolize profile p. 36 // If the source is a local binary, it tries using symbolizer and obj. 37 // If the source is a URL, it fetches symbol information using symbolz. 38 func symbolize(mode, source string, p *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { 39 remote, local := true, true 40 for _, o := range strings.Split(strings.ToLower(mode), ":") { 41 switch o { 42 case "none", "no": 43 return nil 44 case "local": 45 remote, local = false, true 46 case "remote": 47 remote, local = true, false 48 default: 49 ui.PrintErr("ignoring unrecognized symbolization option: " + mode) 50 ui.PrintErr("expecting -symbolize=[local|remote|none][:force]") 51 fallthrough 52 case "", "force": 53 // -force is recognized by symbolizer.Symbolize. 54 // If the source is remote, and the mapping file 55 // does not exist, don't use local symbolization. 56 if isRemote(source) { 57 if len(p.Mapping) == 0 { 58 local = false 59 } else if _, err := os.Stat(p.Mapping[0].File); err != nil { 60 local = false 61 } 62 } 63 } 64 } 65 66 var err error 67 if local { 68 // Symbolize using binutils. 69 if err = symbolizer.Symbolize(mode, p, obj, ui); err == nil { 70 return nil 71 } 72 } 73 if remote { 74 err = symbolz.Symbolize(source, fetch.PostURL, p) 75 } 76 return err 77 } 78 79 // isRemote returns whether source is a URL for a remote source. 80 func isRemote(source string) bool { 81 url, err := url.Parse(source) 82 if err != nil { 83 url, err = url.Parse("http://" + source) 84 if err != nil { 85 return false 86 } 87 } 88 if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" { 89 return false 90 } 91 return true 92 } 93 94 // flags implements the driver.FlagPackage interface using the builtin flag package. 95 type flags struct { 96 } 97 98 func (flags) Bool(o string, d bool, c string) *bool { 99 return flag.Bool(o, d, c) 100 } 101 102 func (flags) Int(o string, d int, c string) *int { 103 return flag.Int(o, d, c) 104 } 105 106 func (flags) Float64(o string, d float64, c string) *float64 { 107 return flag.Float64(o, d, c) 108 } 109 110 func (flags) String(o, d, c string) *string { 111 return flag.String(o, d, c) 112 } 113 114 func (flags) Parse(usage func()) []string { 115 flag.Usage = usage 116 flag.Parse() 117 args := flag.Args() 118 if len(args) == 0 { 119 usage() 120 } 121 return args 122 } 123 124 func (flags) ExtraUsage() string { 125 return "" 126 } 127 128 // objTool implements plugin.ObjTool using Go libraries 129 // (instead of invoking GNU binutils). 130 type objTool struct { 131 mu sync.Mutex 132 disasmCache map[string]*objfile.Disasm 133 } 134 135 func (*objTool) Open(name string, start uint64) (plugin.ObjFile, error) { 136 of, err := objfile.Open(name) 137 if err != nil { 138 return nil, err 139 } 140 f := &file{ 141 name: name, 142 file: of, 143 } 144 if start != 0 { 145 if load, err := of.LoadAddress(); err == nil { 146 f.offset = start - load 147 } 148 } 149 return f, nil 150 } 151 152 func (*objTool) Demangle(names []string) (map[string]string, error) { 153 // No C++, nothing to demangle. 154 return make(map[string]string), nil 155 } 156 157 func (t *objTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { 158 d, err := t.cachedDisasm(file) 159 if err != nil { 160 return nil, err 161 } 162 var asm []plugin.Inst 163 d.Decode(start, end, nil, func(pc, size uint64, file string, line int, text string) { 164 asm = append(asm, plugin.Inst{Addr: pc, File: file, Line: line, Text: text}) 165 }) 166 return asm, nil 167 } 168 169 func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) { 170 t.mu.Lock() 171 defer t.mu.Unlock() 172 if t.disasmCache == nil { 173 t.disasmCache = make(map[string]*objfile.Disasm) 174 } 175 d := t.disasmCache[file] 176 if d != nil { 177 return d, nil 178 } 179 f, err := objfile.Open(file) 180 if err != nil { 181 return nil, err 182 } 183 d, err = f.Disasm() 184 f.Close() 185 if err != nil { 186 return nil, err 187 } 188 t.disasmCache[file] = d 189 return d, nil 190 } 191 192 func (*objTool) SetConfig(config string) { 193 // config is usually used to say what binaries to invoke. 194 // Ignore entirely. 195 } 196 197 // file implements plugin.ObjFile using Go libraries 198 // (instead of invoking GNU binutils). 199 // A file represents a single executable being analyzed. 200 type file struct { 201 name string 202 offset uint64 203 sym []objfile.Sym 204 file *objfile.File 205 pcln objfile.Liner 206 207 triedDwarf bool 208 dwarf *dwarf.Data 209 } 210 211 func (f *file) Name() string { 212 return f.name 213 } 214 215 func (f *file) Base() uint64 { 216 // No support for shared libraries. 217 return 0 218 } 219 220 func (f *file) BuildID() string { 221 // No support for build ID. 222 return "" 223 } 224 225 func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) { 226 if f.pcln == nil { 227 pcln, err := f.file.PCLineTable() 228 if err != nil { 229 return nil, err 230 } 231 f.pcln = pcln 232 } 233 addr -= f.offset 234 file, line, fn := f.pcln.PCToLine(addr) 235 if fn != nil { 236 frame := []plugin.Frame{ 237 { 238 Func: fn.Name, 239 File: file, 240 Line: line, 241 }, 242 } 243 return frame, nil 244 } 245 246 frames := f.dwarfSourceLine(addr) 247 if frames != nil { 248 return frames, nil 249 } 250 251 return nil, fmt.Errorf("no line information for PC=%#x", addr) 252 } 253 254 // dwarfSourceLine tries to get file/line information using DWARF. 255 // This is for C functions that appear in the profile. 256 // Returns nil if there is no information available. 257 func (f *file) dwarfSourceLine(addr uint64) []plugin.Frame { 258 if f.dwarf == nil && !f.triedDwarf { 259 // Ignore any error--we don't care exactly why there 260 // is no DWARF info. 261 f.dwarf, _ = f.file.DWARF() 262 f.triedDwarf = true 263 } 264 265 if f.dwarf != nil { 266 r := f.dwarf.Reader() 267 unit, err := r.SeekPC(addr) 268 if err == nil { 269 if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil { 270 return frames 271 } 272 } 273 } 274 275 return nil 276 } 277 278 // dwarfSourceLineEntry tries to get file/line information from a 279 // DWARF compilation unit. Returns nil if it doesn't find anything. 280 func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []plugin.Frame { 281 lines, err := f.dwarf.LineReader(entry) 282 if err != nil { 283 return nil 284 } 285 var lentry dwarf.LineEntry 286 if err := lines.SeekPC(addr, &lentry); err != nil { 287 return nil 288 } 289 290 // Try to find the function name. 291 name := "" 292 FindName: 293 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() { 294 if entry.Tag == dwarf.TagSubprogram { 295 ranges, err := f.dwarf.Ranges(entry) 296 if err != nil { 297 return nil 298 } 299 for _, pcs := range ranges { 300 if pcs[0] <= addr && addr < pcs[1] { 301 var ok bool 302 // TODO: AT_linkage_name, AT_MIPS_linkage_name. 303 name, ok = entry.Val(dwarf.AttrName).(string) 304 if ok { 305 break FindName 306 } 307 } 308 } 309 } 310 } 311 312 // TODO: Report inlined functions. 313 314 frames := []plugin.Frame{ 315 { 316 Func: name, 317 File: lentry.File.Name, 318 Line: lentry.Line, 319 }, 320 } 321 322 return frames 323 } 324 325 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { 326 if f.sym == nil { 327 sym, err := f.file.Symbols() 328 if err != nil { 329 return nil, err 330 } 331 f.sym = sym 332 } 333 var out []*plugin.Sym 334 for _, s := range f.sym { 335 // Ignore a symbol with address 0 and size 0. 336 // An ELF STT_FILE symbol will look like that. 337 if s.Addr == 0 && s.Size == 0 { 338 continue 339 } 340 if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) { 341 out = append(out, &plugin.Sym{ 342 Name: []string{s.Name}, 343 File: f.name, 344 Start: s.Addr, 345 End: s.Addr + uint64(s.Size) - 1, 346 }) 347 } 348 } 349 return out, nil 350 } 351 352 func (f *file) Close() error { 353 f.file.Close() 354 return nil 355 }