mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/vendor: upgrade pprof to latest
This change upgrades the vendored pprof to pick up the fix for a serious issue that made the source view in browser mode blank (tracked upstream as google/pprof#621). I also had to patch pprof.go, since one of the upstream commit we included introduced a breaking change in the file interface (the Base method is now called ObjAddr and has a different signature). I've manually verified that the upgrade fixes the aforementioned issues with the source view. Fixes #45786 Change-Id: I00659ae539a2ad603758e1f06572374d483b9ddc Reviewed-on: https://go-review.googlesource.com/c/go/+/318049 Trust: Alberto Donizetti <alb.donizetti@gmail.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
parent
4c8f48ed4f
commit
68327e1aa1
14 changed files with 416 additions and 196 deletions
|
|
@ -3,7 +3,7 @@ module cmd
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5
|
github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
|
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
|
||||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
|
github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a h1:jmAp/2PZAScNd62lTD3Mcb0Ey9FvIIJtLohPhtxZJ+Q=
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e h1:pv3V0NlNSh5Q6AX/StwGLBjcLS7UN4m4Gq+V+uSecqM=
|
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e h1:pv3V0NlNSh5Q6AX/StwGLBjcLS7UN4m4Gq+V+uSecqM=
|
||||||
|
|
|
||||||
|
|
@ -232,9 +232,9 @@ func (f *file) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Base() uint64 {
|
func (f *file) ObjAddr(addr uint64) (uint64, error) {
|
||||||
// No support for shared libraries.
|
// No support for shared libraries.
|
||||||
return 0
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) BuildID() string {
|
func (f *file) BuildID() string {
|
||||||
|
|
|
||||||
4
src/cmd/vendor/github.com/google/pprof/driver/driver.go
generated
vendored
4
src/cmd/vendor/github.com/google/pprof/driver/driver.go
generated
vendored
|
|
@ -159,8 +159,8 @@ type ObjFile interface {
|
||||||
// Name returns the underlying file name, if available.
|
// Name returns the underlying file name, if available.
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// Base returns the base address to use when looking up symbols in the file.
|
// ObjAddr returns the objdump address corresponding to a runtime address.
|
||||||
Base() uint64
|
ObjAddr(addr uint64) (uint64, error)
|
||||||
|
|
||||||
// BuildID returns the GNU build ID of the file, or an empty string.
|
// BuildID returns the GNU build ID of the file, or an empty string.
|
||||||
BuildID() string
|
BuildID() string
|
||||||
|
|
|
||||||
186
src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
generated
vendored
186
src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
generated
vendored
|
|
@ -42,7 +42,12 @@ type Binutils struct {
|
||||||
rep *binrep
|
rep *binrep
|
||||||
}
|
}
|
||||||
|
|
||||||
var objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`)
|
var (
|
||||||
|
objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`)
|
||||||
|
|
||||||
|
// Defined for testing
|
||||||
|
elfOpen = elf.Open
|
||||||
|
)
|
||||||
|
|
||||||
// binrep is an immutable representation for Binutils. It is atomically
|
// binrep is an immutable representation for Binutils. It is atomically
|
||||||
// replaced on every mutation to provide thread-safe access.
|
// replaced on every mutation to provide thread-safe access.
|
||||||
|
|
@ -421,14 +426,23 @@ func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.Obj
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||||
ef, err := elf.Open(name)
|
ef, err := elfOpen(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error parsing %s: %v", name, err)
|
return nil, fmt.Errorf("error parsing %s: %v", name, err)
|
||||||
}
|
}
|
||||||
defer ef.Close()
|
defer ef.Close()
|
||||||
|
|
||||||
var stextOffset *uint64
|
buildID := ""
|
||||||
var pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
|
if f, err := os.Open(name); err == nil {
|
||||||
|
if id, err := elfexec.GetBuildID(f); err == nil {
|
||||||
|
buildID = fmt.Sprintf("%x", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
stextOffset *uint64
|
||||||
|
pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
|
||||||
|
)
|
||||||
if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) {
|
if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) {
|
||||||
// Reading all Symbols is expensive, and we only rarely need it so
|
// Reading all Symbols is expensive, and we only rarely need it so
|
||||||
// we don't want to do it every time. But if _stext happens to be
|
// we don't want to do it every time. But if _stext happens to be
|
||||||
|
|
@ -450,38 +464,29 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ph *elf.ProgHeader
|
// Check that we can compute a base for the binary. This may not be the
|
||||||
// For user space executables, find the actual program segment that is
|
// correct base value, so we don't save it. We delay computing the actual base
|
||||||
// associated with the given mapping. Skip this search if limit <= start.
|
// value until we have a sample address for this mapping, so that we can
|
||||||
// We cannot use just a check on the start address of the mapping to tell if
|
// correctly identify the associated program segment that is needed to compute
|
||||||
// it's a kernel / .ko module mapping, because with quipper address remapping
|
// the base.
|
||||||
// enabled, the address would be in the lower half of the address space.
|
if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset); err != nil {
|
||||||
if stextOffset == nil && start < limit && limit < (uint64(1)<<63) {
|
|
||||||
ph, err = elfexec.FindProgHeaderForMapping(ef, offset, limit-start)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to find program header for file %q, mapping pgoff %x, memsz=%x: %v", name, offset, limit-start, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For the kernel, find the program segment that includes the .text section.
|
|
||||||
ph = elfexec.FindTextProgHeader(ef)
|
|
||||||
}
|
|
||||||
|
|
||||||
base, err := elfexec.GetBase(&ef.FileHeader, ph, stextOffset, start, limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not identify base for %s: %v", name, err)
|
return nil, fmt.Errorf("could not identify base for %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildID := ""
|
|
||||||
if f, err := os.Open(name); err == nil {
|
|
||||||
if id, err := elfexec.GetBuildID(f); err == nil {
|
|
||||||
buildID = fmt.Sprintf("%x", id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isData := ph != nil && ph.Flags&elf.PF_X == 0
|
|
||||||
if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
|
if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
|
||||||
return &fileNM{file: file{b, name, base, buildID, isData}}, nil
|
return &fileNM{file: file{
|
||||||
|
b: b,
|
||||||
|
name: name,
|
||||||
|
buildID: buildID,
|
||||||
|
m: &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset},
|
||||||
|
}}, nil
|
||||||
}
|
}
|
||||||
return &fileAddr2Line{file: file{b, name, base, buildID, isData}}, nil
|
return &fileAddr2Line{file: file{
|
||||||
|
b: b,
|
||||||
|
name: name,
|
||||||
|
buildID: buildID,
|
||||||
|
m: &elfMapping{start: start, limit: limit, offset: offset, stextOffset: stextOffset},
|
||||||
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||||
|
|
@ -511,21 +516,119 @@ func (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFil
|
||||||
return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
|
return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// elfMapping stores the parameters of a runtime mapping that are needed to
|
||||||
|
// identify the ELF segment associated with a mapping.
|
||||||
|
type elfMapping struct {
|
||||||
|
// Runtime mapping parameters.
|
||||||
|
start, limit, offset uint64
|
||||||
|
// Offset of _stext symbol. Only defined for kernel images, nil otherwise.
|
||||||
|
stextOffset *uint64
|
||||||
|
}
|
||||||
|
|
||||||
// file implements the binutils.ObjFile interface.
|
// file implements the binutils.ObjFile interface.
|
||||||
type file struct {
|
type file struct {
|
||||||
b *binrep
|
b *binrep
|
||||||
name string
|
name string
|
||||||
base uint64
|
|
||||||
buildID string
|
buildID string
|
||||||
|
|
||||||
|
baseOnce sync.Once // Ensures the base, baseErr and isData are computed once.
|
||||||
|
base uint64
|
||||||
|
baseErr error // Any eventual error while computing the base.
|
||||||
isData bool
|
isData bool
|
||||||
|
// Mapping information. Relevant only for ELF files, nil otherwise.
|
||||||
|
m *elfMapping
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeBase computes the relocation base for the given binary file only if
|
||||||
|
// the elfMapping field is set. It populates the base and isData fields and
|
||||||
|
// returns an error.
|
||||||
|
func (f *file) computeBase(addr uint64) error {
|
||||||
|
if f == nil || f.m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if addr < f.m.start || addr >= f.m.limit {
|
||||||
|
return fmt.Errorf("specified address %x is outside the mapping range [%x, %x] for file %q", addr, f.m.start, f.m.limit, f.name)
|
||||||
|
}
|
||||||
|
ef, err := elfOpen(f.name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing %s: %v", f.name, err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
|
||||||
|
var ph *elf.ProgHeader
|
||||||
|
// For user space executables, find the actual program segment that is
|
||||||
|
// associated with the given mapping. Skip this search if limit <= start.
|
||||||
|
// We cannot use just a check on the start address of the mapping to tell if
|
||||||
|
// it's a kernel / .ko module mapping, because with quipper address remapping
|
||||||
|
// enabled, the address would be in the lower half of the address space.
|
||||||
|
if f.m.stextOffset == nil && f.m.start < f.m.limit && f.m.limit < (uint64(1)<<63) {
|
||||||
|
// Get all program headers associated with the mapping.
|
||||||
|
headers, hasLoadables := elfexec.ProgramHeadersForMapping(ef, f.m.offset, f.m.limit-f.m.start)
|
||||||
|
|
||||||
|
// Some ELF files don't contain any loadable program segments, e.g. .ko
|
||||||
|
// kernel modules. It's not an error to have no header in such cases.
|
||||||
|
if hasLoadables {
|
||||||
|
ph, err = matchUniqueHeader(headers, addr-f.m.start+f.m.offset)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find program header for file %q, ELF mapping %#v, address %x: %v", f.name, *f.m, addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For the kernel, find the program segment that includes the .text section.
|
||||||
|
ph = elfexec.FindTextProgHeader(ef)
|
||||||
|
}
|
||||||
|
|
||||||
|
base, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.stextOffset, f.m.start, f.m.limit, f.m.offset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.base = base
|
||||||
|
f.isData = ph != nil && ph.Flags&elf.PF_X == 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchUniqueHeader attempts to identify a unique header from the given list,
|
||||||
|
// using the given file offset to disambiguate between multiple segments. It
|
||||||
|
// returns an error if the header list is empty or if it cannot identify a
|
||||||
|
// unique header.
|
||||||
|
func matchUniqueHeader(headers []*elf.ProgHeader, fileOffset uint64) (*elf.ProgHeader, error) {
|
||||||
|
if len(headers) == 0 {
|
||||||
|
return nil, errors.New("no program header matches mapping info")
|
||||||
|
}
|
||||||
|
if len(headers) == 1 {
|
||||||
|
// Don't use the file offset if we already have a single header.
|
||||||
|
return headers[0], nil
|
||||||
|
}
|
||||||
|
// We have multiple input segments. Attempt to identify a unique one
|
||||||
|
// based on the given file offset.
|
||||||
|
var ph *elf.ProgHeader
|
||||||
|
for _, h := range headers {
|
||||||
|
if fileOffset >= h.Off && fileOffset < h.Off+h.Memsz {
|
||||||
|
if ph != nil {
|
||||||
|
// Assuming no other bugs, this can only happen if we have two or
|
||||||
|
// more small program segments that fit on the same page, and a
|
||||||
|
// segment other than the last one includes uninitialized data.
|
||||||
|
return nil, fmt.Errorf("found second program header (%#v) that matches file offset %x, first program header is %#v. Does first program segment contain uninitialized data?", *h, fileOffset, *ph)
|
||||||
|
}
|
||||||
|
ph = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ph == nil {
|
||||||
|
return nil, fmt.Errorf("no program header matches file offset %x", fileOffset)
|
||||||
|
}
|
||||||
|
return ph, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Name() string {
|
func (f *file) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Base() uint64 {
|
func (f *file) ObjAddr(addr uint64) (uint64, error) {
|
||||||
return f.base
|
f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
|
||||||
|
if f.baseErr != nil {
|
||||||
|
return 0, f.baseErr
|
||||||
|
}
|
||||||
|
return addr - f.base, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) BuildID() string {
|
func (f *file) BuildID() string {
|
||||||
|
|
@ -533,7 +636,11 @@ func (f *file) BuildID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
|
func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
|
||||||
return []plugin.Frame{}, nil
|
f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
|
||||||
|
if f.baseErr != nil {
|
||||||
|
return nil, f.baseErr
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Close() error {
|
func (f *file) Close() error {
|
||||||
|
|
@ -560,6 +667,10 @@ type fileNM struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
|
func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
|
||||||
|
f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
|
||||||
|
if f.baseErr != nil {
|
||||||
|
return nil, f.baseErr
|
||||||
|
}
|
||||||
if f.addr2linernm == nil {
|
if f.addr2linernm == nil {
|
||||||
addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
|
addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -579,9 +690,14 @@ type fileAddr2Line struct {
|
||||||
file
|
file
|
||||||
addr2liner *addr2Liner
|
addr2liner *addr2Liner
|
||||||
llvmSymbolizer *llvmSymbolizer
|
llvmSymbolizer *llvmSymbolizer
|
||||||
|
isData bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
|
func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
|
||||||
|
f.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })
|
||||||
|
if f.baseErr != nil {
|
||||||
|
return nil, f.baseErr
|
||||||
|
}
|
||||||
f.once.Do(f.init)
|
f.once.Do(f.init)
|
||||||
if f.llvmSymbolizer != nil {
|
if f.llvmSymbolizer != nil {
|
||||||
return f.llvmSymbolizer.addrInfo(addr)
|
return f.llvmSymbolizer.addrInfo(addr)
|
||||||
|
|
|
||||||
94
src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go
generated
vendored
94
src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go
generated
vendored
|
|
@ -284,83 +284,71 @@ func FindTextProgHeader(f *elf.File) *elf.ProgHeader {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindProgHeaderForMapping returns the loadable program segment header that is
|
// ProgramHeadersForMapping returns the loadable program segment headers that
|
||||||
// fully contained in the runtime mapping with file offset pgoff and memory size
|
// are fully contained in the runtime mapping with file offset pgoff and memory
|
||||||
// memsz, or an error if the segment cannot be determined. The function returns
|
// size memsz, and if the binary includes any loadable segments.
|
||||||
// a nil program header and no error if the ELF binary has no loadable segments.
|
func ProgramHeadersForMapping(f *elf.File, pgoff, memsz uint64) ([]*elf.ProgHeader, bool) {
|
||||||
func FindProgHeaderForMapping(f *elf.File, pgoff, memsz uint64) (*elf.ProgHeader, error) {
|
const (
|
||||||
|
// pageSize defines the virtual memory page size used by the loader. This
|
||||||
|
// value is dependent on the memory management unit of the CPU. The page
|
||||||
|
// size is 4KB virtually on all the architectures that we care about, so we
|
||||||
|
// define this metric as a constant. If we encounter architectures where
|
||||||
|
// page sie is not 4KB, we must try to guess the page size on the system
|
||||||
|
// where the profile was collected, possibly using the architecture
|
||||||
|
// specified in the ELF file header.
|
||||||
|
pageSize = 4096
|
||||||
|
pageOffsetMask = pageSize - 1
|
||||||
|
pageMask = ^uint64(pageOffsetMask)
|
||||||
|
)
|
||||||
var headers []*elf.ProgHeader
|
var headers []*elf.ProgHeader
|
||||||
loadables := 0
|
hasLoadables := false
|
||||||
for _, p := range f.Progs {
|
for _, p := range f.Progs {
|
||||||
|
// The segment must be fully included in the mapping.
|
||||||
if p.Type == elf.PT_LOAD && pgoff <= p.Off && p.Off+p.Memsz <= pgoff+memsz {
|
if p.Type == elf.PT_LOAD && pgoff <= p.Off && p.Off+p.Memsz <= pgoff+memsz {
|
||||||
|
alignedOffset := uint64(0)
|
||||||
|
if p.Off > (p.Vaddr & pageOffsetMask) {
|
||||||
|
alignedOffset = p.Off - (p.Vaddr & pageOffsetMask)
|
||||||
|
}
|
||||||
|
if alignedOffset <= pgoff {
|
||||||
headers = append(headers, &p.ProgHeader)
|
headers = append(headers, &p.ProgHeader)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if p.Type == elf.PT_LOAD {
|
if p.Type == elf.PT_LOAD {
|
||||||
loadables++
|
hasLoadables = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(headers) == 1 {
|
if len(headers) < 2 {
|
||||||
return headers[0], nil
|
return headers, hasLoadables
|
||||||
}
|
|
||||||
// Some ELF files don't contain any program segments, e.g. .ko loadable kernel
|
|
||||||
// modules. Don't return an error in such cases.
|
|
||||||
if loadables == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if len(headers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no program header matches file offset %x and memory size %x", pgoff, memsz)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Segments are mapped page aligned. In some cases, segments may be smaller
|
// If we have more than one matching segments, try a strict check on the
|
||||||
// than a page, which causes the next segment to start at a file offset that
|
// segment memory size. We use a heuristic to compute the minimum mapping size
|
||||||
// is logically on the same page if we were to align file offsets by page.
|
// required for a segment, assuming mappings are page aligned.
|
||||||
// Example:
|
|
||||||
// LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
|
|
||||||
// 0x00000000000006fc 0x00000000000006fc R E 0x200000
|
|
||||||
// LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
|
|
||||||
// 0x0000000000000230 0x0000000000000238 RW 0x200000
|
|
||||||
//
|
|
||||||
// In this case, perf records the following mappings for this executable:
|
|
||||||
// 0 0 [0xc0]: PERF_RECORD_MMAP2 87867/87867: [0x400000(0x1000) @ 0 00:3c 512041 0]: r-xp exename
|
|
||||||
// 0 0 [0xc0]: PERF_RECORD_MMAP2 87867/87867: [0x600000(0x2000) @ 0 00:3c 512041 0]: rw-p exename
|
|
||||||
//
|
|
||||||
// Both mappings have file offset 0. The first mapping is one page length and
|
|
||||||
// it can include only the first loadable segment. Due to page alignment, the
|
|
||||||
// second mapping starts also at file offset 0, and it spans two pages. It can
|
|
||||||
// include both the first and the second loadable segments. We must return the
|
|
||||||
// correct program header to compute the correct base offset.
|
|
||||||
//
|
|
||||||
// We cannot use the mapping protections to distinguish between segments,
|
|
||||||
// because protections are not passed through to this function.
|
|
||||||
// We cannot use the start address to differentiate between segments, because
|
|
||||||
// with ASLR, the mapping start address can be any value.
|
|
||||||
//
|
|
||||||
// We use a heuristic to compute the minimum mapping size required for a
|
|
||||||
// segment, assuming mappings are 4k page aligned, and return the segment that
|
|
||||||
// matches the given mapping size.
|
|
||||||
const pageSize = 4096
|
|
||||||
|
|
||||||
// The memory size based heuristic makes sense only if the mapping size is a
|
// The memory size based heuristic makes sense only if the mapping size is a
|
||||||
// multiple of 4k page size.
|
// multiple of page size.
|
||||||
if memsz%pageSize != 0 {
|
if memsz%pageSize != 0 {
|
||||||
return nil, fmt.Errorf("mapping size = %x and %d segments match the passed in mapping", memsz, len(headers))
|
return headers, hasLoadables
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return an error if no segment, or multiple segments match the size, so we can debug.
|
// Return all found headers if we cannot narrow the selection to a single
|
||||||
|
// program segment.
|
||||||
var ph *elf.ProgHeader
|
var ph *elf.ProgHeader
|
||||||
pageMask := ^uint64(pageSize - 1)
|
|
||||||
for _, h := range headers {
|
for _, h := range headers {
|
||||||
wantSize := (h.Vaddr+h.Memsz+pageSize-1)&pageMask - (h.Vaddr & pageMask)
|
wantSize := (h.Vaddr+h.Memsz+pageSize-1)&pageMask - (h.Vaddr & pageMask)
|
||||||
if wantSize != memsz {
|
if wantSize != memsz {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ph != nil {
|
if ph != nil {
|
||||||
return nil, fmt.Errorf("found second program header (%#v) that matches memsz %x, first program header is %#v", *h, memsz, *ph)
|
// Found a second program header matching, so return all previously
|
||||||
|
// identified headers.
|
||||||
|
return headers, hasLoadables
|
||||||
}
|
}
|
||||||
ph = h
|
ph = h
|
||||||
}
|
}
|
||||||
if ph == nil {
|
if ph == nil {
|
||||||
return nil, fmt.Errorf("found %d matching program headers, but none matches mapping size %x", len(headers), memsz)
|
// No matching header for the strict check. Return all previously identified
|
||||||
|
// headers.
|
||||||
|
return headers, hasLoadables
|
||||||
}
|
}
|
||||||
return ph, nil
|
return []*elf.ProgHeader{ph}, hasLoadables
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go
generated
vendored
5
src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go
generated
vendored
|
|
@ -131,8 +131,9 @@ type ObjFile interface {
|
||||||
// Name returns the underlyinf file name, if available
|
// Name returns the underlyinf file name, if available
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// Base returns the base address to use when looking up symbols in the file.
|
// ObjAddr returns the objdump (linker) address corresponding to a runtime
|
||||||
Base() uint64
|
// address, and an error.
|
||||||
|
ObjAddr(addr uint64) (uint64, error)
|
||||||
|
|
||||||
// BuildID returns the GNU build ID of the file, or an empty string.
|
// BuildID returns the GNU build ID of the file, or an empty string.
|
||||||
BuildID() string
|
BuildID() string
|
||||||
|
|
|
||||||
15
src/cmd/vendor/github.com/google/pprof/internal/report/report.go
generated
vendored
15
src/cmd/vendor/github.com/google/pprof/internal/report/report.go
generated
vendored
|
|
@ -445,7 +445,7 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ns := annotateAssembly(insts, sns, s.base)
|
ns := annotateAssembly(insts, sns, s.file)
|
||||||
|
|
||||||
fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
|
fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
|
||||||
for _, name := range s.sym.Name[1:] {
|
for _, name := range s.sym.Name[1:] {
|
||||||
|
|
@ -534,7 +534,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex
|
||||||
addr = *address
|
addr = *address
|
||||||
}
|
}
|
||||||
msyms, err := f.Symbols(rx, addr)
|
msyms, err := f.Symbols(rx, addr)
|
||||||
base := f.Base()
|
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|
@ -543,7 +542,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex
|
||||||
objSyms = append(objSyms,
|
objSyms = append(objSyms,
|
||||||
&objSymbol{
|
&objSymbol{
|
||||||
sym: ms,
|
sym: ms,
|
||||||
base: base,
|
|
||||||
file: f,
|
file: f,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -558,7 +556,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex
|
||||||
// added to correspond to sample addresses
|
// added to correspond to sample addresses
|
||||||
type objSymbol struct {
|
type objSymbol struct {
|
||||||
sym *plugin.Sym
|
sym *plugin.Sym
|
||||||
base uint64
|
|
||||||
file plugin.ObjFile
|
file plugin.ObjFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -578,8 +575,7 @@ func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.N
|
||||||
for _, s := range symbols {
|
for _, s := range symbols {
|
||||||
// Gather samples for this symbol.
|
// Gather samples for this symbol.
|
||||||
for _, n := range ns {
|
for _, n := range ns {
|
||||||
address := n.Info.Address - s.base
|
if address, err := s.file.ObjAddr(n.Info.Address); err == nil && address >= s.sym.Start && address < s.sym.End {
|
||||||
if address >= s.sym.Start && address < s.sym.End {
|
|
||||||
symNodes[s] = append(symNodes[s], n)
|
symNodes[s] = append(symNodes[s], n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -621,7 +617,7 @@ func (a *assemblyInstruction) cumValue() int64 {
|
||||||
// annotateAssembly annotates a set of assembly instructions with a
|
// annotateAssembly annotates a set of assembly instructions with a
|
||||||
// set of samples. It returns a set of nodes to display. base is an
|
// set of samples. It returns a set of nodes to display. base is an
|
||||||
// offset to adjust the sample addresses.
|
// offset to adjust the sample addresses.
|
||||||
func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, base uint64) []assemblyInstruction {
|
func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, file plugin.ObjFile) []assemblyInstruction {
|
||||||
// Add end marker to simplify printing loop.
|
// Add end marker to simplify printing loop.
|
||||||
insts = append(insts, plugin.Inst{
|
insts = append(insts, plugin.Inst{
|
||||||
Addr: ^uint64(0),
|
Addr: ^uint64(0),
|
||||||
|
|
@ -645,7 +641,10 @@ func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, base uint64) []a
|
||||||
|
|
||||||
// Sum all the samples until the next instruction (to account
|
// Sum all the samples until the next instruction (to account
|
||||||
// for samples attributed to the middle of an instruction).
|
// for samples attributed to the middle of an instruction).
|
||||||
for next := insts[ix+1].Addr; s < len(samples) && samples[s].Info.Address-base < next; s++ {
|
for next := insts[ix+1].Addr; s < len(samples); s++ {
|
||||||
|
if addr, err := file.ObjAddr(samples[s].Info.Address); err != nil || addr >= next {
|
||||||
|
break
|
||||||
|
}
|
||||||
sample := samples[s]
|
sample := samples[s]
|
||||||
n.flatDiv += sample.FlatDiv
|
n.flatDiv += sample.FlatDiv
|
||||||
n.flat += sample.Flat
|
n.flat += sample.Flat
|
||||||
|
|
|
||||||
173
src/cmd/vendor/github.com/google/pprof/internal/report/source.go
generated
vendored
173
src/cmd/vendor/github.com/google/pprof/internal/report/source.go
generated
vendored
|
|
@ -132,6 +132,7 @@ func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
|
||||||
// sourcePrinter holds state needed for generating source+asm HTML listing.
|
// sourcePrinter holds state needed for generating source+asm HTML listing.
|
||||||
type sourcePrinter struct {
|
type sourcePrinter struct {
|
||||||
reader *sourceReader
|
reader *sourceReader
|
||||||
|
synth *synthCode
|
||||||
objectTool plugin.ObjTool
|
objectTool plugin.ObjTool
|
||||||
objects map[string]plugin.ObjFile // Opened object files
|
objects map[string]plugin.ObjFile // Opened object files
|
||||||
sym *regexp.Regexp // May be nil
|
sym *regexp.Regexp // May be nil
|
||||||
|
|
@ -146,6 +147,12 @@ type sourcePrinter struct {
|
||||||
prettyNames map[string]string
|
prettyNames map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addrInfo holds information for an address we are interested in.
|
||||||
|
type addrInfo struct {
|
||||||
|
loc *profile.Location // Always non-nil
|
||||||
|
obj plugin.ObjFile // May be nil
|
||||||
|
}
|
||||||
|
|
||||||
// instructionInfo holds collected information for an instruction.
|
// instructionInfo holds collected information for an instruction.
|
||||||
type instructionInfo struct {
|
type instructionInfo struct {
|
||||||
objAddr uint64 // Address in object file (with base subtracted out)
|
objAddr uint64 // Address in object file (with base subtracted out)
|
||||||
|
|
@ -207,6 +214,7 @@ func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) er
|
||||||
func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
|
func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
|
||||||
sp := &sourcePrinter{
|
sp := &sourcePrinter{
|
||||||
reader: newSourceReader(sourcePath, rpt.options.TrimPath),
|
reader: newSourceReader(sourcePath, rpt.options.TrimPath),
|
||||||
|
synth: newSynthCode(rpt.prof.Mapping),
|
||||||
objectTool: obj,
|
objectTool: obj,
|
||||||
objects: map[string]plugin.ObjFile{},
|
objects: map[string]plugin.ObjFile{},
|
||||||
sym: rpt.options.Symbol,
|
sym: rpt.options.Symbol,
|
||||||
|
|
@ -225,19 +233,21 @@ func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs := map[uint64]bool{}
|
addrs := map[uint64]addrInfo{}
|
||||||
flat := map[uint64]int64{}
|
flat := map[uint64]int64{}
|
||||||
cum := map[uint64]int64{}
|
cum := map[uint64]int64{}
|
||||||
|
|
||||||
// Record an interest in the function corresponding to lines[index].
|
// Record an interest in the function corresponding to lines[index].
|
||||||
markInterest := func(addr uint64, lines []profile.Line, index int) {
|
markInterest := func(addr uint64, loc *profile.Location, index int) {
|
||||||
fn := lines[index]
|
fn := loc.Line[index]
|
||||||
if fn.Function == nil {
|
if fn.Function == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sp.interest[fn.Function.Name] = true
|
sp.interest[fn.Function.Name] = true
|
||||||
sp.interest[fn.Function.SystemName] = true
|
sp.interest[fn.Function.SystemName] = true
|
||||||
addrs[addr] = true
|
if _, ok := addrs[addr]; !ok {
|
||||||
|
addrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if sp.sym matches line.
|
// See if sp.sym matches line.
|
||||||
|
|
@ -270,15 +280,21 @@ func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourc
|
||||||
sp.prettyNames[line.Function.SystemName] = line.Function.Name
|
sp.prettyNames[line.Function.SystemName] = line.Function.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
cum[loc.Address] += value
|
addr := loc.Address
|
||||||
if i == 0 {
|
if addr == 0 {
|
||||||
flat[loc.Address] += value
|
// Some profiles are missing valid addresses.
|
||||||
|
addr = sp.synth.address(loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sp.sym == nil || (address != nil && loc.Address == *address) {
|
cum[addr] += value
|
||||||
|
if i == 0 {
|
||||||
|
flat[addr] += value
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp.sym == nil || (address != nil && addr == *address) {
|
||||||
// Interested in top-level entry of stack.
|
// Interested in top-level entry of stack.
|
||||||
if len(loc.Line) > 0 {
|
if len(loc.Line) > 0 {
|
||||||
markInterest(loc.Address, loc.Line, len(loc.Line)-1)
|
markInterest(addr, loc, len(loc.Line)-1)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -287,7 +303,7 @@ func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourc
|
||||||
matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))
|
matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))
|
||||||
for j, line := range loc.Line {
|
for j, line := range loc.Line {
|
||||||
if (j == 0 && matchFile) || matches(line) {
|
if (j == 0 && matchFile) || matches(line) {
|
||||||
markInterest(loc.Address, loc.Line, j)
|
markInterest(addr, loc, j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -306,10 +322,11 @@ func (sp *sourcePrinter) close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]bool, flat map[uint64]int64) {
|
func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) {
|
||||||
// We found interesting addresses (ones with non-zero samples) above.
|
// We found interesting addresses (ones with non-zero samples) above.
|
||||||
// Get covering address ranges and disassemble the ranges.
|
// Get covering address ranges and disassemble the ranges.
|
||||||
ranges := sp.splitIntoRanges(rpt.prof, addrs, flat)
|
ranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat)
|
||||||
|
sp.handleUnprocessed(addrs, unprocessed)
|
||||||
|
|
||||||
// Trim ranges if there are too many.
|
// Trim ranges if there are too many.
|
||||||
const maxRanges = 25
|
const maxRanges = 25
|
||||||
|
|
@ -321,9 +338,18 @@ func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]bool, fla
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range ranges {
|
for _, r := range ranges {
|
||||||
base := r.obj.Base()
|
objBegin, err := r.obj.ObjAddr(r.begin)
|
||||||
insts, err := sp.objectTool.Disasm(r.mapping.File, r.begin-base, r.end-base,
|
if err != nil {
|
||||||
rpt.options.IntelSyntax)
|
fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range start %x: %v\n", r.begin, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
objEnd, err := r.obj.ObjAddr(r.end)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range end %x: %v\n", r.end, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := r.begin - objBegin
|
||||||
|
insts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(sanjay): Report that the covered addresses are missing.
|
// TODO(sanjay): Report that the covered addresses are missing.
|
||||||
continue
|
continue
|
||||||
|
|
@ -385,6 +411,12 @@ func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]bool, fla
|
||||||
frames = lastFrames
|
frames = lastFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sp.addStack(addr, frames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {
|
||||||
// See if the stack contains a function we are interested in.
|
// See if the stack contains a function we are interested in.
|
||||||
for i, f := range frames {
|
for i, f := range frames {
|
||||||
if !sp.interest[f.Func] {
|
if !sp.interest[f.Func] {
|
||||||
|
|
@ -419,44 +451,75 @@ func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]bool, fla
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// synthAsm is the special disassembler value used for instructions without an object file.
|
||||||
|
const synthAsm = ""
|
||||||
|
|
||||||
|
// handleUnprocessed handles addresses that were skipped by splitIntoRanges because they
|
||||||
|
// did not belong to a known object file.
|
||||||
|
func (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) {
|
||||||
|
// makeFrames synthesizes a []plugin.Frame list for the specified address.
|
||||||
|
// The result will typically have length 1, but may be longer if address corresponds
|
||||||
|
// to inlined calls.
|
||||||
|
makeFrames := func(addr uint64) []plugin.Frame {
|
||||||
|
loc := addrs[addr].loc
|
||||||
|
stack := make([]plugin.Frame, 0, len(loc.Line))
|
||||||
|
for _, line := range loc.Line {
|
||||||
|
fn := line.Function
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack = append(stack, plugin.Frame{
|
||||||
|
Func: fn.Name,
|
||||||
|
File: fn.Filename,
|
||||||
|
Line: int(line.Line),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return stack
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range unprocessed {
|
||||||
|
frames := makeFrames(addr)
|
||||||
|
x := instructionInfo{
|
||||||
|
objAddr: addr,
|
||||||
|
length: 1,
|
||||||
|
disasm: synthAsm,
|
||||||
|
}
|
||||||
|
if len(frames) > 0 {
|
||||||
|
x.file = frames[0].File
|
||||||
|
x.line = frames[0].Line
|
||||||
|
}
|
||||||
|
sp.insts[addr] = x
|
||||||
|
|
||||||
|
sp.addStack(addr, frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitIntoRanges converts the set of addresses we are interested in into a set of address
|
// splitIntoRanges converts the set of addresses we are interested in into a set of address
|
||||||
// ranges to disassemble.
|
// ranges to disassemble. It also returns the set of addresses found that did not have an
|
||||||
func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, set map[uint64]bool, flat map[uint64]int64) []addressRange {
|
// associated object file and were therefore not added to an address range.
|
||||||
// List of mappings so we can stop expanding address ranges at mapping boundaries.
|
func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {
|
||||||
mappings := append([]*profile.Mapping{}, prof.Mapping...)
|
// Partition addresses into two sets: ones with a known object file, and ones without.
|
||||||
sort.Slice(mappings, func(i, j int) bool { return mappings[i].Start < mappings[j].Start })
|
var addrs, unprocessed []uint64
|
||||||
|
for addr, info := range addrMap {
|
||||||
var result []addressRange
|
if info.obj != nil {
|
||||||
addrs := make([]uint64, 0, len(set))
|
|
||||||
for addr := range set {
|
|
||||||
addrs = append(addrs, addr)
|
addrs = append(addrs, addr)
|
||||||
|
} else {
|
||||||
|
unprocessed = append(unprocessed, addr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] })
|
sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] })
|
||||||
|
|
||||||
mappingIndex := 0
|
|
||||||
const expand = 500 // How much to expand range to pick up nearby addresses.
|
const expand = 500 // How much to expand range to pick up nearby addresses.
|
||||||
|
var result []addressRange
|
||||||
for i, n := 0, len(addrs); i < n; {
|
for i, n := 0, len(addrs); i < n; {
|
||||||
begin, end := addrs[i], addrs[i]
|
begin, end := addrs[i], addrs[i]
|
||||||
sum := flat[begin]
|
sum := flat[begin]
|
||||||
i++
|
i++
|
||||||
|
|
||||||
// Advance to mapping containing addrs[i]
|
info := addrMap[begin]
|
||||||
for mappingIndex < len(mappings) && mappings[mappingIndex].Limit <= begin {
|
m := info.loc.Mapping
|
||||||
mappingIndex++
|
obj := info.obj // Non-nil because of the partitioning done above.
|
||||||
}
|
|
||||||
if mappingIndex >= len(mappings) {
|
|
||||||
// TODO(sanjay): Report missed address and its samples.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m := mappings[mappingIndex]
|
|
||||||
obj := sp.objectFile(m)
|
|
||||||
if obj == nil {
|
|
||||||
// TODO(sanjay): Report missed address and its samples.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find following addresses that are close enough to addrs[i].
|
// Find following addresses that are close enough to addrs[i].
|
||||||
for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {
|
for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {
|
||||||
|
|
@ -479,7 +542,7 @@ func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, set map[uint64]b
|
||||||
|
|
||||||
result = append(result, addressRange{begin, end, obj, m, sum})
|
result = append(result, addressRange{begin, end, obj, m, sum})
|
||||||
}
|
}
|
||||||
return result
|
return result, unprocessed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
|
func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
|
||||||
|
|
@ -665,9 +728,12 @@ func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {
|
||||||
return funcs
|
return funcs
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectFile return the object for the named file, opening it if necessary.
|
// objectFile return the object for the specified mapping, opening it if necessary.
|
||||||
// It returns nil on error.
|
// It returns nil on error.
|
||||||
func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
|
func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if object, ok := sp.objects[m.File]; ok {
|
if object, ok := sp.objects[m.File]; ok {
|
||||||
return object // May be nil if we detected an error earlier.
|
return object // May be nil if we detected an error earlier.
|
||||||
}
|
}
|
||||||
|
|
@ -725,12 +791,28 @@ func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineConte
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nestedInfo := false
|
||||||
|
cl := "deadsrc"
|
||||||
|
for _, an := range assembly {
|
||||||
|
if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
|
||||||
|
nestedInfo = true
|
||||||
|
cl = "livesrc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w,
|
fmt.Fprintf(w,
|
||||||
"<span class=line> %6d</span> <span class=deadsrc> %10s %10s %8s %s </span>",
|
"<span class=line> %6d</span> <span class=%s> %10s %10s %8s %s </span>",
|
||||||
lineNo,
|
lineNo, cl,
|
||||||
valueOrDot(flat, rpt), valueOrDot(cum, rpt),
|
valueOrDot(flat, rpt), valueOrDot(cum, rpt),
|
||||||
"", template.HTMLEscapeString(lineContents))
|
"", template.HTMLEscapeString(lineContents))
|
||||||
|
if nestedInfo {
|
||||||
srcIndent := indentation(lineContents)
|
srcIndent := indentation(lineContents)
|
||||||
|
printNested(w, srcIndent, assembly, reader, rpt)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
|
||||||
fmt.Fprint(w, "<span class=asm>")
|
fmt.Fprint(w, "<span class=asm>")
|
||||||
var curCalls []callID
|
var curCalls []callID
|
||||||
for i, an := range assembly {
|
for i, an := range assembly {
|
||||||
|
|
@ -763,6 +845,9 @@ func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineConte
|
||||||
template.HTMLEscapeString(filepath.Base(c.file)), c.line)
|
template.HTMLEscapeString(filepath.Base(c.file)), c.line)
|
||||||
}
|
}
|
||||||
curCalls = an.inlineCalls
|
curCalls = an.inlineCalls
|
||||||
|
if an.instruction == synthAsm {
|
||||||
|
continue
|
||||||
|
}
|
||||||
text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
|
text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
|
||||||
fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
|
fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
|
||||||
"", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
|
"", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
|
||||||
|
|
@ -772,7 +857,7 @@ func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineConte
|
||||||
// would cause double-escaping of file name.
|
// would cause double-escaping of file name.
|
||||||
fileline)
|
fileline)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(w, "</span>")
|
fmt.Fprint(w, "</span>")
|
||||||
}
|
}
|
||||||
|
|
||||||
// printFunctionClosing prints the end of a function in a weblist report.
|
// printFunctionClosing prints the end of a function in a weblist report.
|
||||||
|
|
|
||||||
7
src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go
generated
vendored
7
src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go
generated
vendored
|
|
@ -40,14 +40,7 @@ h1 {
|
||||||
.inlinesrc {
|
.inlinesrc {
|
||||||
color: #000066;
|
color: #000066;
|
||||||
}
|
}
|
||||||
.deadsrc {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.deadsrc:hover {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
}
|
|
||||||
.livesrc {
|
.livesrc {
|
||||||
color: #0000ff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.livesrc:hover {
|
.livesrc:hover {
|
||||||
|
|
|
||||||
39
src/cmd/vendor/github.com/google/pprof/internal/report/synth.go
generated
vendored
Normal file
39
src/cmd/vendor/github.com/google/pprof/internal/report/synth.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/pprof/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// synthCode assigns addresses to locations without an address.
|
||||||
|
type synthCode struct {
|
||||||
|
next uint64
|
||||||
|
addr map[*profile.Location]uint64 // Synthesized address assigned to a location
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSynthCode(mappings []*profile.Mapping) *synthCode {
|
||||||
|
// Find a larger address than any mapping.
|
||||||
|
s := &synthCode{next: 1}
|
||||||
|
for _, m := range mappings {
|
||||||
|
if s.next < m.Limit {
|
||||||
|
s.next = m.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// address returns the synthetic address for loc, creating one if needed.
|
||||||
|
func (s *synthCode) address(loc *profile.Location) uint64 {
|
||||||
|
if loc.Address != 0 {
|
||||||
|
panic("can only synthesize addresses for locations without an address")
|
||||||
|
}
|
||||||
|
if addr, ok := s.addr[loc]; ok {
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
if s.addr == nil {
|
||||||
|
s.addr = map[*profile.Location]uint64{}
|
||||||
|
}
|
||||||
|
addr := s.next
|
||||||
|
s.next++
|
||||||
|
s.addr[loc] = addr
|
||||||
|
return addr
|
||||||
|
}
|
||||||
2
src/cmd/vendor/github.com/google/pprof/profile/encode.go
generated
vendored
2
src/cmd/vendor/github.com/google/pprof/profile/encode.go
generated
vendored
|
|
@ -308,7 +308,7 @@ func (p *Profile) postDecode() error {
|
||||||
if l.strX != 0 {
|
if l.strX != 0 {
|
||||||
value, err = getString(p.stringTable, &l.strX, err)
|
value, err = getString(p.stringTable, &l.strX, err)
|
||||||
labels[key] = append(labels[key], value)
|
labels[key] = append(labels[key], value)
|
||||||
} else if l.numX != 0 {
|
} else if l.numX != 0 || l.unitX != 0 {
|
||||||
numValues := numLabels[key]
|
numValues := numLabels[key]
|
||||||
units := numUnits[key]
|
units := numUnits[key]
|
||||||
if l.unitX != 0 {
|
if l.unitX != 0 {
|
||||||
|
|
|
||||||
1
src/cmd/vendor/github.com/google/pprof/profile/merge.go
generated
vendored
1
src/cmd/vendor/github.com/google/pprof/profile/merge.go
generated
vendored
|
|
@ -231,7 +231,6 @@ func (pm *profileMerger) mapLocation(src *Location) *Location {
|
||||||
}
|
}
|
||||||
|
|
||||||
if l, ok := pm.locationsByID[src.ID]; ok {
|
if l, ok := pm.locationsByID[src.ID]; ok {
|
||||||
pm.locationsByID[src.ID] = l
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
src/cmd/vendor/modules.txt
vendored
2
src/cmd/vendor/modules.txt
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
# github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5
|
# github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/google/pprof/driver
|
github.com/google/pprof/driver
|
||||||
github.com/google/pprof/internal/binutils
|
github.com/google/pprof/internal/binutils
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue