]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/vendor: upgrade pprof to latest
authorAlberto Donizetti <alb.donizetti@gmail.com>
Fri, 7 May 2021 17:10:28 +0000 (19:10 +0200)
committerAlberto Donizetti <alb.donizetti@gmail.com>
Sat, 8 May 2021 14:59:49 +0000 (14:59 +0000)
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>
14 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/pprof/pprof.go
src/cmd/vendor/github.com/google/pprof/driver/driver.go
src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go
src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go
src/cmd/vendor/github.com/google/pprof/internal/report/report.go
src/cmd/vendor/github.com/google/pprof/internal/report/source.go
src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go
src/cmd/vendor/github.com/google/pprof/internal/report/synth.go [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/profile/encode.go
src/cmd/vendor/github.com/google/pprof/profile/merge.go
src/cmd/vendor/modules.txt

index a15cbe070ad5f0fbc606239547503aacf3eff544..7a96bc64095afeedcb617f53911352752c25cb04 100644 (file)
@@ -3,7 +3,7 @@ module cmd
 go 1.17
 
 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
        golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
        golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
index 8a7ad4290abd81cdd2412a681e75f2e018603c8c..1c6e2248208cb9272b49916b6d543ef738587fb2 100644 (file)
@@ -1,8 +1,8 @@
 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/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-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a h1:jmAp/2PZAScNd62lTD3Mcb0Ey9FvIIJtLohPhtxZJ+Q=
+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/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e h1:pv3V0NlNSh5Q6AX/StwGLBjcLS7UN4m4Gq+V+uSecqM=
index 11f91cbedb9940b59f6450226c6bfe77c7be33af..1d10a7b41f3a6b0657b54c5096a10793c17f0176 100644 (file)
@@ -232,9 +232,9 @@ func (f *file) Name() string {
        return f.name
 }
 
-func (f *file) Base() uint64 {
+func (f *file) ObjAddr(addr uint64) (uint64, error) {
        // No support for shared libraries.
-       return 0
+       return 0, nil
 }
 
 func (f *file) BuildID() string {
index e65bc2f417d813da273db45361ca668edeaa10cf..fc05f919baf7ae940be7b8c36a094b8810ee8be1 100644 (file)
@@ -159,8 +159,8 @@ type ObjFile interface {
        // Name returns the underlying file name, if available.
        Name() string
 
-       // Base returns the base address to use when looking up symbols in the file.
-       Base() uint64
+       // ObjAddr returns the objdump address corresponding to a runtime address.
+       ObjAddr(addr uint64) (uint64, error)
 
        // BuildID returns the GNU build ID of the file, or an empty string.
        BuildID() string
index 576a6ee66a18477140e284e2a539cff4497c1e2a..5ed8a1f9f1e3823c884a45c3bdb78055f86184dd 100644 (file)
@@ -42,7 +42,12 @@ type Binutils struct {
        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
 // 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) {
-       ef, err := elf.Open(name)
+       ef, err := elfOpen(name)
        if err != nil {
                return nil, fmt.Errorf("error parsing %s: %v", name, err)
        }
        defer ef.Close()
 
-       var stextOffset *uint64
-       var pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
+       buildID := ""
+       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) {
                // 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
@@ -450,38 +464,29 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi
                }
        }
 
-       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 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 {
+       // Check that we can compute a base for the binary. This may not be the
+       // correct base value, so we don't save it. We delay computing the actual base
+       // value until we have a sample address for this mapping, so that we can
+       // correctly identify the associated program segment that is needed to compute
+       // the base.
+       if _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset); err != nil {
                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) {
-               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) {
@@ -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
 }
 
+// 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.
 type file struct {
        b       *binrep
        name    string
-       base    uint64
        buildID string
-       isData  bool
+
+       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
+       // 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 {
        return f.name
 }
 
-func (f *file) Base() uint64 {
-       return f.base
+func (f *file) ObjAddr(addr uint64) (uint64, error) {
+       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 {
@@ -533,7 +636,11 @@ func (f *file) BuildID() string {
 }
 
 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 {
@@ -560,6 +667,10 @@ type fileNM struct {
 }
 
 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 {
                addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
                if err != nil {
@@ -579,9 +690,14 @@ type fileAddr2Line struct {
        file
        addr2liner     *addr2Liner
        llvmSymbolizer *llvmSymbolizer
+       isData         bool
 }
 
 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)
        if f.llvmSymbolizer != nil {
                return f.llvmSymbolizer.addrInfo(addr)
index 3b3c6ee89ffd00b55d6c49063616756c9204716d..2638b2db2d9a95a5a74eb104a389127e59f1c97e 100644 (file)
@@ -284,83 +284,71 @@ func FindTextProgHeader(f *elf.File) *elf.ProgHeader {
        return nil
 }
 
-// FindProgHeaderForMapping returns the loadable program segment header that is
-// fully contained in the runtime mapping with file offset pgoff and memory size
-// memsz, or an error if the segment cannot be determined. The function returns
-// a nil program header and no error if the ELF binary has no loadable segments.
-func FindProgHeaderForMapping(f *elf.File, pgoff, memsz uint64) (*elf.ProgHeader, error) {
+// ProgramHeadersForMapping returns the loadable program segment headers that
+// are fully contained in the runtime mapping with file offset pgoff and memory
+// size memsz, and if the binary includes any loadable segments.
+func ProgramHeadersForMapping(f *elf.File, pgoff, memsz uint64) ([]*elf.ProgHeader, bool) {
+       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
-       loadables := 0
+       hasLoadables := false
        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 {
-                       headers = append(headers, &p.ProgHeader)
+                       alignedOffset := uint64(0)
+                       if p.Off > (p.Vaddr & pageOffsetMask) {
+                               alignedOffset = p.Off - (p.Vaddr & pageOffsetMask)
+                       }
+                       if alignedOffset <= pgoff {
+                               headers = append(headers, &p.ProgHeader)
+                       }
                }
                if p.Type == elf.PT_LOAD {
-                       loadables++
+                       hasLoadables = true
                }
        }
-       if len(headers) == 1 {
-               return headers[0], nil
+       if len(headers) < 2 {
+               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
-       // than a page, which causes the next segment to start at a file offset that
-       // is logically on the same page if we were to align file offsets by page.
-       // 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
 
+       // If we have more than one matching segments, try a strict check on the
+       // segment memory size. We use a heuristic to compute the minimum mapping size
+       // required for a segment, assuming mappings are page aligned.
        // 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 {
-               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
-       pageMask := ^uint64(pageSize - 1)
        for _, h := range headers {
                wantSize := (h.Vaddr+h.Memsz+pageSize-1)&pageMask - (h.Vaddr & pageMask)
                if wantSize != memsz {
                        continue
                }
                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
        }
        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
 }
index 3a8d0af7305fe7879fa9f8c2722ed35777780531..a57a0b20a96256f6d887105f76dc7e55a7c51f0e 100644 (file)
@@ -131,8 +131,9 @@ type ObjFile interface {
        // Name returns the underlyinf file name, if available
        Name() string
 
-       // Base returns the base address to use when looking up symbols in the file.
-       Base() uint64
+       // ObjAddr returns the objdump (linker) address corresponding to a runtime
+       // address, and an error.
+       ObjAddr(addr uint64) (uint64, error)
 
        // BuildID returns the GNU build ID of the file, or an empty string.
        BuildID() string
index bc5685d61e1b16eec1c4516198b518126092634a..4a86554880132d11fa44384b1c95bfc88326d343 100644 (file)
@@ -445,7 +445,7 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e
                        return err
                }
 
-               ns := annotateAssembly(insts, sns, s.base)
+               ns := annotateAssembly(insts, sns, s.file)
 
                fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
                for _, name := range s.sym.Name[1:] {
@@ -534,7 +534,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex
                        addr = *address
                }
                msyms, err := f.Symbols(rx, addr)
-               base := f.Base()
                f.Close()
                if err != nil {
                        continue
@@ -543,7 +542,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex
                        objSyms = append(objSyms,
                                &objSymbol{
                                        sym:  ms,
-                                       base: base,
                                        file: f,
                                },
                        )
@@ -558,7 +556,6 @@ func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regex
 // added to correspond to sample addresses
 type objSymbol struct {
        sym  *plugin.Sym
-       base uint64
        file plugin.ObjFile
 }
 
@@ -578,8 +575,7 @@ func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.N
        for _, s := range symbols {
                // Gather samples for this symbol.
                for _, n := range ns {
-                       address := n.Info.Address - s.base
-                       if address >= s.sym.Start && address < s.sym.End {
+                       if address, err := s.file.ObjAddr(n.Info.Address); err == nil && address >= s.sym.Start && address < s.sym.End {
                                symNodes[s] = append(symNodes[s], n)
                        }
                }
@@ -621,7 +617,7 @@ func (a *assemblyInstruction) cumValue() int64 {
 // annotateAssembly annotates a set of assembly instructions with a
 // set of samples. It returns a set of nodes to display. base is an
 // 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.
        insts = append(insts, plugin.Inst{
                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
                // 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]
                        n.flatDiv += sample.FlatDiv
                        n.flat += sample.Flat
index 4f841eff5dc314397e598681300e12a761117d81..54245e5f9ea6a0f2848f6a0b4a063a52bffd03e3 100644 (file)
@@ -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.
 type sourcePrinter struct {
        reader     *sourceReader
+       synth      *synthCode
        objectTool plugin.ObjTool
        objects    map[string]plugin.ObjFile  // Opened object files
        sym        *regexp.Regexp             // May be nil
@@ -146,6 +147,12 @@ type sourcePrinter struct {
        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.
 type instructionInfo struct {
        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 {
        sp := &sourcePrinter{
                reader:      newSourceReader(sourcePath, rpt.options.TrimPath),
+               synth:       newSynthCode(rpt.prof.Mapping),
                objectTool:  obj,
                objects:     map[string]plugin.ObjFile{},
                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{}
        cum := map[uint64]int64{}
 
        // Record an interest in the function corresponding to lines[index].
-       markInterest := func(addr uint64, lines []profile.Line, index int) {
-               fn := lines[index]
+       markInterest := func(addr uint64, loc *profile.Location, index int) {
+               fn := loc.Line[index]
                if fn.Function == nil {
                        return
                }
                sp.interest[fn.Function.Name] = 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.
@@ -270,15 +280,21 @@ func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourc
                                sp.prettyNames[line.Function.SystemName] = line.Function.Name
                        }
 
-                       cum[loc.Address] += value
+                       addr := loc.Address
+                       if addr == 0 {
+                               // Some profiles are missing valid addresses.
+                               addr = sp.synth.address(loc)
+                       }
+
+                       cum[addr] += value
                        if i == 0 {
-                               flat[loc.Address] += value
+                               flat[addr] += value
                        }
 
-                       if sp.sym == nil || (address != nil && loc.Address == *address) {
+                       if sp.sym == nil || (address != nil && addr == *address) {
                                // Interested in top-level entry of stack.
                                if len(loc.Line) > 0 {
-                                       markInterest(loc.Address, loc.Line, len(loc.Line)-1)
+                                       markInterest(addr, loc, len(loc.Line)-1)
                                }
                                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))
                        for j, line := range loc.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.
        // 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.
        const maxRanges = 25
@@ -321,9 +338,18 @@ func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]bool, fla
        }
 
        for _, r := range ranges {
-               base := r.obj.Base()
-               insts, err := sp.objectTool.Disasm(r.mapping.File, r.begin-base, r.end-base,
-                       rpt.options.IntelSyntax)
+               objBegin, err := r.obj.ObjAddr(r.begin)
+               if err != nil {
+                       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 {
                        // TODO(sanjay): Report that the covered addresses are missing.
                        continue
@@ -385,78 +411,115 @@ func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]bool, fla
                                frames = lastFrames
                        }
 
-                       // See if the stack contains a function we are interested in.
-                       for i, f := range frames {
-                               if !sp.interest[f.Func] {
-                                       continue
-                               }
+                       sp.addStack(addr, frames)
+               }
+       }
+}
 
-                               // Record sub-stack under frame's file/line.
-                               fname := canonicalizeFileName(f.File)
-                               file := sp.files[fname]
-                               if file == nil {
-                                       file = &sourceFile{
-                                               fname:    fname,
-                                               lines:    map[int][]sourceInst{},
-                                               funcName: map[int]string{},
-                                       }
-                                       sp.files[fname] = file
-                               }
-                               callees := frames[:i]
-                               stack := make([]callID, 0, len(callees))
-                               for j := len(callees) - 1; j >= 0; j-- { // Reverse so caller is first
-                                       stack = append(stack, callID{
-                                               file: callees[j].File,
-                                               line: callees[j].Line,
-                                       })
-                               }
-                               file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})
+func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {
+       // See if the stack contains a function we are interested in.
+       for i, f := range frames {
+               if !sp.interest[f.Func] {
+                       continue
+               }
 
-                               // Remember the first function name encountered per source line
-                               // and assume that that line belongs to that function.
-                               if _, ok := file.funcName[f.Line]; !ok {
-                                       file.funcName[f.Line] = f.Func
-                               }
+               // Record sub-stack under frame's file/line.
+               fname := canonicalizeFileName(f.File)
+               file := sp.files[fname]
+               if file == nil {
+                       file = &sourceFile{
+                               fname:    fname,
+                               lines:    map[int][]sourceInst{},
+                               funcName: map[int]string{},
                        }
+                       sp.files[fname] = file
+               }
+               callees := frames[:i]
+               stack := make([]callID, 0, len(callees))
+               for j := len(callees) - 1; j >= 0; j-- { // Reverse so caller is first
+                       stack = append(stack, callID{
+                               file: callees[j].File,
+                               line: callees[j].Line,
+                       })
+               }
+               file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})
+
+               // Remember the first function name encountered per source line
+               // and assume that that line belongs to that function.
+               if _, ok := file.funcName[f.Line]; !ok {
+                       file.funcName[f.Line] = f.Func
                }
        }
 }
 
-// splitIntoRanges converts the set of addresses we are interested in into a set of address
-// ranges to disassemble.
-func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, set map[uint64]bool, flat map[uint64]int64) []addressRange {
-       // List of mappings so we can stop expanding address ranges at mapping boundaries.
-       mappings := append([]*profile.Mapping{}, prof.Mapping...)
-       sort.Slice(mappings, func(i, j int) bool { return mappings[i].Start < mappings[j].Start })
+// 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
+       }
 
-       var result []addressRange
-       addrs := make([]uint64, 0, len(set))
-       for addr := range set {
-               addrs = append(addrs, addr)
+       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
+// ranges to disassemble. It also returns the set of addresses found that did not have an
+// associated object file and were therefore not added to an address range.
+func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {
+       // Partition addresses into two sets: ones with a known object file, and ones without.
+       var addrs, unprocessed []uint64
+       for addr, info := range addrMap {
+               if info.obj != nil {
+                       addrs = append(addrs, addr)
+               } else {
+                       unprocessed = append(unprocessed, addr)
+               }
        }
        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.
+       var result []addressRange
        for i, n := 0, len(addrs); i < n; {
                begin, end := addrs[i], addrs[i]
                sum := flat[begin]
                i++
 
-               // Advance to mapping containing addrs[i]
-               for mappingIndex < len(mappings) && mappings[mappingIndex].Limit <= begin {
-                       mappingIndex++
-               }
-               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
-               }
+               info := addrMap[begin]
+               m := info.loc.Mapping
+               obj := info.obj // Non-nil because of the partitioning done above.
 
                // Find following addresses that are close enough to addrs[i].
                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})
        }
-       return result
+       return result, unprocessed
 }
 
 func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
@@ -665,9 +728,12 @@ func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {
        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.
 func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
+       if m == nil {
+               return nil
+       }
        if object, ok := sp.objects[m.File]; ok {
                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
        }
 
+       nestedInfo := false
+       cl := "deadsrc"
+       for _, an := range assembly {
+               if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
+                       nestedInfo = true
+                       cl = "livesrc"
+               }
+       }
+
        fmt.Fprintf(w,
-               "<span class=line> %6d</span> <span class=deadsrc>  %10s %10s %8s  %s </span>",
-               lineNo,
+               "<span class=line> %6d</span> <span class=%s>  %10s %10s %8s  %s </span>",
+               lineNo, cl,
                valueOrDot(flat, rpt), valueOrDot(cum, rpt),
                "", template.HTMLEscapeString(lineContents))
-       srcIndent := indentation(lineContents)
+       if nestedInfo {
+               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>")
        var curCalls []callID
        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)
                }
                curCalls = an.inlineCalls
+               if an.instruction == synthAsm {
+                       continue
+               }
                text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
                fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
                        "", 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.
                        fileline)
        }
-       fmt.Fprintln(w, "</span>")
+       fmt.Fprint(w, "</span>")
 }
 
 // printFunctionClosing prints the end of a function in a weblist report.
index 26e8bdbba8a6dc44230e406d7a733348c12abfee..17c9f6eb947c512a0a8f253cfa00f4b1eed94512 100644 (file)
@@ -40,14 +40,7 @@ h1 {
 .inlinesrc {
   color: #000066;
 }
-.deadsrc {
-cursor: pointer;
-}
-.deadsrc:hover {
-background-color: #eeeeee;
-}
 .livesrc {
-color: #0000ff;
 cursor: pointer;
 }
 .livesrc:hover {
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/synth.go b/src/cmd/vendor/github.com/google/pprof/internal/report/synth.go
new file mode 100644 (file)
index 0000000..7a35bbc
--- /dev/null
@@ -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
+}
index 1e84c72d43daab811098b83b013ffaef58fe6bc6..ab7f03ae2677b4a5673def3d47e349821629f606 100644 (file)
@@ -308,7 +308,7 @@ func (p *Profile) postDecode() error {
                        if l.strX != 0 {
                                value, err = getString(p.stringTable, &l.strX, err)
                                labels[key] = append(labels[key], value)
-                       } else if l.numX != 0 {
+                       } else if l.numX != 0 || l.unitX != 0 {
                                numValues := numLabels[key]
                                units := numUnits[key]
                                if l.unitX != 0 {
index 5ab6e9b9b082b381a3d0d9a5fe3651fec7ac53de..9978e7330e6e6fe670b78561c2eb45d166b217bb 100644 (file)
@@ -231,7 +231,6 @@ func (pm *profileMerger) mapLocation(src *Location) *Location {
        }
 
        if l, ok := pm.locationsByID[src.ID]; ok {
-               pm.locationsByID[src.ID] = l
                return l
        }
 
index 6b19ec3aea1c458ef561cdced14cbc657789ecc3..5fb1c25aceef69f365967326c139adcce6ac7bc2 100644 (file)
@@ -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
 github.com/google/pprof/driver
 github.com/google/pprof/internal/binutils