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