1// Copyright 2014 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// pprof is a tool for visualization of profile.data. It is based on6// the upstream version at github.com/google/pprof, with minor7// modifications specific to the Go distribution. Please consider8// upstreaming any modifications to these packages.910package main1112import (13 "crypto/tls"14 "debug/dwarf"15 "flag"16 "fmt"17 "io"18 "net/http"19 "net/url"20 "os"21 "regexp"22 "strconv"23 "strings"24 "sync"25 "time"2627 "cmd/internal/disasm"28 "cmd/internal/objfile"29 "cmd/internal/telemetry/counter"3031 "github.com/google/pprof/driver"32 "github.com/google/pprof/profile"33)3435func main() {36 counter.Open()37 counter.Inc("pprof/invocations")38 options := &driver.Options{39 Fetch: new(fetcher),40 Obj: new(objTool),41 UI: newUI(),42 }43 err := driver.PProf(options)44 counter.CountFlags("pprof/flag:", *flag.CommandLine) // pprof will use the flag package as its default45 if err != nil {46 fmt.Fprintf(os.Stderr, "%v\n", err)47 os.Exit(2)48 }49}5051type fetcher struct {52}5354func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) {55 // Firstly, determine if the src is an existing file on the disk.56 // If it is a file, let regular pprof open it.57 // If it is not a file, when the src contains `:`58 // (e.g. mem_2023-11-02_03:55:24 or abc:123/mem_2023-11-02_03:55:24),59 // url.Parse will recognize it as a link and ultimately report an error,60 // similar to `abc:123/mem_2023-11-02_03:55:24:61 // Get "http://abc:123/mem_2023-11-02_03:55:24": dial tcp: lookup abc: no such host`62 if _, openErr := os.Stat(src); openErr == nil {63 return nil, "", nil64 }65 sourceURL, timeout := adjustURL(src, duration, timeout)66 if sourceURL == "" {67 // Could not recognize URL, let regular pprof attempt to fetch the profile (eg. from a file)68 return nil, "", nil69 }70 fmt.Fprintln(os.Stderr, "Fetching profile over HTTP from", sourceURL)71 if duration > 0 {72 fmt.Fprintf(os.Stderr, "Please wait... (%v)\n", duration)73 }74 p, err := getProfile(sourceURL, timeout)75 return p, sourceURL, err76}7778func getProfile(source string, timeout time.Duration) (*profile.Profile, error) {79 url, err := url.Parse(source)80 if err != nil {81 return nil, err82 }8384 var tlsConfig *tls.Config85 if url.Scheme == "https+insecure" {86 tlsConfig = &tls.Config{87 InsecureSkipVerify: true,88 }89 url.Scheme = "https"90 source = url.String()91 }9293 client := &http.Client{94 Transport: &http.Transport{95 ResponseHeaderTimeout: timeout + 5*time.Second,96 Proxy: http.ProxyFromEnvironment,97 TLSClientConfig: tlsConfig,98 },99 }100 resp, err := client.Get(source)101 if err != nil {102 return nil, err103 }104 defer resp.Body.Close()105 if resp.StatusCode != http.StatusOK {106 return nil, statusCodeError(resp)107 }108 return profile.Parse(resp.Body)109}110111func statusCodeError(resp *http.Response) error {112 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {113 // error is from pprof endpoint114 if body, err := io.ReadAll(resp.Body); err == nil {115 return fmt.Errorf("server response: %s - %s", resp.Status, body)116 }117 }118 return fmt.Errorf("server response: %s", resp.Status)119}120121// cpuProfileHandler is the Go pprof CPU profile handler URL.122const cpuProfileHandler = "/debug/pprof/profile"123124// adjustURL applies the duration/timeout values and Go specific defaults.125func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {126 u, err := url.Parse(source)127 if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {128 // Try adding http:// to catch sources of the form hostname:port/path.129 // url.Parse treats "hostname" as the scheme.130 u, err = url.Parse("http://" + source)131 }132 if err != nil || u.Host == "" {133 return "", 0134 }135136 if u.Path == "" || u.Path == "/" {137 u.Path = cpuProfileHandler138 }139140 // Apply duration/timeout overrides to URL.141 values := u.Query()142 if duration > 0 {143 values.Set("seconds", fmt.Sprint(int(duration.Seconds())))144 } else {145 if urlSeconds := values.Get("seconds"); urlSeconds != "" {146 if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {147 duration = time.Duration(us) * time.Second148 }149 }150 }151 if timeout <= 0 {152 if duration > 0 {153 timeout = duration + duration/2154 } else {155 timeout = 60 * time.Second156 }157 }158 u.RawQuery = values.Encode()159 return u.String(), timeout160}161162// objTool implements driver.ObjTool using Go libraries163// (instead of invoking GNU binutils).164type objTool struct {165 mu sync.Mutex166 disasmCache map[string]*disasm.Disasm167}168169func (*objTool) Open(name string, start, limit, offset uint64, relocationSymbol string) (driver.ObjFile, error) {170 of, err := objfile.Open(name)171 if err != nil {172 return nil, err173 }174 f := &file{175 name: name,176 file: of,177 }178 if start != 0 {179 if load, err := of.LoadAddress(); err == nil {180 f.offset = start - load181 }182 }183 return f, nil184}185186func (*objTool) Demangle(names []string) (map[string]string, error) {187 // No C++, nothing to demangle.188 return make(map[string]string), nil189}190191func (t *objTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]driver.Inst, error) {192 if intelSyntax {193 return nil, fmt.Errorf("printing assembly in Intel syntax is not supported")194 }195 d, err := t.cachedDisasm(file)196 if err != nil {197 return nil, err198 }199 var asm []driver.Inst200 d.Decode(start, end, nil, false, func(pc, size uint64, file string, line int, text string) {201 asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text})202 })203 return asm, nil204}205206func (t *objTool) cachedDisasm(file string) (*disasm.Disasm, error) {207 t.mu.Lock()208 defer t.mu.Unlock()209 if t.disasmCache == nil {210 t.disasmCache = make(map[string]*disasm.Disasm)211 }212 d := t.disasmCache[file]213 if d != nil {214 return d, nil215 }216 f, err := objfile.Open(file)217 if err != nil {218 return nil, err219 }220 d, err = disasm.DisasmForFile(f)221 f.Close()222 if err != nil {223 return nil, err224 }225 t.disasmCache[file] = d226 return d, nil227}228229func (*objTool) SetConfig(config string) {230 // config is usually used to say what binaries to invoke.231 // Ignore entirely.232}233234// file implements driver.ObjFile using Go libraries235// (instead of invoking GNU binutils).236// A file represents a single executable being analyzed.237type file struct {238 name string239 offset uint64240 sym []objfile.Sym241 file *objfile.File242 pcln objfile.Liner243244 triedDwarf bool245 dwarf *dwarf.Data246}247248func (f *file) Name() string {249 return f.name250}251252func (f *file) ObjAddr(addr uint64) (uint64, error) {253 return addr - f.offset, nil254}255256func (f *file) BuildID() string {257 // No support for build ID.258 return ""259}260261func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {262 if f.pcln == nil {263 pcln, err := f.file.PCLineTable()264 if err != nil {265 return nil, err266 }267 f.pcln = pcln268 }269 addr -= f.offset270 file, line, fn := f.pcln.PCToLine(addr)271 if fn != nil {272 frame := []driver.Frame{273 {274 Func: fn.Name,275 File: file,276 Line: line,277 },278 }279 return frame, nil280 }281282 frames := f.dwarfSourceLine(addr)283 if frames != nil {284 return frames, nil285 }286287 return nil, fmt.Errorf("no line information for PC=%#x", addr)288}289290// dwarfSourceLine tries to get file/line information using DWARF.291// This is for C functions that appear in the profile.292// Returns nil if there is no information available.293func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {294 if f.dwarf == nil && !f.triedDwarf {295 // Ignore any error--we don't care exactly why there296 // is no DWARF info.297 f.dwarf, _ = f.file.DWARF()298 f.triedDwarf = true299 }300301 if f.dwarf != nil {302 r := f.dwarf.Reader()303 unit, err := r.SeekPC(addr)304 if err == nil {305 if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {306 return frames307 }308 }309 }310311 return nil312}313314// dwarfSourceLineEntry tries to get file/line information from a315// DWARF compilation unit. Returns nil if it doesn't find anything.316func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {317 lines, err := f.dwarf.LineReader(entry)318 if err != nil {319 return nil320 }321 var lentry dwarf.LineEntry322 if err := lines.SeekPC(addr, &lentry); err != nil {323 return nil324 }325326 // Try to find the function name.327 name := ""328FindName:329 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {330 if entry.Tag == dwarf.TagSubprogram {331 ranges, err := f.dwarf.Ranges(entry)332 if err != nil {333 return nil334 }335 for _, pcs := range ranges {336 if pcs[0] <= addr && addr < pcs[1] {337 var ok bool338 // TODO: AT_linkage_name, AT_MIPS_linkage_name.339 name, ok = entry.Val(dwarf.AttrName).(string)340 if ok {341 break FindName342 }343 }344 }345 }346 }347348 // TODO: Report inlined functions.349350 frames := []driver.Frame{351 {352 Func: name,353 File: lentry.File.Name,354 Line: lentry.Line,355 },356 }357358 return frames359}360361func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {362 if f.sym == nil {363 sym, err := f.file.Symbols()364 if err != nil {365 return nil, err366 }367 f.sym = sym368 }369 var out []*driver.Sym370 for _, s := range f.sym {371 // Ignore a symbol with address 0 and size 0.372 // An ELF STT_FILE symbol will look like that.373 if s.Addr == 0 && s.Size == 0 {374 continue375 }376 if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {377 out = append(out, &driver.Sym{378 Name: []string{s.Name},379 File: f.name,380 Start: s.Addr,381 End: s.Addr + uint64(s.Size) - 1,382 })383 }384 }385 return out, nil386}387388func (f *file) Close() error {389 f.file.Close()390 return nil391}392393// newUI will be set in readlineui.go in some platforms394// for interactive readline functionality.395var newUI = func() driver.UI { return nil }
Findings
✓ No findings reported for this file.