From a25d2a2b24d15561e04cb39d221fe37893ea9b4e Mon Sep 17 00:00:00 2001 From: David Chase Date: Thu, 27 Jun 2019 22:39:15 -0400 Subject: [PATCH] proc: added *BinaryInfo.AllPCsForFileLine for faster bulk queries (#1592) Support for bulk queries makes the DWARF quality checker (github.com/dr2chase/dwarf-goodness/cmd/dwarf-goodness) run much more efficiently (replace quadratic cost with linear). --- pkg/dwarf/line/state_machine.go | 30 ++++++++++++++++++++++++++++++ pkg/proc/bininfo.go | 14 ++++++++++++++ pkg/proc/proc_test.go | 23 ++++++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index da92487e..3612f25f 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -136,6 +136,36 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) return } +// AllPCsForFileLines Adds all PCs for a given file and set (domain of map) of lines +// to the map value corresponding to each line. +func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64) { + if lineInfo == nil { + return + } + + var ( + lastAddr uint64 + sm = newStateMachine(lineInfo, lineInfo.Instructions) + ) + + for { + if err := sm.next(); err != nil { + if lineInfo.Logf != nil { + lineInfo.Logf("AllPCsForFileLine error: %v", err) + } + break + } + if sm.address != lastAddr && sm.isStmt && sm.valid && sm.file == f { + if pcs, ok := m[sm.line]; ok { + pcs = append(pcs, sm.address) + m[sm.line] = pcs + lastAddr = sm.address + } + } + } + return +} + var NoSourceError = errors.New("no source available") // AllPCsBetween returns all PC addresses between begin and end (including both begin and end) that have the is_stmt flag set and do not belong to excludeFile:excludeLine diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 9bc0752b..07b1ddf9 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -406,6 +406,20 @@ func (bi *BinaryInfo) AllPCsForFileLine(filename string, lineno int) []uint64 { return r } +// AllPCsForFileLines returns a map providing all PC addresses for filename and each line in linenos +func (bi *BinaryInfo) AllPCsForFileLines(filename string, linenos []int) map[int][]uint64 { + r := make(map[int][]uint64) + for _, line := range linenos { + r[line] = make([]uint64, 0, 1) + } + for _, cu := range bi.compileUnits { + if cu.lineInfo.Lookup[filename] != nil { + cu.lineInfo.AllPCsForFileLines(filename, r) + } + } + return r +} + // PCToFunc returns the function containing the given PC address func (bi *BinaryInfo) PCToFunc(pc uint64) *Function { i := sort.Search(len(bi.Functions), func(i int) bool { diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index e2b2392b..8934dfbd 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -3629,6 +3629,27 @@ func checkFrame(frame proc.Stackframe, fnname, file string, line int, inlined bo return nil } +func TestAllPCsForFileLines(t *testing.T) { + if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) { + // Versions of go before 1.10 do not have DWARF information for inlined calls + t.Skip("inlining not supported") + } + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p proc.Process, fixture protest.Fixture) { + l2pcs := p.BinInfo().AllPCsForFileLines(fixture.Source, []int{7, 20}) + if len(l2pcs) != 2 { + t.Fatalf("expected two map entries for %s:{%d,%d} (got %d: %v)", fixture.Source, 7, 20, len(l2pcs), l2pcs) + } + pcs := l2pcs[20] + if len(pcs) < 1 { + t.Fatalf("expected at least one location for %s:%d (got %d: %#x)", fixture.Source, 20, len(pcs), pcs) + } + pcs = l2pcs[7] + if len(pcs) < 2 { + t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 7, len(pcs), pcs) + } + }) +} + func TestInlinedStacktraceAndVariables(t *testing.T) { if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) { // Versions of go before 1.10 do not have DWARF information for inlined calls @@ -3680,7 +3701,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p proc.Process, fixture protest.Fixture) { pcs := p.BinInfo().AllPCsForFileLine(fixture.Source, 7) if len(pcs) < 2 { - t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 6, len(pcs), pcs) + t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 7, len(pcs), pcs) } for _, pc := range pcs { t.Logf("setting breakpoint at %#x\n", pc)