gocore: add gocore reader
Add a library that can extract a bunch of information from a Go core dump.
Update golang/go#21356
Change-Id: I9997c80452a4ad205d0799f89b2e268b4a5a9e2f
Reviewed-on: https://go-review.googlesource.com/73191
Reviewed-by: Austin Clements <austin@google.com>
diff --git a/gocore/dwarf.go b/gocore/dwarf.go
new file mode 100644
index 0000000..6aa0fdd
--- /dev/null
+++ b/gocore/dwarf.go
@@ -0,0 +1,541 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "debug/dwarf"
+ "fmt"
+ "sort"
+ "strings"
+
+ "golang.org/x/debug/core"
+)
+
+// read DWARF types from core dump.
+func (p *Process) readDWARFTypes() {
+ d, _ := p.proc.DWARF()
+
+ // Make one of our own Types for each dwarf type.
+ r := d.Reader()
+ var types []*Type
+ for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() {
+ switch e.Tag {
+ case dwarf.TagArrayType, dwarf.TagPointerType, dwarf.TagStructType, dwarf.TagBaseType, dwarf.TagSubroutineType, dwarf.TagTypedef:
+ dt, err := d.Type(e.Offset)
+ if err != nil {
+ continue
+ }
+ t := &Type{Name: gocoreName(dt), Size: dwarfSize(dt, p.proc.PtrSize())}
+ p.dwarfMap[dt] = t
+ types = append(types, t)
+ }
+ }
+
+ // Fill in fields of types. Postponed until now so we're sure
+ // we have all the Types allocated and available.
+ for dt, t := range p.dwarfMap {
+ switch x := dt.(type) {
+ case *dwarf.ArrayType:
+ t.Kind = KindArray
+ t.Elem = p.dwarfMap[x.Type]
+ t.Count = x.Count
+ case *dwarf.PtrType:
+ t.Kind = KindPtr
+ // unsafe.Pointer has a void base type.
+ if _, ok := x.Type.(*dwarf.VoidType); !ok {
+ t.Elem = p.dwarfMap[x.Type]
+ }
+ case *dwarf.StructType:
+ t.Kind = KindStruct
+ for _, f := range x.Field {
+ t.Fields = append(t.Fields, Field{Name: f.Name, Type: p.dwarfMap[f.Type], Off: f.ByteOffset})
+ }
+ case *dwarf.BoolType:
+ t.Kind = KindBool
+ case *dwarf.IntType:
+ t.Kind = KindInt
+ case *dwarf.UintType:
+ t.Kind = KindUint
+ case *dwarf.FloatType:
+ t.Kind = KindFloat
+ case *dwarf.ComplexType:
+ t.Kind = KindComplex
+ case *dwarf.FuncType:
+ t.Kind = KindFunc
+ case *dwarf.TypedefType:
+ // handle these types in the loop below
+ default:
+ panic(fmt.Sprintf("unknown type %s %T", dt, dt))
+ }
+ }
+
+ // Detect strings & slices
+ for _, t := range types {
+ if t.Kind != KindStruct {
+ continue
+ }
+ if t.Name == "string" { // TODO: also "struct runtime.stringStructDWARF" ?
+ t.Kind = KindString
+ t.Elem = t.Fields[0].Type.Elem // TODO: check that it is always uint8.
+ t.Fields = nil
+ }
+ if len(t.Name) >= 9 && t.Name[:9] == "struct []" ||
+ len(t.Name) >= 2 && t.Name[:2] == "[]" {
+ t.Kind = KindSlice
+ t.Elem = t.Fields[0].Type.Elem
+ t.Fields = nil
+ }
+ }
+
+ // Copy info from base types into typedefs.
+ r = d.Reader()
+ for dt, t := range p.dwarfMap {
+ tt, ok := dt.(*dwarf.TypedefType)
+ if !ok {
+ continue
+ }
+ base := tt.Type
+ // Walk typedef chain until we reach a non-typedef type.
+ for {
+ if x, ok := base.(*dwarf.TypedefType); ok {
+ base = x.Type
+ continue
+ }
+ break
+ }
+ bt := p.dwarfMap[base]
+
+ // Copy type info from base. Everything except the name.
+ name := t.Name
+ *t = *bt
+ t.Name = name
+
+ // Detect some special types. If the base is some particular type,
+ // then the alias gets marked as special.
+ // We have aliases like:
+ // interface {} -> struct runtime.eface
+ // error -> struct runtime.iface
+ // Note: the base itself does not get marked as special.
+ // (Unlike strings and slices, where they do.)
+ if bt.Name == "runtime.eface" {
+ t.Kind = KindEface
+ t.Fields = nil
+ }
+ if bt.Name == "runtime.iface" {
+ t.Kind = KindIface
+ t.Fields = nil
+ }
+ }
+
+ // Make a runtime name -> Type map for existing DWARF types.
+ p.runtimeNameMap = map[string][]*Type{}
+ for dt, t := range p.dwarfMap {
+ name := runtimeName(dt)
+ p.runtimeNameMap[name] = append(p.runtimeNameMap[name], t)
+ }
+
+ // Construct the runtime.specialfinalizer type. It won't be found
+ // in DWARF before 1.10 because it does not appear in the type of any variable.
+ // type specialfinalizer struct {
+ // special special
+ // fn *funcval
+ // nret uintptr
+ // fint *_type
+ // ot *ptrtype
+ // }
+ if p.runtimeNameMap["runtime.specialfinalizer"] == nil {
+ special := p.findType("runtime.special")
+ p.runtimeNameMap["runtime.specialfinalizer"] = []*Type{
+ &Type{
+ Name: "runtime.specialfinalizer",
+ Size: special.Size + 4*p.proc.PtrSize(),
+ Kind: KindStruct,
+ Fields: []Field{
+ Field{
+ Name: "special",
+ Off: 0,
+ Type: special,
+ },
+ Field{
+ Name: "fn",
+ Off: special.Size,
+ Type: p.findType("*runtime.funcval"),
+ },
+ Field{
+ Name: "nret",
+ Off: special.Size + p.proc.PtrSize(),
+ Type: p.findType("uintptr"),
+ },
+ Field{
+ Name: "fint",
+ Off: special.Size + 2*p.proc.PtrSize(),
+ Type: p.findType("*runtime._type"),
+ },
+ Field{
+ Name: "fn",
+ Off: special.Size + 3*p.proc.PtrSize(),
+ Type: p.findType("*runtime.ptrtype"),
+ },
+ },
+ },
+ }
+ }
+}
+
+// dwarfSize is used to compute the size of a DWARF type.
+// dt.Size() is wrong when it returns a negative number.
+// This function implements just enough to correct the bad behavior.
+func dwarfSize(dt dwarf.Type, ptrSize int64) int64 {
+ s := dt.Size()
+ if s >= 0 {
+ return s
+ }
+ switch x := dt.(type) {
+ case *dwarf.FuncType:
+ return ptrSize // Fix for issue 21097.
+ case *dwarf.ArrayType:
+ return x.Count * dwarfSize(x.Type, ptrSize)
+ case *dwarf.TypedefType:
+ return dwarfSize(x.Type, ptrSize)
+ default:
+ panic(fmt.Sprintf("unhandled: %T", x))
+ }
+}
+
+// gocoreName generates the name this package uses to refer to a dwarf type.
+// This name differs from the dwarf name in that it stays closer to the Go name for the type.
+// For instance (dwarf name -> gocoreName)
+// struct runtime.siginfo -> runtime.siginfo
+// *void -> unsafe.Pointer
+// struct struct { runtime.signalLock uint32; runtime.hz int32 } -> struct { signalLock uint32; hz int32 }
+func gocoreName(dt dwarf.Type) string {
+ switch x := dt.(type) {
+ case *dwarf.PtrType:
+ if _, ok := x.Type.(*dwarf.VoidType); ok {
+ return "unsafe.Pointer"
+ }
+ return "*" + gocoreName(x.Type)
+ case *dwarf.ArrayType:
+ return fmt.Sprintf("[%d]%s", x.Count, gocoreName(x.Type))
+ case *dwarf.StructType:
+ if !strings.HasPrefix(x.StructName, "struct {") {
+ // This is a named type, return that name.
+ return x.StructName
+ }
+ // Build gocore name from the DWARF fields.
+ s := "struct {"
+ first := true
+ for _, f := range x.Field {
+ if !first {
+ s += ";"
+ }
+ name := f.Name
+ if i := strings.Index(name, "."); i >= 0 {
+ // Remove pkg path from field names.
+ name = name[i+1:]
+ }
+ s += fmt.Sprintf(" %s %s", name, gocoreName(f.Type))
+ first = false
+ }
+ s += " }"
+ return s
+ default:
+ return dt.String()
+ }
+}
+
+// Generate the name the runtime uses for a dwarf type. The DWARF generator
+// and the runtime use slightly different names for the same underlying type.
+func runtimeName(dt dwarf.Type) string {
+ switch x := dt.(type) {
+ case *dwarf.PtrType:
+ if _, ok := x.Type.(*dwarf.VoidType); ok {
+ return "unsafe.Pointer"
+ }
+ return "*" + runtimeName(x.Type)
+ case *dwarf.ArrayType:
+ return fmt.Sprintf("[%d]%s", x.Count, runtimeName(x.Type))
+ case *dwarf.StructType:
+ if !strings.HasPrefix(x.StructName, "struct {") {
+ // This is a named type, return that name.
+ return x.StructName
+ }
+ // Figure out which fields have anonymous names.
+ var anon []bool
+ for _, f := range strings.Split(x.StructName[8:len(x.StructName)-1], ";") {
+ f = strings.TrimSpace(f)
+ anon = append(anon, !strings.Contains(f, " "))
+ // TODO: this isn't perfect. If the field type has a space in it,
+ // then this logic doesn't work. Need to search for keyword for
+ // field type, like "interface", "struct", ...
+ }
+ // Make sure anon is long enough. This probably never triggers.
+ for len(anon) < len(x.Field) {
+ anon = append(anon, false)
+ }
+
+ // Build runtime name from the DWARF fields.
+ s := "struct {"
+ first := true
+ for _, f := range x.Field {
+ if !first {
+ s += ";"
+ }
+ name := f.Name
+ if i := strings.Index(name, "."); i >= 0 {
+ name = name[i+1:]
+ }
+ if anon[0] {
+ s += fmt.Sprintf(" %s", runtimeName(f.Type))
+ } else {
+ s += fmt.Sprintf(" %s %s", name, runtimeName(f.Type))
+ }
+ first = false
+ anon = anon[1:]
+ }
+ s += " }"
+ return s
+ default:
+ name := dt.String()
+ if i := strings.LastIndex(name, "/"); i >= 0 {
+ name = name[i+1:] // Runtime uses only last name in package path.
+ }
+ return name
+ }
+}
+
+// readRuntimeConstants populates the p.rtConstants map.
+func (p *Process) readRuntimeConstants() {
+ p.rtConstants = map[string]int64{}
+
+ // Hardcoded values for Go 1.9.
+ // (Go did not have constants in DWARF before 1.10.)
+ m := p.rtConstants
+ m["_MSpanDead"] = 0
+ m["_MSpanInUse"] = 1
+ m["_MSpanManual"] = 2
+ m["_MSpanFree"] = 3
+ m["_Gidle"] = 0
+ m["_Grunnable"] = 1
+ m["_Grunning"] = 2
+ m["_Gsyscall"] = 3
+ m["_Gwaiting"] = 4
+ m["_Gdead"] = 6
+ m["_Gscan"] = 0x1000
+ m["_PCDATA_StackMapIndex"] = 0
+ m["_FUNCDATA_LocalsPointerMaps"] = 1
+ m["_FUNCDATA_ArgsPointerMaps"] = 0
+ m["tflagExtraStar"] = 1 << 1
+ m["kindGCProg"] = 1 << 6
+ m["kindDirectIface"] = 1 << 5
+ m["_PageSize"] = 1 << 13
+ m["_KindSpecialFinalizer"] = 1
+
+ // From 1.10, these constants are recorded in DWARF records.
+ d, _ := p.proc.DWARF()
+ r := d.Reader()
+ for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() {
+ if e.Tag != dwarf.TagConstant {
+ continue
+ }
+ name := e.AttrField(dwarf.AttrName).Val.(string)
+ if !strings.HasPrefix(name, "runtime.") {
+ continue
+ }
+ name = name[8:]
+ c := e.AttrField(dwarf.AttrConstValue)
+ if c == nil {
+ continue
+ }
+ p.rtConstants[name] = c.Val.(int64)
+ }
+}
+
+const (
+ _DW_OP_addr = 0x03
+ _DW_OP_call_frame_cfa = 0x9c
+ _DW_OP_plus = 0x22
+ _DW_OP_consts = 0x11
+)
+
+func (p *Process) readGlobals() {
+ d, _ := p.proc.DWARF()
+ r := d.Reader()
+ for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() {
+ if e.Tag != dwarf.TagVariable {
+ continue
+ }
+ loc := e.AttrField(dwarf.AttrLocation).Val.([]byte)
+ if len(loc) == 0 || loc[0] != _DW_OP_addr {
+ continue
+ }
+ var a core.Address
+ if p.proc.PtrSize() == 8 {
+ a = core.Address(p.proc.ByteOrder().Uint64(loc[1:]))
+ } else {
+ a = core.Address(p.proc.ByteOrder().Uint32(loc[1:]))
+ }
+ if !p.proc.Writeable(a) {
+ // Read-only globals can't have heap pointers.
+ // TODO: keep roots around anyway?
+ continue
+ }
+ dt, err := d.Type(e.AttrField(dwarf.AttrType).Val.(dwarf.Offset))
+ if err != nil {
+ panic(err)
+ }
+ if _, ok := dt.(*dwarf.UnspecifiedType); ok {
+ 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],
+ Frame: nil,
+ })
+ }
+}
+
+func (p *Process) readStackVars() {
+ type Var struct {
+ name string
+ off int64
+ typ *Type
+ }
+ vars := map[*Func][]Var{}
+ var curfn *Func
+ d, _ := p.proc.DWARF()
+ r := d.Reader()
+ for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() {
+ if e.Tag == dwarf.TagSubprogram {
+ min := core.Address(e.AttrField(dwarf.AttrLowpc).Val.(uint64))
+ max := core.Address(e.AttrField(dwarf.AttrHighpc).Val.(uint64))
+ f := p.funcTab.find(min)
+ if f == nil {
+ // some func Go doesn't know about. C?
+ curfn = nil
+ } else {
+ if f.entry != min {
+ panic("dwarf and runtime don't agree about start of " + f.name)
+ }
+ if p.funcTab.find(max-1) != f {
+ panic("function ranges don't match for " + f.name)
+ }
+ curfn = f
+ }
+ continue
+ }
+ if e.Tag != dwarf.TagVariable && e.Tag != dwarf.TagFormalParameter {
+ continue
+ }
+ aloc := e.AttrField(dwarf.AttrLocation)
+ if aloc == nil {
+ continue
+ }
+ // Interpret locations of the form
+ // DW_OP_call_frame_cfa
+ // DW_OP_consts <off>
+ // DW_OP_plus
+ // (with possibly missing DW_OP_consts & DW_OP_plus for the zero offset.)
+ // TODO: handle other possible locations (e.g. register locations).
+ loc := aloc.Val.([]byte)
+ if len(loc) == 0 || loc[0] != _DW_OP_call_frame_cfa {
+ continue
+ }
+ loc = loc[1:]
+ var off int64
+ if len(loc) != 0 && loc[0] == _DW_OP_consts {
+ loc = loc[1:]
+ var s uint
+ for len(loc) > 0 {
+ b := loc[0]
+ loc = loc[1:]
+ off += int64(b&0x7f) << s
+ s += 7
+ if b&0x80 == 0 {
+ break
+ }
+ }
+ off = off << (64 - s) >> (64 - s)
+ if len(loc) == 0 || loc[0] != _DW_OP_plus {
+ continue
+ }
+ loc = loc[1:]
+ }
+ if len(loc) != 0 {
+ continue // more stuff we don't recognize
+ }
+ dt, err := d.Type(e.AttrField(dwarf.AttrType).Val.(dwarf.Offset))
+ if err != nil {
+ panic(err)
+ }
+ name := e.AttrField(dwarf.AttrName).Val.(string)
+ vars[curfn] = append(vars[curfn], Var{name: name, off: off, typ: p.dwarfMap[dt]})
+ }
+
+ // Get roots from goroutine stacks.
+ for _, g := range p.goroutines {
+ for _, f := range g.frames {
+ // Start with all pointer slots as unnamed.
+ unnamed := map[core.Address]bool{}
+ 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,
+ Frame: f,
+ }
+ f.roots = append(f.roots, r)
+ // Remove this variable from the set of unnamed pointers.
+ for a := r.Addr; a < r.Addr.Add(r.Type.Size); a = a.Add(p.proc.PtrSize()) {
+ delete(unnamed, a)
+ }
+ }
+ // Emit roots for unnamed pointer slots in the frame.
+ // Make deterministic by sorting first.
+ s := make([]core.Address, 0, len(unnamed))
+ for a := range unnamed {
+ s = append(s, a)
+ }
+ 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"),
+ Frame: f,
+ }
+ f.roots = append(f.roots, r)
+ }
+ }
+ }
+}
+
+/* Dwarf encoding notes
+
+type XXX sss
+
+translates to a dwarf type pkg.XXX of the type of sss (uint, float, ...)
+
+exception: if sss is a struct or array, then we get two types, the "unnamed" and "named" type.
+The unnamed type is a dwarf struct type with name "struct pkg.XXX" or a dwarf array type with
+name [N]elem.
+Then there is a typedef with pkg.XXX pointing to "struct pkg.XXX" or [N]elem.
+
+For structures, lowercase field names are prepended with the package name (pkg path?).
+
+type XXX interface{}
+pkg.XXX is a typedef to "struct runtime.eface"
+type XXX interface{f()}
+pkg.XXX is a typedef to "struct runtime.iface"
+
+Sometimes there is even a chain of identically-named typedefs. I have no idea why.
+main.XXX -> main.XXX -> struct runtime.iface
+
+*/
diff --git a/gocore/gocore_test.go b/gocore/gocore_test.go
new file mode 100644
index 0000000..ab6d980
--- /dev/null
+++ b/gocore/gocore_test.go
@@ -0,0 +1,165 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "reflect"
+ "testing"
+
+ "golang.org/x/debug/core"
+)
+
+// loadTest loads a simple core file which resulted from running the
+// following program on linux/amd64 with go 1.9.0 (the earliest supported runtime):
+// package main
+// func main() {
+// _ = *(*int)(nil)
+// }
+func loadExample(t *testing.T) *Process {
+ c, err := core.Core("testdata/core", "testdata")
+ if err != nil {
+ t.Fatalf("can't load test core file: %s", err)
+ }
+ p, err := Core(c, FlagTypes|FlagReverse)
+ if err != nil {
+ t.Fatalf("can't parse Go core: %s", err)
+ }
+ return p
+}
+
+func TestObjects(t *testing.T) {
+ p := loadExample(t)
+ n := 0
+ p.ForEachObject(func(x Object) bool {
+ n++
+ return true
+ })
+ if n != 104 {
+ t.Errorf("#objects = %d, want 104", n)
+ }
+}
+
+func TestRoots(t *testing.T) {
+ p := loadExample(t)
+ n := 0
+ p.ForEachRoot(func(r *Root) bool {
+ n++
+ return true
+ })
+ if n != 257 {
+ t.Errorf("#roots = %d, want 257", n)
+ }
+}
+
+// TestConfig checks the configuration accessors.
+func TestConfig(t *testing.T) {
+ p := loadExample(t)
+ if v := p.BuildVersion(); v != "go1.9" {
+ t.Errorf("version=%s, wanted go1.9", v)
+ }
+ if n := p.Stats().Size; n != 2732032 {
+ t.Errorf("all stats=%d, want 2732032", n)
+ }
+}
+
+func TestFindFunc(t *testing.T) {
+ p := loadExample(t)
+ a := core.Address(0x404000)
+ f := p.FindFunc(a)
+ if f == nil {
+ t.Errorf("can't find function at %x", a)
+ return
+ }
+ if n := f.Name(); n != "runtime.recvDirect" {
+ t.Errorf("funcname(%x)=%s, want runtime.recvDirect", a, n)
+ }
+}
+
+func TestTypes(t *testing.T) {
+ p := loadExample(t)
+ // Check the type of a few objects.
+ for _, s := range [...]struct {
+ addr core.Address
+ size int64
+ kind Kind
+ name string
+ repeat int64
+ }{
+ {0xc420000480, 384, KindStruct, "runtime.g", 1},
+ {0xc42000a020, 32, KindPtr, "*runtime.g", 4},
+ {0xc420082000, 96, KindStruct, "hchan<bool>", 1},
+ {0xc420062000, 64, KindStruct, "runtime._defer", 1},
+ } {
+ x, i := p.FindObject(s.addr)
+ if x == 0 {
+ t.Errorf("can't find object at %x", s.addr)
+ continue
+ }
+ if i != 0 {
+ t.Errorf("offset(%x)=%d, want 0", s.addr, i)
+ }
+ if p.Size(x) != s.size {
+ t.Errorf("size(%x)=%d, want %d", s.addr, p.Size(x), s.size)
+ }
+ typ, repeat := p.Type(x)
+ if typ.Kind != s.kind {
+ t.Errorf("kind(%x)=%s, want %s", s.addr, typ.Kind, s.kind)
+ }
+ if typ.Name != s.name {
+ t.Errorf("name(%x)=%s, want %s", s.addr, typ.Name, s.name)
+ }
+ if repeat != s.repeat {
+ t.Errorf("repeat(%x)=%d, want %d", s.addr, repeat, s.repeat)
+ }
+
+ y, i := p.FindObject(s.addr + 1)
+ if y != x {
+ t.Errorf("can't find object at %x", s.addr+1)
+ }
+ if i != 1 {
+ t.Errorf("offset(%x)=%d, want i", s.addr, i)
+ }
+ }
+}
+
+func TestReverse(t *testing.T) {
+ p := loadExample(t)
+
+ // Build the pointer map.
+ // m[x]=y means address x has a pointer to address y.
+ m1 := map[core.Address]core.Address{}
+ p.ForEachObject(func(x Object) bool {
+ p.ForEachPtr(x, func(i int64, y Object, j int64) bool {
+ m1[p.Addr(x).Add(i)] = p.Addr(y).Add(j)
+ return true
+ })
+ return true
+ })
+ p.ForEachRoot(func(r *Root) bool {
+ p.ForEachRootPtr(r, func(i int64, y Object, j int64) bool {
+ m1[r.Addr.Add(i)] = p.Addr(y).Add(j)
+ return true
+ })
+ return true
+ })
+
+ // Build the same, with reverse entries.
+ m2 := map[core.Address]core.Address{}
+ p.ForEachObject(func(y Object) bool {
+ p.ForEachReversePtr(y, func(x Object, r *Root, i, j int64) bool {
+ if r != nil {
+ m2[r.Addr.Add(i)] = p.Addr(y).Add(j)
+ } else {
+ m2[p.Addr(x).Add(i)] = p.Addr(y).Add(j)
+ }
+ return true
+ })
+ return true
+ })
+
+ if !reflect.DeepEqual(m1, m2) {
+ t.Errorf("forward and reverse edges don't match")
+ }
+}
diff --git a/gocore/goroutine.go b/gocore/goroutine.go
new file mode 100644
index 0000000..b4891eb
--- /dev/null
+++ b/gocore/goroutine.go
@@ -0,0 +1,108 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "golang.org/x/debug/core"
+)
+
+type Goroutine struct {
+ r region // inferior region holding the runtime.g
+ stackSize int64 // current stack allocation
+ frames []*Frame
+
+ // TODO: defers, in-progress panics
+}
+
+// Stack returns the total allocated stack for g.
+func (g *Goroutine) Stack() int64 {
+ return g.stackSize
+}
+
+// Addr returns the address of the runtime.g that identifies this goroutine.
+func (g *Goroutine) Addr() core.Address {
+ return g.r.a
+}
+
+// Frames returns the list of frames on the stack of the Goroutine.
+// The first frame is the most recent one.
+// This list is post-optimization, so any inlined calls, tail calls, etc.
+// will not appear.
+func (g *Goroutine) Frames() []*Frame {
+ return g.frames
+}
+
+// A Frame represents the local variables of a single Go function invocation.
+// (Note that in the presence of inlining, a Frame may contain local variables
+// for more than one Go function invocation.)
+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
+
+ roots []*Root // GC roots in this frame
+
+ // TODO: keep vars from dwarf around?
+}
+
+// Func returns the function for which this frame is an activation record.
+func (f *Frame) Func() *Func {
+ return f.f
+}
+
+// Min returns the minimum address of this frame.
+func (f *Frame) Min() core.Address {
+ return f.min
+}
+
+// Max returns the maximum address of this frame.
+func (f *Frame) Max() core.Address {
+ return f.max
+}
+
+// PC returns the program counter of the next instruction to be executed by this frame.
+func (f *Frame) PC() core.Address {
+ return f.pc
+}
+
+// Roots returns a list of all the garbage collection roots in the frame.
+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 Func represents a Go function.
+type Func struct {
+ r region // inferior region holding a runtime._func
+ module *module
+ name string
+ entry core.Address
+ frameSize pcTab // map from pc to frame size at that pc
+ pcdata []int32
+ funcdata []core.Address
+ stackMap pcTab // map from pc to stack map # (index into locals and args bitmaps)
+ closure *Type // the type to use for closures of this function. Lazily allocated.
+}
+
+// Name returns the name of the function, as reported by DWARF.
+// Names are opaque; do not depend on the format of the returned name.
+func (f *Func) Name() string {
+ return f.name
+}
+
+// Entry returns the address of the entry point of f.
+func (f *Func) Entry() core.Address {
+ return f.entry
+}
diff --git a/gocore/module.go b/gocore/module.go
new file mode 100644
index 0000000..93b5451
--- /dev/null
+++ b/gocore/module.go
@@ -0,0 +1,192 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "fmt"
+ "sort"
+
+ "golang.org/x/debug/core"
+)
+
+type module struct {
+ r region // inferior region holding a runtime.moduledata
+ types, etypes core.Address // range that holds all the runtime._type data in this module
+}
+
+func (p *Process) readModules() {
+ // Note: the cast is necessary for cores generated by Go 1.9, where
+ // runtime.moduledata is just an unsafe.Pointer.
+ ms := p.rtGlobals["modulesSlice"].Cast("*[]*runtime.moduledata").Deref()
+ n := ms.SliceLen()
+ for i := int64(0); i < n; i++ {
+ md := ms.SliceIndex(i).Deref()
+ p.modules = append(p.modules, p.readModule(md))
+ }
+ p.funcTab.sort()
+}
+
+func (p *Process) readModule(r region) *module {
+ m := &module{r: r}
+ m.types = core.Address(r.Field("types").Uintptr())
+ m.etypes = core.Address(r.Field("etypes").Uintptr())
+
+ // Read the pc->function table
+ pcln := r.Field("pclntable")
+ ftab := r.Field("ftab")
+ n := ftab.SliceLen() - 1 // last slot is a dummy, just holds entry
+ for i := int64(0); i < n; i++ {
+ ft := ftab.SliceIndex(i)
+ min := core.Address(ft.Field("entry").Uintptr())
+ max := core.Address(ftab.SliceIndex(i + 1).Field("entry").Uintptr())
+ fr := pcln.SliceIndex(int64(ft.Field("funcoff").Uintptr())).Cast("runtime._func")
+ f := m.readFunc(fr, pcln)
+ if f.entry != min {
+ panic(fmt.Errorf("entry %x and min %x don't match for %s", f.entry, min, f.name))
+ }
+ p.funcTab.add(min, max, f)
+ }
+
+ return m
+}
+
+// readFunc parses a runtime._func and returns a *Func.
+// r must have type runtime._func.
+// pcln must have type []byte and represent the module's pcln table region.
+func (m *module) readFunc(r region, pcln region) *Func {
+ f := &Func{module: m, r: r}
+ f.entry = core.Address(r.Field("entry").Uintptr())
+ f.name = r.p.proc.ReadCString(pcln.SliceIndex(int64(r.Field("nameoff").Int32())).a)
+ f.frameSize.read(r.p.proc, pcln.SliceIndex(int64(r.Field("pcsp").Int32())).a)
+
+ // Parse pcdata and funcdata, which are laid out beyond the end of the _func.
+ a := r.a.Add(int64(r.p.findType("runtime._func").Size))
+ n := r.Field("npcdata").Int32()
+ for i := int32(0); i < n; i++ {
+ f.pcdata = append(f.pcdata, r.p.proc.ReadInt32(a))
+ a = a.Add(4)
+ }
+ a = a.Align(r.p.proc.PtrSize())
+ n = r.Field("nfuncdata").Int32()
+ for i := int32(0); i < n; i++ {
+ f.funcdata = append(f.funcdata, r.p.proc.ReadPtr(a))
+ a = a.Add(r.p.proc.PtrSize())
+ }
+
+ // Read pcln tables we need.
+ if stackmap := int(r.p.rtConstants["_PCDATA_StackMapIndex"]); stackmap < len(f.pcdata) {
+ f.stackMap.read(r.p.proc, pcln.SliceIndex(int64(f.pcdata[stackmap])).a)
+ } else {
+ f.stackMap.setEmpty()
+ }
+
+ return f
+}
+
+type funcTabEntry struct {
+ min, max core.Address
+ f *Func
+}
+
+type funcTab struct {
+ entries []funcTabEntry
+}
+
+// add records that PCs in the range [min,max) map to function f.
+func (t *funcTab) add(min, max core.Address, f *Func) {
+ t.entries = append(t.entries, funcTabEntry{min: min, max: max, f: f})
+}
+
+// sort must be called after all the adds, but before any find.
+func (t *funcTab) sort() {
+ sort.Slice(t.entries, func(i, j int) bool {
+ return t.entries[i].min < t.entries[j].min
+ })
+}
+
+// Finds a Func for the given address. Sort must have been called already.
+func (t *funcTab) find(pc core.Address) *Func {
+ n := sort.Search(len(t.entries), func(i int) bool {
+ return t.entries[i].max > pc
+ })
+ if n == len(t.entries) || pc < t.entries[n].min || pc >= t.entries[n].max {
+ return nil
+ }
+ return t.entries[n].f
+}
+
+// a pcTab maps from an offset in a function to an int64.
+type pcTab struct {
+ entries []pcTabEntry
+}
+
+type pcTabEntry struct {
+ bytes int64 // # of bytes this entry covers
+ val int64 // value over that range of bytes
+}
+
+// read parses a pctab from the core file at address data.
+func (t *pcTab) read(core *core.Process, data core.Address) {
+ var pcQuantum int64
+ switch core.Arch() {
+ case "386", "amd64", "amd64p32":
+ pcQuantum = 1
+ case "s390x":
+ pcQuantum = 2
+ case "arm", "arm64", "mips", "mipsle", "mips64", "mips64le", "ppc64", "ppc64le":
+ pcQuantum = 4
+ default:
+ panic("unknown architecture " + core.Arch())
+ }
+ val := int64(-1)
+ first := true
+ for {
+ // Advance value.
+ v, n := readVarint(core, data)
+ if v == 0 && !first {
+ return
+ }
+ data = data.Add(n)
+ if v&1 != 0 {
+ val += ^(v >> 1)
+ } else {
+ val += v >> 1
+ }
+
+ // Advance pc.
+ v, n = readVarint(core, data)
+ data = data.Add(n)
+ t.entries = append(t.entries, pcTabEntry{bytes: v * pcQuantum, val: val})
+ first = false
+ }
+}
+
+func (t *pcTab) setEmpty() {
+ t.entries = []pcTabEntry{{bytes: 1<<63 - 1, val: -1}}
+}
+
+func (t *pcTab) find(off int64) int64 {
+ for _, e := range t.entries {
+ if off < e.bytes {
+ return e.val
+ }
+ off -= e.bytes
+ }
+ panic("can't find pctab entry")
+}
+
+// readVarint reads a varint from the core file.
+// val is the value, n is the number of bytes consumed.
+func readVarint(core *core.Process, a core.Address) (val, n int64) {
+ for {
+ b := core.ReadUint8(a)
+ val |= int64(b&0x7f) << uint(n*7)
+ n++
+ a++
+ if b&0x80 == 0 {
+ return
+ }
+ }
+}
diff --git a/gocore/object.go b/gocore/object.go
new file mode 100644
index 0000000..6990e33
--- /dev/null
+++ b/gocore/object.go
@@ -0,0 +1,339 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "math/bits"
+ "strings"
+
+ "golang.org/x/debug/core"
+)
+
+// An Object represents a single object in the Go heap.
+type Object core.Address
+
+// markObjects finds all the live objects in the heap and marks them
+// in the p.heapInfo mark fields.
+func (p *Process) markObjects() {
+ ptrSize := p.proc.PtrSize()
+
+ // number of live objects found so far
+ n := 0
+ // total size of live objects
+ var live int64
+
+ var q []Object
+
+ // Function to call when we find a new pointer.
+ add := func(x core.Address) {
+ h := p.findHeapInfo(x)
+ if h == nil || h.base == 0 { // not in heap or not in a valid span
+ // Invalid spans can happen with intra-stack pointers.
+ return
+ }
+ // Round down to object start.
+ x = h.base.Add(x.Sub(h.base) / h.size * h.size)
+ // Object start may map to a different info. Reload.
+ h = p.findHeapInfo(x)
+ // Find mark bit
+ b := uint64(x) % heapInfoSize / 8
+ if h.mark&(uint64(1)<<b) != 0 { // already found
+ return
+ }
+ h.mark |= uint64(1) << b
+ n++
+ live += h.size
+ q = append(q, Object(x))
+ }
+
+ // Start with scanning all the roots.
+ // Note that we don't just use the DWARF roots, just in case DWARF isn't complete.
+ // Instead we use exactly what the runtime uses.
+
+ // Goroutine roots
+ for _, g := range p.goroutines {
+ for _, f := range g.frames {
+ for a := range f.Live {
+ add(p.proc.ReadPtr(a))
+ }
+ }
+ }
+
+ // Global roots
+ for _, m := range p.modules {
+ for _, s := range [2]string{"data", "bss"} {
+ min := core.Address(m.r.Field(s).Uintptr())
+ max := core.Address(m.r.Field("e" + s).Uintptr())
+ gc := m.r.Field("gc" + s + "mask").Field("bytedata").Address()
+ num := max.Sub(min) / ptrSize
+ for i := int64(0); i < num; i++ {
+ if p.proc.ReadUint8(gc.Add(i/8))>>uint(i%8)&1 != 0 {
+ add(p.proc.ReadPtr(min.Add(i * ptrSize)))
+ }
+ }
+ }
+ }
+
+ // Finalizers
+ for _, r := range p.globals {
+ if !strings.HasPrefix(r.Name, "finalizer for ") {
+ continue
+ }
+ for _, f := range r.Type.Fields {
+ if f.Type.Kind == KindPtr {
+ add(p.proc.ReadPtr(r.Addr.Add(f.Off)))
+ }
+ }
+ }
+
+ // Expand root set to all reachable objects.
+ for len(q) > 0 {
+ x := q[len(q)-1]
+ q = q[:len(q)-1]
+
+ // Scan object for pointers.
+ size := p.Size(x)
+ for i := int64(0); i < size; i += ptrSize {
+ a := core.Address(x).Add(i)
+ if p.isPtrFromHeap(a) {
+ add(p.proc.ReadPtr(a))
+ }
+ }
+ }
+
+ p.nObj = n
+
+ // Initialize firstIdx fields in the heapInfo, for fast object index lookups.
+ for i := len(p.heapInfo) - 1; i >= 0; i-- {
+ h := &p.heapInfo[i]
+ if h.mark == 0 { // not really necessary, just leave -1 sentinel as a double check.
+ continue
+ }
+ n -= bits.OnesCount64(h.mark)
+ h.firstIdx = n
+ }
+
+ // Update stats to include the live/garbage distinction.
+ alloc := p.Stats().Child("heap").Child("in use spans").Child("alloc")
+ alloc.Children = []*Stats{
+ &Stats{"live", live, nil},
+ &Stats{"garbage", alloc.Size - live, nil},
+ }
+}
+
+// isPtrFromHeap reports whether the inferior at address a contains a pointer.
+// a must be somewhere in the heap.
+func (p *Process) isPtrFromHeap(a core.Address) bool {
+ // Convert arena offset in words to bitmap offset in bits.
+ off := a.Sub(p.arenaStart)
+ off >>= p.proc.LogPtrSize()
+
+ // Find bit in bitmap. It goes backwards from the end.
+ // Each byte contains pointer/nonpointer bits for 4 words in its low nybble.
+ return p.proc.ReadUint8(p.bitmapEnd.Add(-(off>>2)-1))>>uint(off&3)&1 != 0
+}
+
+// IsPtr reports whether the inferior at address a contains a pointer.
+func (p *Process) IsPtr(a core.Address) bool {
+ if a >= p.arenaStart && a < p.arenaUsed {
+ return p.isPtrFromHeap(a)
+ }
+ for _, m := range p.modules {
+ for _, s := range [2]string{"data", "bss"} {
+ min := core.Address(m.r.Field(s).Uintptr())
+ max := core.Address(m.r.Field("e" + s).Uintptr())
+ if a < min || a >= max {
+ continue
+ }
+ gc := m.r.Field("gc" + s + "mask").Field("bytedata").Address()
+ i := a.Sub(min)
+ return p.proc.ReadUint8(gc.Add(i/8))>>uint(i%8) != 0
+ }
+ }
+ // Everywhere else can't be a pointer. At least, not a pointer into the Go heap.
+ // TODO: stacks?
+ // TODO: finalizers?
+ return false
+}
+
+// FindObject finds the object containing a. Returns that object and the offset within
+// that object to which a points.
+// Returns 0,0 if a doesn't point to a live heap object.
+func (p *Process) FindObject(a core.Address) (Object, int64) {
+ // Round down to the start of an object.
+ h := p.findHeapInfo(a)
+ if h == nil || h.size == 0 {
+ // Not in Go heap, or in a span
+ // that doesn't hold Go objects (freed, stacks, ...)
+ return 0, 0
+ }
+ x := h.base.Add(a.Sub(h.base) / h.size * h.size)
+ // Check if object is marked.
+ h = p.findHeapInfo(x)
+ if h.mark>>(uint64(x)%heapInfoSize/8)&1 == 0 {
+ return 0, 0
+ }
+ return Object(x), a.Sub(x)
+}
+
+func (p *Process) findObjectIndex(a core.Address) (int, int64) {
+ x, off := p.FindObject(a)
+ if x == 0 {
+ return -1, 0
+ }
+ h := p.findHeapInfo(core.Address(x))
+ return h.firstIdx + bits.OnesCount64(h.mark&(uint64(1)<<(uint64(x)%heapInfoSize/8)-1)), off
+}
+
+// ForEachObject calls fn with each object in the Go heap.
+// If fn returns false, ForEachObject returns immediately.
+func (p *Process) ForEachObject(fn func(x Object) bool) {
+ for i := 0; i < len(p.heapInfo); i++ {
+ m := p.heapInfo[i].mark
+ for m != 0 {
+ j := bits.TrailingZeros64(m)
+ m &= m - 1
+ if !fn(Object(p.arenaStart.Add(int64(i)*heapInfoSize + int64(j)*8))) {
+ return
+ }
+ }
+ }
+}
+
+// ForEachRoot calls fn with each garbage collection root.
+// If fn returns false, ForEachRoot returns immediately.
+func (p *Process) ForEachRoot(fn func(r *Root) bool) {
+ for _, r := range p.globals {
+ if !fn(r) {
+ return
+ }
+ }
+ for _, g := range p.goroutines {
+ for _, f := range g.frames {
+ for _, r := range f.roots {
+ if !fn(r) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// Addr returns the starting address of x.
+func (p *Process) Addr(x Object) core.Address {
+ return core.Address(x)
+}
+
+// Size returns the size of x in bytes.
+func (p *Process) Size(x Object) int64 {
+ return p.findHeapInfo(core.Address(x)).size
+}
+
+// Type returns the type and repeat count for the object x.
+// x contains at least repeat copies of the returned type.
+// FlagTypes must have been passed to Core when p was constructed.
+func (p *Process) Type(x Object) (*Type, int64) {
+ i, _ := p.findObjectIndex(core.Address(x))
+ return p.types[i].t, p.types[i].r
+}
+
+// ForEachPtr calls fn for all heap pointers it finds in x.
+// It calls fn with:
+// the offset of the pointer slot in x
+// the pointed-to object y
+// the offset in y where the pointer points.
+// If fn returns false, ForEachPtr returns immediately.
+// For an edge from an object to its finalizer, the first argument
+// passed to fn will be -1. (TODO: implement)
+func (p *Process) ForEachPtr(x Object, fn func(int64, Object, int64) bool) {
+ size := p.Size(x)
+ for i := int64(0); i < size; i += p.proc.PtrSize() {
+ a := core.Address(x).Add(i)
+ if !p.isPtrFromHeap(a) {
+ continue
+ }
+ ptr := p.proc.ReadPtr(a)
+ y, off := p.FindObject(ptr)
+ if y != 0 {
+ if !fn(i, y, off) {
+ return
+ }
+ }
+ }
+}
+
+// ForEachRootPtr behaves like ForEachPtr but it starts with a Root instead of an Object.
+func (p *Process) ForEachRootPtr(r *Root, fn func(int64, Object, int64) bool) {
+ edges1(p, r, 0, r.Type, fn)
+}
+
+// edges1 calls fn for the edges found in an object of type t living at offset off in the root r.
+// If fn returns false, return immediately with false.
+func edges1(p *Process, r *Root, off int64, t *Type, fn func(int64, Object, int64) bool) bool {
+ switch t.Kind {
+ case KindBool, KindInt, KindUint, KindFloat, KindComplex:
+ // no edges here
+ case KindIface, KindEface:
+ // The first word is a type or itab.
+ // Itabs are never in the heap.
+ // Types might be, though.
+ a := r.Addr.Add(off)
+ if r.Frame == nil || r.Frame.Live[a] {
+ dst, off2 := p.FindObject(p.proc.ReadPtr(a))
+ if dst != 0 {
+ if !fn(off, dst, off2) {
+ return false
+ }
+ }
+ }
+ // Treat second word like a pointer.
+ off += p.proc.PtrSize()
+ fallthrough
+ case KindPtr, KindString, KindSlice, KindFunc:
+ a := r.Addr.Add(off)
+ if r.Frame == nil || r.Frame.Live[a] {
+ dst, off2 := p.FindObject(p.proc.ReadPtr(a))
+ if dst != 0 {
+ if !fn(off, dst, off2) {
+ return false
+ }
+ }
+ }
+ case KindArray:
+ s := t.Elem.Size
+ for i := int64(0); i < t.Count; i++ {
+ if !edges1(p, r, off+i*s, t.Elem, fn) {
+ return false
+ }
+ }
+ case KindStruct:
+ for _, f := range t.Fields {
+ if !edges1(p, r, off+f.Off, f.Type, fn) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+const heapInfoSize = 512
+
+// Information for heapInfoSize bytes of heap.
+type heapInfo struct {
+ base core.Address // start of the span containing this heap region
+ size int64 // size of objects in the span
+ mark uint64 // 64 mark bits, one for every 8 bytes
+ firstIdx int // the index of the first object that starts in this region (-1 if none)
+}
+
+// findHeapInfo finds the heapInfo structure for a.
+// Returns nil if a is not a heap address.
+func (p *Process) findHeapInfo(a core.Address) *heapInfo {
+ if a < p.arenaStart || a >= p.arenaUsed {
+ return nil
+ }
+ i := a.Sub(p.arenaStart) / heapInfoSize
+ return &p.heapInfo[i]
+}
diff --git a/gocore/process.go b/gocore/process.go
new file mode 100644
index 0000000..2e061c8
--- /dev/null
+++ b/gocore/process.go
@@ -0,0 +1,546 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "debug/dwarf"
+ "fmt"
+ "strings"
+
+ "golang.org/x/debug/core"
+)
+
+// A Process represents the state of a Go process that core dumped.
+type Process struct {
+ proc *core.Process
+
+ arenaStart core.Address
+ arenaUsed core.Address
+ bitmapEnd core.Address
+
+ // data structure for fast object finding
+ heapInfo []heapInfo
+
+ // number of live objects
+ nObj int
+
+ goroutines []*Goroutine
+
+ // runtime info
+ rtGlobals map[string]region
+ rtConstants map[string]int64
+
+ // A module is a loadable unit. Most Go programs have 1, programs
+ // which load plugins will have more.
+ modules []*module
+
+ // address -> function mapping
+ funcTab funcTab
+
+ // map from dwarf type to *Type
+ dwarfMap map[dwarf.Type]*Type
+
+ // map from address of runtime._type to *Type
+ runtimeMap map[core.Address]*Type
+
+ // map from runtime type name to the set of *Type with that name
+ // Used to find candidates to put in the runtimeMap map.
+ runtimeNameMap map[string][]*Type
+
+ // memory usage by category
+ stats *Stats
+
+ buildVersion string
+
+ globals []*Root
+
+ // Types of each object, indexed by object index.
+ // Only initialized if FlagTypes is passed to Core.
+ types []typeInfo
+
+ // Reverse edges.
+ // The reverse edges for object #i are redge[ridx[i]:ridx[i+1]].
+ // A "reverse edge" for object #i is a location in memory where a pointer
+ // to object #i lives.
+ // Only initialized if FlagReverse is passed to Core.
+ redge []core.Address
+ ridx []int64
+ // Sorted list of all roots.
+ // Only initialized if FlagReverse is passed to Core.
+ rootIdx []*Root
+}
+
+// Process returns the core.Process used to construct this Process.
+func (p *Process) Process() *core.Process {
+ return p.proc
+}
+
+func (p *Process) Goroutines() []*Goroutine {
+ return p.goroutines
+}
+
+// Stats returns a breakdown of the program's memory use by category.
+func (p *Process) Stats() *Stats {
+ return p.stats
+}
+
+// BuildVersion returns the Go version that was used to build the inferior binary.
+func (p *Process) BuildVersion() string {
+ return p.buildVersion
+}
+
+func (p *Process) Globals() []*Root {
+ return p.globals
+}
+
+// FindFunc returns the function which contains the code at address pc, if any.
+func (p *Process) FindFunc(pc core.Address) *Func {
+ return p.funcTab.find(pc)
+}
+
+func (p *Process) findType(name string) *Type {
+ s := p.runtimeNameMap[name]
+ if len(s) == 0 {
+ panic("can't find type " + name)
+ }
+ return s[0]
+}
+
+// A Flags indicates optional analyses for Core to compute.
+type Flags uint8
+
+const (
+ // FlagTypes requests that Core compute type information for all Go objects,
+ // required to use the Type function.
+ // Setting this flag will require more initialization time and use more memory.
+ FlagTypes Flags = 1 << iota
+ // FlagReverse requests that Core compute reverse edge information,
+ // required to use ForEachReversePtr.
+ // Setting this flag will require more initialization time and use more memory.
+ FlagReverse
+)
+
+// Core takes a loaded core file and extracts Go information from it.
+// flags is a bitmask of data that should be extracted from the core.
+func Core(proc *core.Process, flags Flags) (p *Process, err error) {
+ // Make sure we have DWARF info.
+ if _, err := proc.DWARF(); err != nil {
+ return nil, err
+ }
+
+ // Guard against failures of proc.Read* routines.
+ /*
+ defer func() {
+ e := recover()
+ if e == nil {
+ return
+ }
+ p = nil
+ if x, ok := e.(error); ok {
+ err = x
+ return
+ }
+ panic(e) // Not an error, re-panic it.
+ }()
+ */
+
+ p = &Process{
+ proc: proc,
+ runtimeMap: map[core.Address]*Type{},
+ dwarfMap: map[dwarf.Type]*Type{},
+ }
+
+ // Initialize everything that just depends on DWARF.
+ p.readDWARFTypes()
+ p.readRuntimeConstants()
+ p.readGlobals()
+
+ // Find runtime globals we care about. Initialize regions for them.
+ p.rtGlobals = map[string]region{}
+ for _, g := range p.globals {
+ if strings.HasPrefix(g.Name, "runtime.") {
+ p.rtGlobals[g.Name[8:]] = region{p: p, a: g.Addr, typ: g.Type}
+ }
+ }
+
+ // Read all the data that depend on runtime globals.
+ p.buildVersion = p.rtGlobals["buildVersion"].String()
+ p.readModules()
+ p.readSpans()
+ p.readGs()
+ p.readStackVars() // needs to be after readGs.
+ p.markObjects() // needs to be after readGlobals, readStackVars.
+ if flags&FlagTypes != 0 {
+ p.typeHeap() // needs to be after markObjects.
+ }
+ if flags&FlagReverse != 0 {
+ p.reverseEdges() // needs to be after markObjects.
+ }
+
+ return p, nil
+}
+
+func (p *Process) readSpans() {
+ mheap := p.rtGlobals["mheap_"]
+
+ spanTableStart := mheap.Field("spans").SlicePtr().Address()
+ spanTableEnd := spanTableStart.Add(mheap.Field("spans").SliceCap() * p.proc.PtrSize())
+ arenaStart := core.Address(mheap.Field("arena_start").Uintptr())
+ arenaUsed := core.Address(mheap.Field("arena_used").Uintptr())
+ arenaEnd := core.Address(mheap.Field("arena_end").Uintptr())
+ bitmapEnd := core.Address(mheap.Field("bitmap").Uintptr())
+ bitmapStart := bitmapEnd.Add(-int64(mheap.Field("bitmap_mapped").Uintptr()))
+
+ p.arenaStart = arenaStart
+ p.arenaUsed = arenaUsed
+ p.bitmapEnd = bitmapEnd
+
+ var all int64
+ var text int64
+ var readOnly int64
+ var heap int64
+ var spanTable int64
+ var bitmap int64
+ var data int64
+ var bss int64 // also includes mmap'd regions
+ for _, m := range p.proc.Mappings() {
+ size := m.Size()
+ all += size
+ switch m.Perm() {
+ case core.Read:
+ readOnly += size
+ case core.Read | core.Exec:
+ text += size
+ case core.Read | core.Write:
+ if m.CopyOnWrite() {
+ // Check if m.file == text's file? That could distinguish
+ // data segment from mmapped file.
+ data += size
+ break
+ }
+ attribute := func(x, y core.Address, p *int64) {
+ a := x.Max(m.Min())
+ b := y.Min(m.Max())
+ if a < b {
+ *p += b.Sub(a)
+ size -= b.Sub(a)
+ }
+ }
+ attribute(spanTableStart, spanTableEnd, &spanTable)
+ attribute(arenaStart, arenaEnd, &heap)
+ attribute(bitmapStart, bitmapEnd, &bitmap)
+ // Any other anonymous mapping is bss.
+ // TODO: how to distinguish original bss from anonymous mmap?
+ bss += size
+ default:
+ panic("weird mapping " + m.Perm().String())
+ }
+ }
+ pageSize := p.rtConstants["_PageSize"]
+
+ // Span types
+ spanInUse := uint8(p.rtConstants["_MSpanInUse"])
+ spanManual := uint8(p.rtConstants["_MSpanManual"])
+ spanDead := uint8(p.rtConstants["_MSpanDead"])
+ spanFree := uint8(p.rtConstants["_MSpanFree"])
+
+ // Process spans.
+ if pageSize%heapInfoSize != 0 {
+ panic(fmt.Sprintf("page size not a multiple of %d", heapInfoSize))
+ }
+ p.heapInfo = make([]heapInfo, (p.arenaUsed-p.arenaStart)/heapInfoSize)
+ allspans := mheap.Field("allspans")
+ var allSpanSize int64
+ var freeSpanSize int64
+ var manualSpanSize int64
+ var inUseSpanSize int64
+ var allocSize int64
+ var freeSize int64
+ var spanRoundSize int64
+ var manualAllocSize int64
+ var manualFreeSize int64
+ n := allspans.SliceLen()
+ for i := int64(0); i < n; i++ {
+ s := allspans.SliceIndex(i).Deref()
+ min := core.Address(s.Field("startAddr").Uintptr())
+ elemSize := int64(s.Field("elemsize").Uintptr())
+ nPages := int64(s.Field("npages").Uintptr())
+ spanSize := nPages * pageSize
+ max := min.Add(spanSize)
+ allSpanSize += spanSize
+ switch s.Field("state").Uint8() {
+ case spanInUse:
+ inUseSpanSize += spanSize
+ n := int64(s.Field("nelems").Uintptr())
+ // An object is allocated if it is marked as
+ // allocated or it is below freeindex.
+ x := s.Field("allocBits").Address()
+ alloc := make([]bool, n)
+ for i := int64(0); i < n; i++ {
+ alloc[i] = p.proc.ReadUint8(x.Add(i/8))>>uint(i%8)&1 != 0
+ }
+ k := int64(s.Field("freeindex").Uintptr())
+ for i := int64(0); i < k; i++ {
+ alloc[i] = true
+ }
+ for i := int64(0); i < n; i++ {
+ if alloc[i] {
+ allocSize += elemSize
+ } else {
+ freeSize += elemSize
+ }
+ }
+ spanRoundSize += spanSize - n*elemSize
+ for a := min; a < max; a += heapInfoSize {
+ p.heapInfo[(a.Sub(p.arenaStart))/heapInfoSize] = heapInfo{base: min, size: elemSize, firstIdx: -1}
+ }
+
+ // Process special records.
+ for sp := s.Field("specials"); sp.Address() != 0; sp = sp.Field("next") {
+ sp = sp.Deref() // *special to special
+ if sp.Field("kind").Uint8() != uint8(p.rtConstants["_KindSpecialFinalizer"]) {
+ // All other specials (just profile records) can't point into the heap.
+ continue
+ }
+ obj := min.Add(int64(sp.Field("offset").Uint16()))
+ p.globals = append(p.globals,
+ &Root{
+ Name: fmt.Sprintf("finalizer for %x", obj),
+ Addr: sp.a,
+ Type: p.findType("runtime.specialfinalizer"),
+ Frame: nil,
+ })
+ // TODO: these aren't really "globals", as they
+ // are kept alive by the object they reference being alive.
+ // But we have no way of adding edges from an object to
+ // the corresponding finalizer data, so we punt on that thorny
+ // issue for now.
+ }
+ case spanFree:
+ freeSpanSize += spanSize
+ case spanDead:
+ // These are just deallocated span descriptors. They use no heap.
+ case spanManual:
+ manualSpanSize += spanSize
+ manualAllocSize += spanSize
+ for x := core.Address(s.Field("manualFreeList").Cast("uintptr").Uintptr()); x != 0; x = p.proc.ReadPtr(x) {
+ manualAllocSize -= elemSize
+ manualFreeSize += elemSize
+ }
+ }
+ }
+
+ p.stats = &Stats{"all", all, []*Stats{
+ &Stats{"text", text, nil},
+ &Stats{"readonly", readOnly, nil},
+ &Stats{"data", data, nil},
+ &Stats{"bss", bss, nil},
+ &Stats{"heap", heap, []*Stats{
+ &Stats{"in use spans", inUseSpanSize, []*Stats{
+ &Stats{"alloc", allocSize, nil},
+ &Stats{"free", freeSize, nil},
+ &Stats{"round", spanRoundSize, nil},
+ }},
+ &Stats{"manual spans", manualSpanSize, []*Stats{
+ &Stats{"alloc", manualAllocSize, nil},
+ &Stats{"free", manualFreeSize, nil},
+ }},
+ &Stats{"free spans", freeSpanSize, nil},
+ }},
+ &Stats{"ptr bitmap", bitmap, nil},
+ &Stats{"span table", spanTable, nil},
+ }}
+
+ var check func(*Stats)
+ check = func(s *Stats) {
+ if len(s.Children) == 0 {
+ return
+ }
+ var sum int64
+ for _, c := range s.Children {
+ sum += c.Size
+ }
+ if sum != s.Size {
+ panic(fmt.Sprintf("check failed for %s: %d vs %d", s.Name, s.Size, sum))
+ }
+ for _, c := range s.Children {
+ check(c)
+ }
+ }
+ check(p.stats)
+}
+
+func (p *Process) readGs() {
+ // TODO: figure out how to "flush" running Gs.
+ allgs := p.rtGlobals["allgs"]
+ n := allgs.SliceLen()
+ for i := int64(0); i < n; i++ {
+ r := allgs.SliceIndex(i).Deref()
+ g := p.readG(r)
+ if g == nil {
+ continue
+ }
+ p.goroutines = append(p.goroutines, g)
+ }
+}
+
+func (p *Process) readG(r region) *Goroutine {
+ g := &Goroutine{r: r}
+ stk := r.Field("stack")
+ g.stackSize = int64(stk.Field("hi").Uintptr() - stk.Field("lo").Uintptr())
+
+ var osT *core.Thread // os thread working on behalf of this G (if any).
+ mp := r.Field("m")
+ if mp.Address() != 0 {
+ m := mp.Deref()
+ pid := m.Field("procid").Uint64()
+ // TODO check that m.curg points to g?
+ for _, t := range p.proc.Threads() {
+ if t.Pid() == pid {
+ osT = t
+ }
+ }
+ }
+ status := r.Field("atomicstatus").Uint32()
+ status &^= uint32(p.rtConstants["_Gscan"])
+ var sp, pc core.Address
+ switch status {
+ case uint32(p.rtConstants["_Gidle"]):
+ return g
+ case uint32(p.rtConstants["_Grunnable"]), uint32(p.rtConstants["_Gwaiting"]):
+ sched := r.Field("sched")
+ sp = core.Address(sched.Field("sp").Uintptr())
+ pc = core.Address(sched.Field("pc").Uintptr())
+ case uint32(p.rtConstants["_Grunning"]):
+ sp = osT.SP()
+ pc = osT.PC()
+ // TODO: back up to the calling frame?
+ case uint32(p.rtConstants["_Gsyscall"]):
+ sp = core.Address(r.Field("syscallsp").Uintptr())
+ pc = core.Address(r.Field("syscallpc").Uintptr())
+ // TODO: or should we use the osT registers?
+ case uint32(p.rtConstants["_Gdead"]):
+ return nil
+ // TODO: copystack, others?
+ default:
+ // Unknown state. We can't read the frames, so just bail now.
+ // TODO: make this switch complete and then panic here.
+ // TODO: or just return nil?
+ return g
+ }
+ for {
+ f := p.readFrame(sp, pc)
+ 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" {
+ // Continue traceback at location where the signal
+ // interrupted normal execution.
+ ctxt := p.proc.ReadPtr(sp.Add(16)) // 3rd arg
+ //ctxt is a *ucontext
+ mctxt := ctxt.Add(5 * 8)
+ // mctxt is a *mcontext
+ sp = p.proc.ReadPtr(mctxt.Add(15 * 8))
+ pc = p.proc.ReadPtr(mctxt.Add(16 * 8))
+ // TODO: totally arch-dependent!
+ } else {
+ sp = f.max
+ pc = core.Address(p.proc.ReadUintptr(sp - 8)) // TODO:amd64 only
+ }
+ if pc == 0 {
+ // TODO: when would this happen?
+ break
+ }
+ if f.f.name == "runtime.systemstack" {
+ // switch over to goroutine stack
+ sched := r.Field("sched")
+ sp = core.Address(sched.Field("sp").Uintptr())
+ pc = core.Address(sched.Field("pc").Uintptr())
+ }
+ }
+ return g
+}
+
+func (p *Process) readFrame(sp, pc core.Address) *Frame {
+ f := p.funcTab.find(pc)
+ if f == nil {
+ panic(fmt.Errorf(" pc not found %x\n", pc))
+ }
+ off := pc.Sub(f.entry)
+ size := f.frameSize.find(off)
+ size += p.proc.PtrSize() // TODO: on amd64, the pushed return address
+
+ frame := &Frame{f: f, pc: pc, min: sp, max: sp.Add(size)}
+
+ // Find live ptrs in locals
+ live := map[core.Address]bool{}
+ if x := int(p.rtConstants["_FUNCDATA_LocalsPointerMaps"]); x < len(f.funcdata) {
+ locals := region{p: p, a: f.funcdata[x], typ: p.findType("runtime.stackmap")}
+ n := locals.Field("n").Int32() // # of bitmaps
+ nbit := locals.Field("nbit").Int32() // # of bits per bitmap
+ idx := f.stackMap.find(off)
+ if idx < 0 {
+ idx = 0
+ }
+ if idx < int64(n) {
+ bits := locals.Field("bytedata").a.Add(int64(nbit+7) / 8 * idx)
+ base := frame.max.Add(-16).Add(-int64(nbit) * p.proc.PtrSize())
+ // TODO: -16 for amd64. Return address and parent's frame pointer
+ for i := int64(0); i < int64(nbit); i++ {
+ if p.proc.ReadUint8(bits.Add(i/8))>>uint(i&7)&1 != 0 {
+ live[base.Add(i*p.proc.PtrSize())] = true
+ }
+ }
+ }
+ }
+ // Same for args
+ if x := int(p.rtConstants["_FUNCDATA_ArgsPointerMaps"]); x < len(f.funcdata) {
+ args := region{p: p, a: f.funcdata[x], typ: p.findType("runtime.stackmap")}
+ n := args.Field("n").Int32() // # of bitmaps
+ nbit := args.Field("nbit").Int32() // # of bits per bitmap
+ idx := f.stackMap.find(off)
+ if idx < 0 {
+ idx = 0
+ }
+ if idx < int64(n) {
+ bits := args.Field("bytedata").a.Add(int64(nbit+7) / 8 * idx)
+ base := frame.max
+ // TODO: add to base for LR archs.
+ for i := int64(0); i < int64(nbit); i++ {
+ if p.proc.ReadUint8(bits.Add(i/8))>>uint(i&7)&1 != 0 {
+ live[base.Add(i*p.proc.PtrSize())] = true
+ }
+ }
+ }
+ }
+ frame.Live = live
+
+ return frame
+}
+
+// A Stats struct is the node of a tree representing the entire memory
+// usage of the Go program. Children of a node break its usage down
+// by category.
+// We maintain the invariant that, if there are children,
+// Size == sum(c.Size for c in Children).
+type Stats struct {
+ Name string
+ Size int64
+ Children []*Stats
+}
+
+func (s *Stats) Child(name string) *Stats {
+ for _, c := range s.Children {
+ if c.Name == name {
+ return c
+ }
+ }
+ return nil
+}
diff --git a/gocore/region.go b/gocore/region.go
new file mode 100644
index 0000000..b0fdc79
--- /dev/null
+++ b/gocore/region.go
@@ -0,0 +1,159 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import "golang.org/x/debug/core"
+
+// A region is a piece of the virtual address space of the inferior.
+// It has an address and a type.
+// Note that it is the type of the thing in the region,
+// not the type of the reference to the region.
+type region struct {
+ p *Process
+ a core.Address
+ typ *Type
+}
+
+// Address returns the address that a region of pointer type points to.
+func (r region) Address() core.Address {
+ if r.typ.Kind != KindPtr {
+ panic("can't ask for the Address of a non-pointer " + r.typ.Name)
+ }
+ return r.p.proc.ReadPtr(r.a)
+}
+
+// Int returns the int value stored in r.
+func (r region) Int() int64 {
+ if r.typ.Kind != KindInt || r.typ.Size != r.p.proc.PtrSize() {
+ panic("not an int: " + r.typ.Name)
+ }
+ return r.p.proc.ReadInt(r.a)
+}
+
+// Uintptr returns the uintptr value stored in r.
+func (r region) Uintptr() uint64 {
+ if r.typ.Kind != KindUint || r.typ.Size != r.p.proc.PtrSize() {
+ panic("not a uintptr: " + r.typ.Name)
+ }
+ return r.p.proc.ReadUintptr(r.a)
+}
+
+// Cast the region to the given type.
+func (r region) Cast(typ string) region {
+ return region{p: r.p, a: r.a, typ: r.p.findType(typ)}
+}
+
+// Deref loads from a pointer. r must contain a pointer.
+func (r region) Deref() region {
+ if r.typ.Kind != KindPtr {
+ panic("can't deref on non-pointer: " + r.typ.Name)
+ }
+ if r.typ.Elem == nil {
+ panic("can't deref unsafe.Pointer")
+ }
+ p := r.p.proc.ReadPtr(r.a)
+ return region{p: r.p, a: p, typ: r.typ.Elem}
+}
+
+// Uint64 returns the uint64 value stored in r.
+// r must have type uint64.
+func (r region) Uint64() uint64 {
+ if r.typ.Kind != KindUint || r.typ.Size != 8 {
+ panic("bad uint64 type " + r.typ.Name)
+ }
+ return r.p.proc.ReadUint64(r.a)
+}
+
+// Uint32 returns the uint32 value stored in r.
+// r must have type uint32.
+func (r region) Uint32() uint32 {
+ if r.typ.Kind != KindUint || r.typ.Size != 4 {
+ panic("bad uint32 type " + r.typ.Name)
+ }
+ return r.p.proc.ReadUint32(r.a)
+}
+
+// Int32 returns the int32 value stored in r.
+// r must have type int32.
+func (r region) Int32() int32 {
+ if r.typ.Kind != KindInt || r.typ.Size != 4 {
+ panic("bad int32 type " + r.typ.Name)
+ }
+ return r.p.proc.ReadInt32(r.a)
+}
+
+// Uint16 returns the uint16 value stored in r.
+// r must have type uint16.
+func (r region) Uint16() uint16 {
+ if r.typ.Kind != KindUint || r.typ.Size != 2 {
+ panic("bad uint16 type " + r.typ.Name)
+ }
+ return r.p.proc.ReadUint16(r.a)
+}
+
+// Uint8 returns the uint8 value stored in r.
+// r must have type uint8.
+func (r region) Uint8() uint8 {
+ if r.typ.Kind != KindUint || r.typ.Size != 1 {
+ panic("bad uint8 type " + r.typ.Name)
+ }
+ return r.p.proc.ReadUint8(r.a)
+}
+
+// String returns the value of the string stored in r.
+func (r region) String() string {
+ if r.typ.Kind != KindString {
+ panic("bad string type " + r.typ.Name)
+ }
+ p := r.p.proc.ReadPtr(r.a)
+ n := r.p.proc.ReadUintptr(r.a.Add(r.p.proc.PtrSize()))
+ b := make([]byte, n)
+ r.p.proc.ReadAt(b, p)
+ return string(b)
+}
+
+// SliceIndex indexes a slice (a[n]). r must contain a slice.
+// n must be in bounds for the slice.
+func (r region) SliceIndex(n int64) region {
+ if r.typ.Kind != KindSlice {
+ panic("can't index a non-slice")
+ }
+ p := r.p.proc.ReadPtr(r.a)
+ return region{p: r.p, a: p.Add(n * r.typ.Elem.Size), typ: r.typ.Elem}
+}
+
+// SlicePtr returns the pointer inside a slice. r must contain a slice.
+func (r region) SlicePtr() region {
+ if r.typ.Kind != KindSlice {
+ panic("can't Ptr a non-slice")
+ }
+ return region{p: r.p, a: r.a, typ: &Type{Name: "*" + r.typ.Name[2:], Size: r.p.proc.PtrSize(), Kind: KindPtr, Elem: r.typ.Elem}}
+}
+
+// SliceLen returns the length of a slice. r must contain a slice.
+func (r region) SliceLen() int64 {
+ if r.typ.Kind != KindSlice {
+ panic("can't len a non-slice")
+ }
+ return r.p.proc.ReadInt(r.a.Add(r.p.proc.PtrSize()))
+}
+
+// SliceCap returns the capacity of a slice. r must contain a slice.
+func (r region) SliceCap() int64 {
+ if r.typ.Kind != KindSlice {
+ panic("can't cap a non-slice")
+ }
+ return r.p.proc.ReadInt(r.a.Add(2 * r.p.proc.PtrSize()))
+}
+
+// Field returns the part of r which contains the field f.
+// r must contain a struct, and f must be one of its fields.
+func (r region) Field(f string) region {
+ finfo := r.typ.field(f)
+ if finfo == nil {
+ panic("can't find field " + r.typ.Name + "." + f)
+ }
+ return region{p: r.p, a: r.a.Add(finfo.Off), typ: finfo.Type}
+}
diff --git a/gocore/reverse.go b/gocore/reverse.go
new file mode 100644
index 0000000..6cda39d
--- /dev/null
+++ b/gocore/reverse.go
@@ -0,0 +1,112 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "sort"
+
+ "golang.org/x/debug/core"
+)
+
+func (p *Process) reverseEdges() {
+ // First, count the number of edges into each object.
+ // This allows for efficient packing of the reverse edge storage.
+ cnt := make([]int64, p.nObj+1)
+ p.ForEachObject(func(x Object) bool {
+ p.ForEachPtr(x, func(_ int64, y Object, _ int64) bool {
+ idx, _ := p.findObjectIndex(p.Addr(y))
+ cnt[idx]++
+ return true
+ })
+ return true
+ })
+ p.ForEachRoot(func(r *Root) bool {
+ p.ForEachRootPtr(r, func(_ int64, y Object, _ int64) bool {
+ idx, _ := p.findObjectIndex(p.Addr(y))
+ cnt[idx]++
+ return true
+ })
+ return true
+ })
+
+ // Compute cumulative count of all incoming edges up to and including each object.
+ var n int64
+ for idx, c := range cnt {
+ n += c
+ cnt[idx] = n
+ }
+
+ // Allocate all the storage for the reverse edges.
+ p.redge = make([]core.Address, n)
+
+ // Add edges to the lists.
+ p.ForEachObject(func(x Object) bool {
+ p.ForEachPtr(x, func(i int64, y Object, _ int64) bool {
+ idx, _ := p.findObjectIndex(p.Addr(y))
+ e := cnt[idx]
+ e--
+ cnt[idx] = e
+ p.redge[e] = p.Addr(x).Add(i)
+ return true
+ })
+ return true
+ })
+ p.ForEachRoot(func(r *Root) bool {
+ p.ForEachRootPtr(r, func(i int64, y Object, _ int64) bool {
+ idx, _ := p.findObjectIndex(p.Addr(y))
+ e := cnt[idx]
+ e--
+ cnt[idx] = e
+ p.redge[e] = r.Addr.Add(i)
+ return true
+ })
+ return true
+ })
+ // At this point, cnt contains the cumulative count of all edges up to
+ // but *not* including each object.
+ p.ridx = cnt
+
+ // Make root index.
+ p.ForEachRoot(func(r *Root) bool {
+ p.rootIdx = append(p.rootIdx, r)
+ return true
+ })
+ sort.Slice(p.rootIdx, func(i, j int) bool { return p.rootIdx[i].Addr < p.rootIdx[j].Addr })
+}
+
+// ForEachReversePtr calls fn for all pointers it finds pointing to y.
+// It calls fn with:
+// the object or root which points to y (exactly one will be non-nil)
+// the offset i in that object or root where the pointer appears.
+// the offset j in y where the pointer points.
+// If fn returns false, ForEachReversePtr returns immediately.
+// FlagReverse must have been passed to Core when p was constructed.
+func (p *Process) ForEachReversePtr(y Object, fn func(x Object, r *Root, i, j int64) bool) {
+ idx, _ := p.findObjectIndex(p.Addr(y))
+ for _, a := range p.redge[p.ridx[idx]:p.ridx[idx+1]] {
+ // Read pointer, compute offset in y.
+ ptr := p.proc.ReadPtr(a)
+ j := ptr.Sub(p.Addr(y))
+
+ // Find source of pointer.
+ x, i := p.FindObject(a)
+ if x != 0 {
+ // Source is an object.
+ if !fn(x, nil, i, j) {
+ return
+ }
+ continue
+ }
+ // Source is a root.
+ k := sort.Search(len(p.rootIdx), func(k int) bool {
+ r := p.rootIdx[k]
+ return a < r.Addr.Add(r.Type.Size)
+ })
+ r := p.rootIdx[k]
+ if !fn(0, r, a.Sub(r.Addr), j) {
+ return
+ }
+ }
+}
diff --git a/gocore/root.go b/gocore/root.go
new file mode 100644
index 0000000..89a1ce8
--- /dev/null
+++ b/gocore/root.go
@@ -0,0 +1,22 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "golang.org/x/debug/core"
+)
+
+// A Root is an area of memory that might have pointers into the Go heap.
+type Root struct {
+ Name string
+ Addr core.Address
+ Type *Type // always non-nil
+ // Frame, if non-nil, points to the frame in which this root lives.
+ // Roots with non-nil Frame fields refer to local variables on a stack.
+ // A stack root might be a large type, with some of its fields live and
+ // others dead. Consult Frame.Live to find out which pointers in a stack
+ // root are live.
+ Frame *Frame
+}
diff --git a/gocore/testdata/README b/gocore/testdata/README
new file mode 100644
index 0000000..11d9761
--- /dev/null
+++ b/gocore/testdata/README
@@ -0,0 +1,14 @@
+This directory contains a simple core file for use in testing.
+
+It was generated by running the following program with go1.9.0.
+
+package main
+
+func main() {
+ _ = *(*int)(nil)
+}
+
+The core file includes the executable by reference using an absolute
+path. The executable was at /tmp/test, so that path is reproduced
+here so the core dump reader can find the executable using this
+testdata directory as the base directory.
diff --git a/gocore/testdata/core b/gocore/testdata/core
new file mode 100644
index 0000000..55318de
--- /dev/null
+++ b/gocore/testdata/core
Binary files differ
diff --git a/gocore/testdata/tmp/test b/gocore/testdata/tmp/test
new file mode 100755
index 0000000..f3353b2
--- /dev/null
+++ b/gocore/testdata/tmp/test
Binary files differ
diff --git a/gocore/type.go b/gocore/type.go
new file mode 100644
index 0000000..17c9c57
--- /dev/null
+++ b/gocore/type.go
@@ -0,0 +1,609 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocore
+
+import (
+ "fmt"
+ "strings"
+
+ "golang.org/x/debug/core"
+)
+
+// A Type is the representation of the type of a Go object.
+// Types are not necessarily canonical.
+// Names are opaque; do not depend on the format of the returned name.
+type Type struct {
+ Name string
+ Size int64
+ Kind Kind
+
+ // Fields only valid for a subset of kinds.
+ Count int64 // for kind == KindArray
+ Elem *Type // for kind == Kind{Ptr,Array,Slice,String}. nil for unsafe.Pointer. Always uint8 for KindString.
+ Fields []Field // for kind == KindStruct
+}
+
+type Kind uint8
+
+const (
+ KindNone Kind = iota
+ KindBool
+ KindInt
+ KindUint
+ KindFloat
+ KindComplex
+ KindArray
+ KindPtr // includes chan, map, unsafe.Pointer
+ KindIface
+ KindEface
+ KindSlice
+ KindString
+ KindStruct
+ KindFunc
+)
+
+func (k Kind) String() string {
+ return [...]string{
+ "KindNone",
+ "KindBool",
+ "KindInt",
+ "KindUint",
+ "KindFloat",
+ "KindComplex",
+ "KindArray",
+ "KindPtr",
+ "KindIface",
+ "KindEface",
+ "KindSlice",
+ "KindString",
+ "KindStruct",
+ "KindFunc",
+ }[k]
+}
+
+// A Field represents a single field of a struct type.
+type Field struct {
+ Name string
+ Off int64
+ Type *Type
+}
+
+func (t *Type) String() string {
+ return t.Name
+}
+
+func (t *Type) field(name string) *Field {
+ if t.Kind != KindStruct {
+ panic("asking for field of non-struct")
+ }
+ for i := range t.Fields {
+ f := &t.Fields[i]
+ if f.Name == name {
+ return f
+ }
+ }
+ return nil
+}
+
+// Convert the address of a runtime._type to a *Type.
+// Guaranteed to return a non-nil *Type.
+func (p *Process) runtimeType2Type(a core.Address) *Type {
+ if t := p.runtimeMap[a]; t != nil {
+ return t
+ }
+
+ // Read runtime._type.size
+ r := region{p: p, a: a, typ: p.findType("runtime._type")}
+ size := int64(r.Field("size").Uintptr())
+
+ // Find module this type is in.
+ var m *module
+ for _, x := range p.modules {
+ if x.types <= a && a < x.etypes {
+ m = x
+ break
+ }
+ }
+
+ // Read information out of the runtime._type.
+ var name string
+ if m != nil {
+ x := m.types.Add(int64(r.Field("str").Int32()))
+ n := uint16(p.proc.ReadUint8(x.Add(1)))<<8 + uint16(p.proc.ReadUint8(x.Add(2)))
+ b := make([]byte, n)
+ p.proc.ReadAt(b, x.Add(3))
+ name = string(b)
+ if r.Field("tflag").Uint8()&uint8(p.rtConstants["tflagExtraStar"]) != 0 {
+ name = name[1:]
+ }
+ } else {
+ // A reflect-generated type.
+ // TODO: The actual name is in the runtime.reflectOffs map.
+ // Too hard to look things up in maps here, just allocate a placeholder for now.
+ name = fmt.Sprintf("reflect.generated%x", a)
+ }
+
+ // Read ptr/nonptr bits
+ ptrSize := p.proc.PtrSize()
+ nptrs := int64(r.Field("ptrdata").Uintptr()) / ptrSize
+ var ptrs []int64
+ if r.Field("kind").Uint8()&uint8(p.rtConstants["kindGCProg"]) == 0 {
+ gcdata := r.Field("gcdata").Address()
+ for i := int64(0); i < nptrs; i++ {
+ if p.proc.ReadUint8(gcdata.Add(i/8))>>uint(i%8)&1 != 0 {
+ ptrs = append(ptrs, i*ptrSize)
+ }
+ }
+ } else {
+ // TODO: run GC program to get ptr indexes
+ }
+
+ // Find a Type that matches this type.
+ // (The matched type will be one constructed from DWARF info.)
+ // It must match name, size, and pointer bits.
+ var candidates []*Type
+ for _, t := range p.runtimeNameMap[name] {
+ if size == t.Size && equal(ptrs, t.ptrs()) {
+ candidates = append(candidates, t)
+ }
+ }
+ var t *Type
+ if len(candidates) > 0 {
+ // If a runtime type matches more than one DWARF type,
+ // pick one arbitrarily.
+ // This looks mostly harmless. DWARF has some redundant entries.
+ // For example, [32]uint8 appears twice.
+ // TODO: investigate the reason for this duplication.
+ t = candidates[0]
+ } else {
+ // There's no corresponding DWARF type. Make our own.
+ t = &Type{Name: name, Size: size, Kind: KindStruct}
+ n := t.Size / ptrSize
+
+ // Types to use for ptr/nonptr fields of runtime types which
+ // have no corresponding DWARF type.
+ ptr := p.findType("unsafe.Pointer")
+ nonptr := p.findType("uintptr")
+ if ptr == nil || nonptr == nil {
+ panic("ptr / nonptr standins missing")
+ }
+
+ for i := int64(0); i < n; i++ {
+ typ := nonptr
+ if len(ptrs) > 0 && ptrs[0] == i*ptrSize {
+ typ = ptr
+ ptrs = ptrs[1:]
+ }
+ t.Fields = append(t.Fields, Field{
+ Name: fmt.Sprintf("f%d", i),
+ Off: i * ptrSize,
+ Type: typ,
+ })
+
+ }
+ if t.Size%ptrSize != 0 {
+ // TODO: tail of <ptrSize data.
+ }
+ }
+ // Memoize.
+ p.runtimeMap[a] = t
+
+ return t
+}
+
+// ptrs returns a sorted list of pointer offsets in t.
+func (t *Type) ptrs() []int64 {
+ return t.ptrs1(nil, 0)
+}
+func (t *Type) ptrs1(s []int64, off int64) []int64 {
+ switch t.Kind {
+ case KindPtr, KindFunc, KindSlice, KindString:
+ s = append(s, off)
+ case KindIface, KindEface:
+ s = append(s, off, off+t.Size/2)
+ case KindArray:
+ if t.Count > 10000 {
+ // Be careful about really large types like [1e9]*byte.
+ // To process such a type we'd make a huge ptrs list.
+ // The ptrs list here is only used for matching
+ // a runtime type with a dwarf type, and for making
+ // fields for types with no dwarf type.
+ // Both uses can fail with no terrible repercussions.
+ // We still will scan the whole object during markObjects, for example.
+ // TODO: make this more robust somehow.
+ break
+ }
+ for i := int64(0); i < t.Count; i++ {
+ s = t.Elem.ptrs1(s, off)
+ off += t.Elem.Size
+ }
+ case KindStruct:
+ for _, f := range t.Fields {
+ s = f.Type.ptrs1(s, off+f.Off)
+ }
+ default:
+ // no pointers
+ }
+ return s
+}
+
+func equal(a, b []int64) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, x := range a {
+ if x != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// A typeInfo contains information about the type of an object.
+// A slice of these hold the results of typing the heap.
+type typeInfo struct {
+ // This object has an effective type of [r]t.
+ // Parts of the object beyond the first r*t.Size bytes have unknown type.
+ // If t == nil, the type is unknown. (TODO: provide access to ptr/nonptr bits in this case.)
+ t *Type
+ r int64
+}
+
+// A typeChunk records type information for a portion of an object.
+// Similar to a typeInfo, but it has an offset so it can be used for interior typings.
+type typeChunk struct {
+ off int64
+ t *Type
+ r int64
+}
+
+func (c typeChunk) min() int64 {
+ return c.off
+}
+func (c typeChunk) max() int64 {
+ return c.off + c.r*c.t.Size
+}
+func (c typeChunk) size() int64 {
+ return c.r * c.t.Size
+}
+func (c typeChunk) matchingAlignment(d typeChunk) bool {
+ if c.t != d.t {
+ panic("can't check alignment of differently typed chunks")
+ }
+ return (c.off-d.off)%c.t.Size == 0
+}
+
+func (c typeChunk) merge(d typeChunk) typeChunk {
+ t := c.t
+ if t != d.t {
+ panic("can't merge chunks with different types")
+ }
+ size := t.Size
+ if (c.off-d.off)%size != 0 {
+ panic("can't merge poorly aligned chunks")
+ }
+ min := c.min()
+ max := c.max()
+ if max < d.min() || min > d.max() {
+ panic("can't merge chunks which don't overlap or abut")
+ }
+ if x := d.min(); x < min {
+ min = x
+ }
+ if x := d.max(); x > max {
+ max = x
+ }
+ return typeChunk{off: min, t: t, r: (max - min) / size}
+}
+func (c typeChunk) String() string {
+ return fmt.Sprintf("%x[%d]%s", c.off, c.r, c.t)
+}
+
+// typeHeap tries to label all the heap objects with types.
+func (p *Process) typeHeap() {
+ // Type info for the start of each object. a.k.a. "0 offset" typings.
+ p.types = make([]typeInfo, p.nObj)
+
+ // Type info for the interior of objects, a.k.a. ">0 offset" typings.
+ // Type information is arranged in chunks. Chunks are stored in an
+ // arbitrary order, and are guaranteed to not overlap. If types are
+ // equal, chunks are also guaranteed not to abut.
+ // Interior typings are kept separate because they hopefully are rare.
+ // TODO: They aren't really that rare. On some large heaps I tried
+ // ~50% of objects have an interior pointer into them.
+ // Keyed by object index.
+ interior := map[int][]typeChunk{}
+
+ // Typings we know about but haven't scanned yet.
+ type workRecord struct {
+ a core.Address
+ t *Type
+ r int64
+ }
+ var work []workRecord
+
+ // add records the fact that we know the object at address a has
+ // r copies of type t.
+ add := func(a core.Address, t *Type, r int64) {
+ if a == 0 { // nil pointer
+ return
+ }
+ i, off := p.findObjectIndex(a)
+ if i < 0 { // pointer doesn't point to an object in the Go heap
+ return
+ }
+ if off == 0 {
+ // We have a 0-offset typing. Replace existing 0-offset typing
+ // if the new one is larger.
+ ot := p.types[i].t
+ or := p.types[i].r
+ if ot == nil || r*t.Size > or*ot.Size {
+ if t == ot {
+ // Scan just the new section.
+ work = append(work, workRecord{
+ a: a.Add(or * ot.Size),
+ t: t,
+ r: r - or,
+ })
+ } else {
+ // Rescan the whole typing using the updated type.
+ work = append(work, workRecord{
+ a: a,
+ t: t,
+ r: r,
+ })
+ }
+ p.types[i].t = t
+ p.types[i].r = r
+ }
+ return
+ }
+
+ // Add an interior typing to object #i.
+ c := typeChunk{off: off, t: t, r: r}
+
+ // Merge the given typing into the chunks we already know.
+ // TODO: this could be O(n) per insert if there are lots of internal pointers.
+ chunks := interior[i]
+ newchunks := chunks[:0]
+ addWork := true
+ for _, d := range chunks {
+ if c.max() <= d.min() || c.min() >= d.max() {
+ // c does not overlap with d.
+ if c.t == d.t && (c.max() == d.min() || c.min() == d.max()) {
+ // c and d abut and share the same base type. Merge them.
+ c = c.merge(d)
+ continue
+ }
+ // Keep existing chunk d.
+ // Overwrites chunks slice, but we're only merging chunks so it
+ // can't overwrite to-be-processed elements.
+ newchunks = append(newchunks, d)
+ continue
+ }
+ // There is some overlap. There are a few possibilities:
+ // 1) One is completely contained in the other.
+ // 2) Both are slices of a larger underlying array.
+ // 3) Some unsafe trickery has happened. Non-containing overlap
+ // can only happen in safe Go via case 2.
+ if c.min() >= d.min() && c.max() <= d.max() {
+ // 1a: c is contained within the existing chunk d.
+ // Note that there can be a type mismatch between c and d,
+ // but we don't care. We use the larger chunk regardless.
+ c = d
+ addWork = false // We've already scanned all of c.
+ continue
+ }
+ if d.min() >= c.min() && d.max() <= c.max() {
+ // 1b: existing chunk d is completely covered by c.
+ continue
+ }
+ if c.t == d.t && c.matchingAlignment(d) {
+ // Union two regions of the same base type. Case 2 above.
+ c = c.merge(d)
+ continue
+ }
+ if c.size() < d.size() {
+ // Keep the larger of the two chunks.
+ c = d
+ addWork = false
+ }
+ }
+ // Add new chunk to list of chunks for object.
+ newchunks = append(newchunks, c)
+ interior[i] = newchunks
+ // Also arrange to scan the new chunk. Note that if we merged
+ // with an existing chunk (or chunks), those will get rescanned.
+ // Duplicate work, but that's ok. TODO: but could be expensive.
+ if addWork {
+ work = append(work, workRecord{
+ a: a.Add(c.off - off),
+ t: c.t,
+ r: c.r,
+ })
+ }
+ }
+
+ // Get typings starting at roots.
+ fr := &frameReader{p: p}
+ p.ForEachRoot(func(r *Root) bool {
+ 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)
+ }
+ return true
+ })
+
+ // Propagate typings through the heap.
+ for len(work) > 0 {
+ c := work[len(work)-1]
+ work = work[:len(work)-1]
+ for i := int64(0); i < c.r; i++ {
+ p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add)
+ }
+ }
+
+ // Merge any interior typings with the 0-offset typing.
+ for i, chunks := range interior {
+ t := p.types[i].t
+ r := p.types[i].r
+ if t == nil {
+ continue // We have no type info at offset 0.
+ }
+ for _, c := range chunks {
+ if c.max() <= r*t.Size {
+ // c is completely contained in the 0-offset typing. Ignore it.
+ continue
+ }
+ if c.min() <= r*t.Size {
+ // Typings overlap or abut. Extend if we can.
+ if c.t == t && c.min()%t.Size == 0 {
+ r = c.max() / t.Size
+ p.types[i].r = r
+ }
+ continue
+ }
+ // Note: at this point we throw away any interior typings that weren't
+ // merged with the 0-offset typing. TODO: make more use of this info.
+ }
+ }
+}
+
+type reader interface {
+ ReadPtr(core.Address) core.Address
+ ReadInt(core.Address) int64
+}
+
+// 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
+}
+
+func (fr *frameReader) ReadPtr(a core.Address) core.Address {
+ if !fr.live[a] {
+ return 0
+ }
+ return fr.p.proc.ReadPtr(a)
+}
+func (fr *frameReader) ReadInt(a core.Address) int64 {
+ return fr.p.proc.ReadInt(a)
+}
+
+// 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)) {
+ ptrSize := p.proc.PtrSize()
+
+ switch t.Kind {
+ case KindBool, KindInt, KindUint, KindFloat, KindComplex:
+ // Nothing to do
+ case KindEface, KindIface:
+ // interface. Use the type word to determine the type
+ // of the pointed-to object.
+ typ := r.ReadPtr(a)
+ if typ == 0 { // nil interface
+ return
+ }
+ ptr := r.ReadPtr(a.Add(ptrSize))
+ if t.Kind == KindIface {
+ typ = p.proc.ReadPtr(typ.Add(p.findType("runtime.itab").field("_type").Off))
+ }
+ // TODO: for KindEface, type the typ pointer. It might point to the heap
+ // if the type was allocated with reflect.
+ dt := p.runtimeType2Type(typ)
+ typr := region{p: p, a: typ, typ: p.findType("runtime._type")}
+ if typr.Field("kind").Uint8()&uint8(p.rtConstants["kindDirectIface"]) != 0 {
+ // Find the base type of the pointer held in the interface.
+ findptr:
+ if dt.Kind == KindArray {
+ dt = dt.Elem
+ goto findptr
+ }
+ if dt.Kind == KindStruct {
+ for _, f := range dt.Fields {
+ if f.Type.Size != 0 {
+ dt = f.Type
+ goto findptr
+ }
+ }
+ }
+ if dt.Kind != KindPtr {
+ panic(fmt.Sprintf("direct type isn't a pointer %s", dt.Kind))
+ }
+ dt = dt.Elem
+ }
+ add(ptr, dt, 1)
+ case KindString:
+ ptr := r.ReadPtr(a)
+ len := r.ReadInt(a.Add(ptrSize))
+ add(ptr, t.Elem, len)
+ case KindSlice:
+ ptr := r.ReadPtr(a)
+ cap := r.ReadInt(a.Add(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)
+ }
+ case KindFunc:
+ // The referent is a closure. We don't know much about the
+ // type of the referent. Its first entry is a code pointer.
+ // 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)
+ if closure == 0 {
+ break
+ }
+ pc := p.proc.ReadPtr(closure)
+ f := p.funcTab.find(pc)
+ if f == nil {
+ panic(fmt.Sprintf("can't find func for closure pc %x", pc))
+ }
+ ft := f.closure
+ if ft == nil {
+ ft = &Type{Name: "closure for " + f.name, Size: ptrSize, Kind: KindPtr}
+ // For now, treat a closure like an unsafe.Pointer.
+ // TODO: better value for size?
+ f.closure = ft
+ }
+ p.typeObject(closure, ft, r, add)
+ case KindArray:
+ n := t.Elem.Size
+ for i := int64(0); i < t.Count; i++ {
+ p.typeObject(a.Add(i*n), t.Elem, r, add)
+ }
+ case KindStruct:
+ if strings.HasPrefix(t.Name, "hash<") {
+ // Special case - maps have a pointer to the first bucket
+ // but it really types all the buckets (like a slice would).
+ var bPtr core.Address
+ var bTyp *Type
+ var n int64
+ for _, f := range t.Fields {
+ if f.Name == "buckets" {
+ bPtr = p.proc.ReadPtr(a.Add(f.Off))
+ bTyp = f.Type.Elem
+ }
+ if f.Name == "B" {
+ n = int64(1) << p.proc.ReadUint8(a.Add(f.Off))
+ }
+ }
+ add(bPtr, bTyp, n)
+ // TODO: also oldbuckets
+ }
+ // TODO: also special case for channels?
+ for _, f := range t.Fields {
+ p.typeObject(a.Add(f.Off), f.Type, r, add)
+ }
+ default:
+ panic(fmt.Sprintf("unknown type kind %s\n", t.Kind))
+ }
+}