Skip to content

Commit

Permalink
Use reverse edges for reachable analysis.
Browse files Browse the repository at this point in the history
  • Loading branch information
randall77 committed Oct 4, 2017
1 parent 5698bde commit 20bf8f3
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 117 deletions.
30 changes: 15 additions & 15 deletions gocore/dwarf.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,8 @@ func (p *Program) typeHeap() {
// Get typings starting at roots.
fr := &frameReader{p: p}
p.ForEachRoot(func(r *Root) bool {
if r.Live != nil {
fr.live = r.Live
if r.Frame != nil {
fr.live = r.Frame.Live
p.typeObject(r.Addr, r.Type, fr, add)
} else {
p.typeObject(r.Addr, r.Type, p.proc, add)
Expand Down Expand Up @@ -671,10 +671,10 @@ func (p *Program) readGlobals() {
continue // Ignore markers like data/edata.
}
p.globals = append(p.globals, &Root{
Name: e.AttrField(dwarf.AttrName).Val.(string),
Addr: a,
Type: p.dwarfMap[dt],
Live: nil,
Name: e.AttrField(dwarf.AttrName).Val.(string),
Addr: a,
Type: p.dwarfMap[dt],
Frame: nil,
})
}
}
Expand Down Expand Up @@ -755,16 +755,16 @@ func (p *Program) readStackVars() {
for _, f := range g.frames {
// Start with all pointer slots as unnamed.
unnamed := map[core.Address]bool{}
for a := range f.live {
for a := range f.Live {
unnamed[a] = true
}
// Emit roots for DWARF entries.
for _, v := range vars[f.f] {
r := &Root{
Name: v.name,
Addr: f.max.Add(v.off),
Type: v.typ,
Live: f.live,
Name: v.name,
Addr: f.max.Add(v.off),
Type: v.typ,
Frame: f,
}
f.roots = append(f.roots, r)
// Remove this variable from the set of unnamed pointers.
Expand All @@ -781,10 +781,10 @@ func (p *Program) readStackVars() {
sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
for _, a := range s {
r := &Root{
Name: "unk",
Addr: a,
Type: p.findType("unsafe.Pointer"),
Live: f.live,
Name: "unk",
Addr: a,
Type: p.findType("unsafe.Pointer"),
Frame: f,
}
f.roots = append(f.roots, r)
}
Expand Down
6 changes: 3 additions & 3 deletions gocore/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (p *Program) readObjects() {
// Goroutine roots
for _, g := range p.goroutines {
for _, f := range g.frames {
for a := range f.live { // TODO: iteration order matter?
for a := range f.Live { // TODO: iteration order matter?
add(p.proc.ReadPtr(a))
}
}
Expand Down Expand Up @@ -297,7 +297,7 @@ func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, Object, int6
// Itabs are never in the heap.
// Types might be, though.
a := r.Addr.Add(off)
if r.Live == nil || r.Live[a] {
if r.Frame == nil || r.Frame.Live[a] {
dst, off2 := p.FindObject(p.proc.ReadPtr(a))
if dst != 0 {
if !fn(off, dst, off2) {
Expand All @@ -310,7 +310,7 @@ func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, Object, int6
fallthrough
case KindPtr, KindString, KindSlice, KindFunc:
a := r.Addr.Add(off)
if r.Live == nil || r.Live[a] {
if r.Frame == nil || r.Frame.Live[a] {
dst, off2 := p.FindObject(p.proc.ReadPtr(a))
if dst != 0 {
if !fn(off, dst, off2) {
Expand Down
5 changes: 4 additions & 1 deletion gocore/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,9 @@ func (p *Program) readG(r region) *Goroutine {
if f.f.name == "runtime.goexit" {
break
}
if len(g.frames) > 0 {
g.frames[len(g.frames)-1].parent = f
}
g.frames = append(g.frames, f)

if f.f.name == "runtime.sigtrampgo" {
Expand Down Expand Up @@ -631,7 +634,7 @@ func (p *Program) readFrame(sp, pc core.Address) *Frame {
}
}
}
frame.live = live
frame.Live = live

return frame
}
13 changes: 10 additions & 3 deletions gocore/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,15 @@ func (g *Goroutine) Frames() []*Frame {
}

type Frame struct {
parent *Frame
f *Func // function whose activation record this frame is
pc core.Address // resumption point
min, max core.Address // extent of stack frame

// Set of locations that contain a live pointer. Note that this set
// may contain locations outside the frame (in particular, the args
// for the frame).
live map[core.Address]bool
Live map[core.Address]bool

roots []*Root

Expand Down Expand Up @@ -170,13 +171,19 @@ func (f *Frame) Roots() []*Root {
return f.roots
}

// Parent returns the parent frame of f, or nil if it is the top of the stack.
func (f *Frame) Parent() *Frame {
return f.parent
}

// A Root is an area of memory that might have pointers into the heap.
type Root struct {
Name string
Addr core.Address
Type *Type
// Live, if non-nil, contains the set of words in the root that are live.
Live map[core.Address]bool
// Frame, if non-nil, points to the frame in which this root lives.
// Roots with non-nil Frame fields should use Frame.Live to filter the live pointers in this Root.
Frame *Frame
}

// A Type is the representation of the type of a Go object.
Expand Down
34 changes: 32 additions & 2 deletions html.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func serveHtml(c *gocore.Program) {
fmt.Fprintf(w, "<table>\n")
fmt.Fprintf(w, "<tr><th align=left>field</th><th align=left colspan=\"2\">type</th><th align=left>value</th></tr>\n")
for _, r := range f.Roots() {
htmlObject(w, c, r.Name, r.Addr, r.Type, r.Live)
htmlObject(w, c, r.Name, r.Addr, r.Type, f.Live)
}
fmt.Fprintf(w, "</table>\n")
}
Expand All @@ -171,7 +171,7 @@ func serveHtml(c *gocore.Program) {
fmt.Fprintf(w, "<table>\n")
fmt.Fprintf(w, "<tr><th align=left>field</th><th align=left colspan=\"2\">type</th><th align=left>value</th></tr>\n")
for _, r := range c.Globals() {
htmlObject(w, c, r.Name, r.Addr, r.Type, r.Live)
htmlObject(w, c, r.Name, r.Addr, r.Type, nil)
}
fmt.Fprintf(w, "</table>\n")
})
Expand Down Expand Up @@ -405,6 +405,20 @@ func field(t *gocore.Type, off int64) string {
}
}

// Returns the name of the field at offset off in x.
func objField(c *gocore.Program, x gocore.Object, off int64) string {
t, r := c.Type(x)
if t == nil {
return fmt.Sprintf("f%d", off)
}
s := ""
if r > 1 {
s = fmt.Sprintf("[%d]", off/t.Size)
off %= t.Size
}
return s + field(t, off)
}

// Returns the name of the region starting at offset off in t.
func region(t *gocore.Type, off int64) string {
if off == 0 {
Expand Down Expand Up @@ -438,3 +452,19 @@ func region(t *gocore.Type, off int64) string {
return ".???"
}
}

func objRegion(c *gocore.Program, x gocore.Object, off int64) string {
t, r := c.Type(x)
if t == nil {
return fmt.Sprintf("f%d", off)
}
if off == 0 {
return ""
}
s := ""
if r > 1 {
s = fmt.Sprintf("[%d]", off/t.Size)
off %= t.Size
}
return s + region(t, off)
}
154 changes: 61 additions & 93 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func main() {
case "objects":
flags = gocore.FlagTypes
case "reachable":
flags = gocore.FlagTypes
flags = gocore.FlagTypes | gocore.FlagReverse
case "html":
flags = gocore.FlagTypes | gocore.FlagReverse
}
Expand Down Expand Up @@ -300,106 +300,74 @@ func main() {
os.Exit(1)
}

// Find the set of objects that can reach the query object.
// Map value is the minimum distance to query object + 1.
m := map[gocore.Object]int64{}
m[obj] = 1

for {
changed := false
c.ForEachObject(func(x gocore.Object) bool {
c.ForEachPtr(x, func(_ int64, y gocore.Object, _ int64) bool {
if m[y] != 0 && (m[x] == 0 || m[x] > m[y]+1) {
m[x] = m[y] + 1
changed = true
}
return true
})
return true
})
if !changed {
break
}
// Breadth-first search backwards until we reach a root.
type hop struct {
i int64 // offset in "from" object (the key in the path map) where the pointer is
x gocore.Object // the "to" object
j int64 // the offset in the "to" object
}

// Find a minimum distance root.
var mind int64
var minr *gocore.Root
var minf *gocore.Frame
var ming *gocore.Goroutine
for _, r := range c.Globals() {
c.ForEachRootPtr(r, func(_ int64, y gocore.Object, _ int64) bool {
if m[y] != 0 && (mind == 0 || m[y] < mind) {
mind = m[y]
minr = r
minf = nil
ming = nil
}
return true
})
}
for _, g := range c.Goroutines() {
for _, f := range g.Frames() {
for _, r := range f.Roots() {
c.ForEachRootPtr(r, func(_ int64, y gocore.Object, _ int64) bool {
if m[y] != 0 && (mind == 0 || m[y] < mind) {
mind = m[y]
minr = r
minf = f
ming = g
}
return true
})
}
depth := map[gocore.Object]int{}
depth[obj] = 0
q := []gocore.Object{obj}
done := false
for !done {
if len(q) == 0 {
panic("can't find a root that can reach the object")
}
}
if mind == 0 {
panic("can't find root holding object live")
}
y := q[0]
q = q[1:]
c.ForEachReversePtr(y, func(x gocore.Object, r *gocore.Root, i, j int64) bool {
if r != nil {
// found it.
// TODO: print stack trace if root is a frame
if r.Frame == nil {
// Print global
fmt.Printf("%s", r.Name)
} else {
// Print stack up to frame in question.
var frames []*gocore.Frame
for f := r.Frame.Parent(); f != nil; f = f.Parent() {
frames = append(frames, f)
}
for k := len(frames) - 1; k >= 0; k-- {
fmt.Printf("%s\n", frames[k].Func().Name())
}
// Print frame + variable in frame.
fmt.Printf("%s.%s", r.Frame.Func().Name(), r.Name)
}
fmt.Printf("%s → \n", field(r.Type, i))

// Print minimum distance path to object.
if minf != nil {
fs := ming.Frames()
for i := len(fs) - 1; i >= 0; i-- {
f := fs[i]
if f != minf {
fmt.Printf("%s\n", f.Func().Name())
} else {
fmt.Printf("%s ", f.Func().Name())
break
z := y
for {
fmt.Printf("%x %s", c.Addr(z), typeName(c, z))
if z == obj {
fmt.Println()
break
}
// Find an edge out of z which goes to an object
// closer to obj.
c.ForEachPtr(z, func(i int64, w gocore.Object, j int64) bool {
if d, ok := depth[w]; ok && d < depth[z] {
fmt.Printf(" %s → %s", objField(c, z, i), objRegion(c, w, j))
z = w
return false
}
return true
})
fmt.Println()
}
done = true
return false
}
}
}
var x gocore.Object
c.ForEachRootPtr(minr, func(i int64, y gocore.Object, j int64) bool {
if m[y] != mind {
return true
}
fmt.Printf("%s %s %s ->", minr.Name, minr.Type, typeFieldName(minr.Type, i))
if j != 0 {
fmt.Printf(" +%d", j)
}
fmt.Println()
x = y
return false
})
for d := mind - 1; d != 0; d-- {
fmt.Printf("%x %s", c.Addr(x), typeName(c, x))
c.ForEachPtr(x, func(i int64, y gocore.Object, j int64) bool {
if m[y] != d {
if _, ok := depth[x]; ok {
// we already found a shorter path to this object.
return true
}
fmt.Printf(" %s ->", fieldName(c, x, i))
if j != 0 {
fmt.Printf(" +%d", j)
}
fmt.Println()
x = y
return false
depth[x] = depth[y] + 1
q = append(q, x)
return true
})
}
fmt.Printf("%x %s\n", c.Addr(x), typeName(c, x))

case "html":
serveHtml(c)
}
Expand Down

0 comments on commit 20bf8f3

Please sign in to comment.