Skip to content

Commit

Permalink
proc,dwarf: cache debug.Entry objects (go-delve#1931)
Browse files Browse the repository at this point in the history
Instead of rescanning debug_info every time we want to read a function
(either to find inlined calls or its variables) cache the tree of
dwarf.Entry that we would generate and use that.

Benchmark before:

BenchmarkConditionalBreakpoints-4   	       1	5164689165 ns/op

Benchmark after:

BenchmarkConditionalBreakpoints-4   	       1	4817425836 ns/op

Updates go-delve#1549
  • Loading branch information
aarzilli authored and abner-chenc committed Nov 23, 2020
1 parent 1783ac4 commit d626cc6
Show file tree
Hide file tree
Showing 15 changed files with 1,080 additions and 354 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require (
github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5
github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/google/go-dap v0.2.0
github.com/cpuguy83/go-md2man v1.0.8 // indirect
github.com/hashicorp/golang-lru v0.5.4
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561
github.com/mattn/go-isatty v0.0.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-dap v0.2.0 h1:whjIGQRumwbR40qRU7CEKuFLmePUUc2s4Nt9DoXXxWk=
github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
Expand Down
255 changes: 255 additions & 0 deletions pkg/dwarf/godwarf/tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package godwarf

import (
"debug/dwarf"
"fmt"
"sort"
)

// Entry represents a debug_info entry.
// When calling Val, if the entry does not have the specified attribute, the
// entry specified by DW_AT_abstract_origin will be searched recursively.
type Entry interface {
Val(dwarf.Attr) interface{}
}

type compositeEntry []*dwarf.Entry

func (ce compositeEntry) Val(attr dwarf.Attr) interface{} {
for _, e := range ce {
if r := e.Val(attr); r != nil {
return r
}
}
return nil
}

// LoadAbstractOrigin loads the entry corresponding to the
// DW_AT_abstract_origin of entry and returns a combination of entry and its
// abstract origin.
func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.Offset) {
ao, ok := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
if !ok {
return entry, entry.Offset
}

r := []*dwarf.Entry{entry}

for {
aordr.Seek(ao)
e, _ := aordr.Next()
if e == nil {
break
}
r = append(r, e)

ao, ok = e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
if !ok {
break
}
}

return compositeEntry(r), entry.Offset
}

// Tree represents a tree of dwarf objects.
type Tree struct {
Entry
typ Type
Tag dwarf.Tag
Offset dwarf.Offset
Ranges [][2]uint64
Children []*Tree
}

// LoadTree returns the tree of DIE rooted at offset 'off'.
// Abstract origins are automatically loaded, if present.
// DIE ranges are bubbled up automatically, if the child of a DIE covers a
// range of addresses that is not covered by its parent LoadTree will fix
// the parent entry.
func LoadTree(off dwarf.Offset, dw *dwarf.Data, staticBase uint64) (*Tree, error) {
rdr := dw.Reader()
rdr.Seek(off)

e, err := rdr.Next()
if err != nil {
return nil, err
}
r := EntryToTree(e)
r.Children, err = loadTreeChildren(e, rdr)
if err != nil {
return nil, err
}

err = r.resolveRanges(dw, staticBase)
if err != nil {
return nil, err
}
r.resolveAbstractEntries(rdr)

return r, nil
}

// EntryToTree converts a single entry, without children to a *Tree object
func EntryToTree(entry *dwarf.Entry) *Tree {
return &Tree{Entry: entry, Offset: entry.Offset, Tag: entry.Tag}
}

func loadTreeChildren(e *dwarf.Entry, rdr *dwarf.Reader) ([]*Tree, error) {
if !e.Children {
return nil, nil
}
children := []*Tree{}
for {
e, err := rdr.Next()
if err != nil {
return nil, err
}
if e.Tag == 0 {
break
}
child := EntryToTree(e)
child.Children, err = loadTreeChildren(e, rdr)
if err != nil {
return nil, err
}
children = append(children, child)
}
return children, nil
}

func (n *Tree) resolveRanges(dw *dwarf.Data, staticBase uint64) error {
var err error
n.Ranges, err = dw.Ranges(n.Entry.(*dwarf.Entry))
if err != nil {
return err
}
for i := range n.Ranges {
n.Ranges[i][0] += staticBase
n.Ranges[i][1] += staticBase
}
n.Ranges = normalizeRanges(n.Ranges)

for _, child := range n.Children {
err := child.resolveRanges(dw, staticBase)
if err != nil {
return err
}
n.Ranges = fuseRanges(n.Ranges, child.Ranges)
}
return nil
}

// normalizeRanges sorts rngs by starting point and fuses overlapping entries.
func normalizeRanges(rngs [][2]uint64) [][2]uint64 {
const (
start = 0
end = 1
)

if len(rngs) == 0 {
return rngs
}

sort.Slice(rngs, func(i, j int) bool {
return rngs[i][start] <= rngs[j][start]
})

// eliminate invalid entries
out := rngs[:0]
for i := range rngs {
if rngs[i][start] < rngs[i][end] {
out = append(out, rngs[i])
}
}
rngs = out

// fuse overlapping entries
out = rngs[:1]
for i := 1; i < len(rngs); i++ {
cur := rngs[i]
if cur[start] <= out[len(out)-1][end] {
out[len(out)-1][end] = max(cur[end], out[len(out)-1][end])
} else {
out = append(out, cur)
}
}
return out
}

func max(a, b uint64) uint64 {
if a > b {
return a
}
return b
}

// fuseRanges fuses rngs2 into rngs1, it's the equivalent of
// normalizeRanges(append(rngs1, rngs2))
// but more efficent.
func fuseRanges(rngs1, rngs2 [][2]uint64) [][2]uint64 {
if rangesContains(rngs1, rngs2) {
return rngs1
}

return normalizeRanges(append(rngs1, rngs2...))
}

// rangesContains checks that rngs1 is a superset of rngs2.
func rangesContains(rngs1, rngs2 [][2]uint64) bool {
i, j := 0, 0
for {
if i >= len(rngs1) {
return false
}
if j >= len(rngs2) {
return true
}
if rangeContains(rngs1[i], rngs2[j]) {
j++
} else {
i++
}
}
}

// rangeContains checks that a contains b.
func rangeContains(a, b [2]uint64) bool {
return a[0] <= b[0] && a[1] >= b[1]
}

func (n *Tree) resolveAbstractEntries(rdr *dwarf.Reader) {
n.Entry, n.Offset = LoadAbstractOrigin(n.Entry.(*dwarf.Entry), rdr)
for _, child := range n.Children {
child.resolveAbstractEntries(rdr)
}
}

// ContainsPC returns true if the ranges of this DIE contains PC.
func (n *Tree) ContainsPC(pc uint64) bool {
for _, rng := range n.Ranges {
if rng[0] > pc {
return false
}
if rng[0] <= pc && pc < rng[1] {
return true
}
}
return false
}

func (n *Tree) Type(dw *dwarf.Data, index int, typeCache map[dwarf.Offset]Type) (Type, error) {
if n.typ == nil {
offset, ok := n.Val(dwarf.AttrType).(dwarf.Offset)
if !ok {
return nil, fmt.Errorf("malformed variable DIE (offset)")
}

var err error
n.typ, err = ReadType(dw, index, offset, typeCache)
if err != nil {
return nil, err
}
}
return n.typ, nil
}
119 changes: 119 additions & 0 deletions pkg/dwarf/godwarf/tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package godwarf

import (
"testing"
)

func makeRanges(v ...uint64) [][2]uint64 {
r := make([][2]uint64, 0, len(v)/2)
for i := 0; i < len(v); i += 2 {
r = append(r, [2]uint64{v[i], v[i+1]})
}
return r
}

func assertRanges(t *testing.T, out, tgt [][2]uint64) {
if len(out) != len(tgt) {
t.Errorf("\nexpected:\t%v\ngot:\t\t%v", tgt, out)
}
for i := range out {
if out[i] != tgt[i] {
t.Errorf("\nexpected:\t%v\ngot:\t\t%v", tgt, out)
break
}
}
}

func TestNormalizeRanges(t *testing.T) {
mr := makeRanges
//assertRanges(t, normalizeRanges(mr(105, 103, 90, 95, 25, 20, 20, 23)), mr(20, 23, 90, 95))
assertRanges(t, normalizeRanges(mr(10, 12, 12, 15)), mr(10, 15))
assertRanges(t, normalizeRanges(mr(12, 15, 10, 12)), mr(10, 15))
assertRanges(t, normalizeRanges(mr(4910012, 4910013, 4910013, 4910098, 4910124, 4910127)), mr(4910012, 4910098, 4910124, 4910127))
}

func TestRangeContains(t *testing.T) {
mr := func(start, end uint64) [2]uint64 {
return [2]uint64{start, end}
}
tcs := []struct {
a, b [2]uint64
tgt bool
}{
{mr(1, 10), mr(1, 11), false},
{mr(1, 10), mr(1, 1), true},
{mr(1, 10), mr(10, 11), false},
{mr(1, 10), mr(1, 10), true},
{mr(1, 10), mr(2, 5), true},
}

for _, tc := range tcs {
if rangeContains(tc.a, tc.b) != tc.tgt {
if tc.tgt {
t.Errorf("range %v does not contan %v (but should)", tc.a, tc.b)
} else {
t.Errorf("range %v does contain %v (but shouldn't)", tc.a, tc.b)
}
}
}
}

func TestRangesContains(t *testing.T) {
mr := makeRanges
tcs := []struct {
rngs1, rngs2 [][2]uint64
tgt bool
}{
{mr(1, 10), mr(1, 11), false},
{mr(1, 10), mr(1, 1), true},
{mr(1, 10), mr(10, 11), false},
{mr(1, 10), mr(1, 10), true},
{mr(1, 10), mr(2, 5), true},

{mr(1, 10, 20, 30), mr(1, 11), false},
{mr(1, 10, 20, 30), mr(1, 1, 20, 22), true},
{mr(1, 10, 20, 30), mr(30, 31), false},
{mr(1, 10, 20, 30), mr(15, 17), false},
{mr(1, 10, 20, 30), mr(1, 5, 6, 9, 21, 24), true},
{mr(1, 10, 20, 30), mr(0, 1), false},
}

for _, tc := range tcs {
if rangesContains(tc.rngs1, tc.rngs2) != tc.tgt {
if tc.tgt {
t.Errorf("ranges %v does not contan %v (but should)", tc.rngs1, tc.rngs2)
} else {
t.Errorf("ranges %v does contain %v (but shouldn't)", tc.rngs1, tc.rngs2)
}
}
}
}

func TestContainsPC(t *testing.T) {
mr := makeRanges

tcs := []struct {
rngs [][2]uint64
pc uint64
tgt bool
}{
{mr(1, 10), 1, true},
{mr(1, 10), 5, true},
{mr(1, 10), 10, false},
{mr(1, 10, 20, 30), 15, false},
{mr(1, 10, 20, 30), 20, true},
{mr(1, 10, 20, 30), 30, false},
{mr(1, 10, 20, 30), 31, false},
}

for _, tc := range tcs {
n := &Tree{Ranges: tc.rngs}
if n.ContainsPC(tc.pc) != tc.tgt {
if tc.tgt {
t.Errorf("ranges %v does not contain %d (but should)", tc.rngs, tc.pc)
} else {
t.Errorf("ranges %v does contain %d (but shouldn't)", tc.rngs, tc.pc)
}
}
}
}
Loading

0 comments on commit d626cc6

Please sign in to comment.