gocore: support typing composite roots
This change adds support for composite roots to typeHeap, which should
make it possible to type more of the heap.
Change-Id: I7757f23bf18375160c4f2607efd290aeeb89ec93
Reviewed-on: https://go-review.googlesource.com/c/debug/+/659797
Reviewed-by: Nicolas Hillegeer <aktau@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go
index 6899f78..9a5981b 100644
--- a/internal/gocore/gocore_test.go
+++ b/internal/gocore/gocore_test.go
@@ -301,9 +301,7 @@
}
// Check object counts.
- //
- // TODO(mknyszek): Support typing roots in pieces.
- if want := 32 << 10; bigSliceElemObjects != want {
+ if want := 3 * (32 << 10); bigSliceElemObjects != want {
t.Errorf("expected exactly %d main.bigSliceElem objects, found %d", want, bigSliceElemObjects)
}
})
diff --git a/internal/gocore/reverse.go b/internal/gocore/reverse.go
index ee9553a..bb611b2 100644
--- a/internal/gocore/reverse.go
+++ b/internal/gocore/reverse.go
@@ -86,7 +86,7 @@
idx, _ := p.findObjectIndex(p.Addr(y))
for _, a := range p.redge[p.ridx[idx]:p.ridx[idx+1]] {
if a.root != nil {
- ptr := p.readRootPtr(a.root, a.rOff)
+ ptr, _ := p.readRootPtr(a.root, a.rOff)
if !fn(0, a.root, a.rOff, ptr.Sub(p.Addr(y))) {
return
}
diff --git a/internal/gocore/root.go b/internal/gocore/root.go
index 2a56957..42eb067 100644
--- a/internal/gocore/root.go
+++ b/internal/gocore/root.go
@@ -68,14 +68,14 @@
return r
}
-func (pr *Process) readRootPtr(r *Root, offset int64) core.Address {
+func (pr *Process) readRootPtr(r *Root, offset int64) (value, from core.Address) {
// TODO(mknyszek): Little-endian only.
ptrBuf := make([]byte, pr.proc.PtrSize())
- pr.readRootAt(r, ptrBuf, offset)
+ from = pr.readRootAt(r, ptrBuf, offset)
if pr.proc.PtrSize() == 4 {
- return core.Address(binary.LittleEndian.Uint32(ptrBuf))
+ return core.Address(binary.LittleEndian.Uint32(ptrBuf)), from
}
- return core.Address(binary.LittleEndian.Uint64(ptrBuf))
+ return core.Address(binary.LittleEndian.Uint64(ptrBuf)), from
}
// ReadRootAt reads data out of this root. offset+len(b) must be less than r.Type.Size.
diff --git a/internal/gocore/type.go b/internal/gocore/type.go
index 1d052a2..7bab5c5 100644
--- a/internal/gocore/type.go
+++ b/internal/gocore/type.go
@@ -5,6 +5,7 @@
package gocore
import (
+ "encoding/binary"
"fmt"
"reflect"
"strings"
@@ -525,18 +526,13 @@
}
// Get typings starting at roots.
- fr := &frameReader{p: p}
p.ForEachRoot(func(r *Root) bool {
- if len(r.pieces) == 1 && r.pieces[0].kind == addrPiece {
- addr := core.Address(r.pieces[0].value)
- if r.Frame != nil {
- fr.live = r.Frame.Live
- p.typeObject(addr, r.Type, fr, add)
- } else {
- p.typeObject(addr, r.Type, p.proc, add)
- }
+ rr := &rootReaderAt{p: p, r: r}
+ if r.Frame != nil {
+ rr.frameLive = r.Frame.Live
+ p.typeObject(rr, r.Type, add)
} else {
- // TODO(mknyszek): Support roots broken into pieces.
+ p.typeObject(rr, r.Type, add)
}
return true
})
@@ -551,7 +547,7 @@
continue
}
for i := int64(0); i < c.r; i++ {
- p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add)
+ p.typeObject(&addrReaderAt{p, c.a.Add(i * c.t.Size)}, c.t, add)
}
}
@@ -581,26 +577,82 @@
}
}
-type reader interface {
- ReadPtr(core.Address) core.Address
- ReadInt(core.Address) int64
+// valueReaderAt is an interface that abstracts over reading logically contiguous
+// values. This is to allow contiguously reading values that are not actually stored
+// contiguously.
+type valueReaderAt interface {
+ // ReadPtrAt loads a core.Address at offset bytes from the start of the value.
+ //
+ // Returns the code.Address read and the address it was read from, if there exists
+ // such a meaningful address (that is, from might be zero if, for example, the
+ // core.Address was read out of a register).
+ ReadPtrAt(offset int64) (value, from core.Address)
+
+ // ReadIntAt loads a Go 'int' at offset bytes from the start of the value.
+ //
+ // Returns the 'int' read and the address it was read from, if there exists
+ // such a meaningful address (that is, from might be zero if, for example, the
+ // 'int' was read out of a register).
+ ReadIntAt(offset int64) (value int64, from core.Address)
+
+ // Advance returns a new valueReaderAt that starts at an offset into the value
+ // represented by this valueReaderAt. This is useful for reading, for example,
+ // struct values. Each field value in the struct can be referenced by advancing
+ // to the field offset.
+ Advance(offset int64) valueReaderAt
}
-// A frameReader reads data out of a stack frame.
-// Any pointer slots marked as dead will read as nil instead of their real value.
-type frameReader struct {
- p *Process
- live map[core.Address]bool
+// addrReaderAt is a trivial valueReaderAt that reads a value stored contiguously
+// in memory at some address.
+type addrReaderAt struct {
+ p *Process
+ a core.Address
}
-func (fr *frameReader) ReadPtr(a core.Address) core.Address {
- if !fr.live[a] {
- return 0
+func (ar *addrReaderAt) ReadPtrAt(offset int64) (value, from core.Address) {
+ return ar.p.proc.ReadPtr(ar.a.Add(offset)), ar.a.Add(offset)
+}
+
+func (ar *addrReaderAt) ReadIntAt(offset int64) (value int64, from core.Address) {
+ return ar.p.proc.ReadInt(ar.a.Add(offset)), ar.a.Add(offset)
+}
+
+func (ar *addrReaderAt) Advance(offset int64) valueReaderAt {
+ nar := *ar
+ nar.a = nar.a.Add(offset)
+ return &nar
+}
+
+// rootReaderAt is a valueReaderAt that reads a value stored logically contiguously
+// in a Root.
+type rootReaderAt struct {
+ p *Process
+ r *Root
+ offset int64
+ frameLive map[core.Address]bool
+}
+
+func (rr *rootReaderAt) ReadPtrAt(offset int64) (value, from core.Address) {
+ ptr, from := rr.p.readRootPtr(rr.r, offset+rr.offset)
+ if from != 0 && rr.frameLive != nil && !rr.frameLive[from] {
+ return 0, from
}
- return fr.p.proc.ReadPtr(a)
+ return ptr, from
}
-func (fr *frameReader) ReadInt(a core.Address) int64 {
- return fr.p.proc.ReadInt(a)
+
+func (rr *rootReaderAt) ReadIntAt(offset int64) (value int64, from core.Address) {
+ var i [8]byte
+ from = rr.p.readRootAt(rr.r, i[:], offset+rr.offset)
+ if rr.p.proc.PtrSize() == 4 {
+ return int64(binary.LittleEndian.Uint32(i[:])), from
+ }
+ return int64(binary.LittleEndian.Uint64(i[:])), from
+}
+
+func (rr *rootReaderAt) Advance(offset int64) valueReaderAt {
+ nrr := *rr
+ nrr.offset += offset
+ return &nrr
}
func methodFromMethodValueWrapper(name string) (string, bool) {
@@ -616,7 +668,7 @@
// typeObject takes an address and a type for the data at that address.
// For each pointer it finds in the memory at that address, it calls add with the pointer
// and the type + repeat count of the thing that it points to.
-func (p *Process) typeObject(a core.Address, t *Type, r reader, add func(core.Address, *Type, int64)) {
+func (p *Process) typeObject(r valueReaderAt, t *Type, add func(core.Address, *Type, int64)) {
ptrSize := p.proc.PtrSize()
switch t.Kind {
@@ -631,13 +683,12 @@
// a dead eface variable on the stack: the data field
// will return nil because it's dead, but the type pointer
// will likely be bogus.
- data := a.Add(ptrSize)
- if r.ReadPtr(data) == 0 { // nil interface
+ if value, _ := r.ReadPtrAt(ptrSize); value == 0 { // nil interface
return
}
// Use the type word to determine the type
// of the pointed-to object.
- typPtr := r.ReadPtr(a)
+ typPtr, data := r.ReadPtrAt(0)
if typPtr == 0 { // nil interface
return
}
@@ -651,7 +702,8 @@
// Indirect interface: the interface introduced a new
// level of indirection, not reflected in the type.
// Read through it.
- add(r.ReadPtr(data), typ, 1)
+ value, _ := r.ReadPtrAt(ptrSize)
+ add(value, typ, 1)
return
}
@@ -672,27 +724,24 @@
}
}
}
- if directTyp.Kind == KindPtr {
- p.typeObject(data, directTyp, r, add)
- break
- }
- if directTyp.Kind == KindFunc {
- add(data, directTyp, 1)
+ if directTyp.Kind == KindPtr || directTyp.Kind == KindFunc {
+ p.typeObject(r.Advance(ptrSize), directTyp, add)
break
}
panic(fmt.Sprintf("type of direct interface, originally %s (kind %s), isn't a pointer: %s (kind %s)", typ, typ.Kind, directTyp, directTyp.Kind))
}
case KindString:
- ptr := r.ReadPtr(a)
- len := r.ReadInt(a.Add(ptrSize))
+ ptr, _ := r.ReadPtrAt(0)
+ len, _ := r.ReadIntAt(ptrSize)
add(ptr, t.Elem, len)
case KindSlice:
- ptr := r.ReadPtr(a)
- cap := r.ReadInt(a.Add(2 * ptrSize))
+ ptr, _ := r.ReadPtrAt(0)
+ cap, _ := r.ReadIntAt(2 * ptrSize)
add(ptr, t.Elem, cap)
case KindPtr:
if t.Elem != nil { // unsafe.Pointer has a nil Elem field.
- add(r.ReadPtr(a), t.Elem, 1)
+ ptr, _ := r.ReadPtrAt(0)
+ add(ptr, t.Elem, 1)
}
case KindFunc:
// The referent is a closure. We don't know much about the
@@ -700,7 +749,7 @@
// The runtime._type we want exists in the binary (for all
// heap-allocated closures, anyway) but it would be hard to find
// just given the pc.
- closure := r.ReadPtr(a)
+ closure, _ := r.ReadPtrAt(0)
if closure == 0 {
break
}
@@ -716,7 +765,7 @@
// TODO: better value for size?
f.closure = ft
}
- p.typeObject(closure, ft, r, add)
+ p.typeObject(&addrReaderAt{p, closure}, ft, add)
// Handle the special case for method value.
// It's a single-entry closure laid out like {pc uintptr, x T}.
@@ -726,7 +775,7 @@
for _, v := range p.dwarfVars[mf] {
if v.kind == dwarfParam {
ptr := closure.Add(p.proc.PtrSize())
- p.typeObject(ptr, v.typ, r, add)
+ p.typeObject(&addrReaderAt{p, ptr}, v.typ, add)
break
}
}
@@ -735,7 +784,7 @@
case KindArray:
n := t.Elem.Size
for i := int64(0); i < t.Count; i++ {
- p.typeObject(a.Add(i*n), t.Elem, r, add)
+ p.typeObject(r.Advance(i*n), t.Elem, add)
}
case KindStruct:
if strings.HasPrefix(t.Name, "hash<") {
@@ -746,11 +795,12 @@
var n int64
for _, f := range t.Fields {
if f.Name == "buckets" {
- bPtr = p.proc.ReadPtr(a.Add(f.Off))
+ bPtr, _ = r.ReadPtrAt(f.Off)
bTyp = f.Type.Elem
}
if f.Name == "B" {
- n = int64(1) << p.proc.ReadUint8(a.Add(f.Off))
+ shift, _ := r.ReadIntAt(f.Off)
+ n = int64(1) << uint8(shift)
}
}
add(bPtr, bTyp, n)
@@ -759,13 +809,13 @@
for _, f := range t.Fields {
// hchan.buf(in chan) is an unsafe.pointer to an [dataqsiz]elemtype.
if strings.HasPrefix(t.Name, "hchan<") && f.Name == "buf" && f.Type.Kind == KindPtr {
- elemType := p.proc.ReadPtr(a.Add(t.field("elemtype").Off))
- bufPtr := r.ReadPtr(a.Add(t.field("buf").Off))
+ elemType, _ := r.ReadPtrAt(t.field("elemtype").Off)
+ bufPtr, _ := r.ReadPtrAt(t.field("buf").Off)
rTyp := p.runtimeType2Type(elemType, 0)
- dataqsiz := p.proc.ReadUintptr(a.Add(t.field("dataqsiz").Off))
+ dataqsiz, _ := r.ReadPtrAt(t.field("dataqsiz").Off)
add(bufPtr, rTyp, int64(dataqsiz))
}
- p.typeObject(a.Add(f.Off), f.Type, r, add)
+ p.typeObject(r.Advance(f.Off), f.Type, add)
}
default:
panic(fmt.Sprintf("unknown type kind %s\n", t.Kind))