aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go')
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go188
1 files changed, 152 insertions, 36 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
index 576a6ee66a..5ed8a1f9f1 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go
@@ -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)