gocore: bugfix to Go 1.17 cores, Go 1.16 cores and Go 1.11+ cores

This CL fixes several bugs in `gocore`, the library that `viewcore` uses to
parse a core dump, as well as adds general updates for the more recent Go
versions.

First, we fix _func types for Go 1.16. In 1.16, the types of a few fields in
_func were changed. This commit makes the corresponding change to gocore. This
is the commit the changed the field types: CL 248332. Additionally, we
updated the func parsing to deal with the new split pclntab.

Previously, the code that deserialized the heapArena.bitmap field to
check whether addresses contained pointers or not was incorrect. It was
treating the entire bitmap as 1 bit per pointer.

However, that is not what the bitmap represents. Each byte in the bitmap
is actually split in half. The high bits contain the 4 scan bits. And
the low bits contain the 4 pointer bits.

See
https://github.com/golang/go/blob/3b304ce7fe35b9d1e8cf0b0518ed2550c361a010/src/runtime/mbitmap.go#L17-L35
for a more detailed description.

This commit corrects the issue and allows gocore to correctly traverse
the object graph.

Finally, for Go 1.17, we stop subtracting the heap unallocated space
represented by curArena from the total size of the heap. This reflects the
change that was made in CL 270537.

Also, a few incidental fixes:
- Stop thrashing the viewcore cache on the first command in interactive mode.
- Synthesize types for 0-size arrays in structs

Change-Id: Ia1636932d7c6c59bbd640f6b5b00b221369fed44
GitHub-Last-Rev: c66ea5a6d246f1f5f089a49824a91d34ebab5279
GitHub-Pull-Request: golang/debug#7
Reviewed-on: https://go-review.googlesource.com/c/debug/+/321736
Trust: Cherry Mui <cherryyz@google.com>
Trust: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/viewcore/main.go b/cmd/viewcore/main.go
index 26e2785..c5842ee 100644
--- a/cmd/viewcore/main.go
+++ b/cmd/viewcore/main.go
@@ -297,14 +297,14 @@
 		cmd.Usage()
 		return
 	}
+	// Interactive mode.
+	cfg.interactive = true
+
 	p, _, err := readCore()
 	if err != nil {
 		exitf("%v\n", err)
 	}
 
-	// Interactive mode.
-	cfg.interactive = true
-
 	// Create a dummy root to run in shell.
 	root := &cobra.Command{}
 	// Make all subcommands of viewcore available in the shell.
diff --git a/internal/gocore/dwarf.go b/internal/gocore/dwarf.go
index d185d34..c5cc173 100644
--- a/internal/gocore/dwarf.go
+++ b/internal/gocore/dwarf.go
@@ -7,6 +7,7 @@
 import (
 	"debug/dwarf"
 	"fmt"
+	"reflect"
 	"regexp"
 	"sort"
 	"strings"
@@ -58,6 +59,23 @@
 			t.Kind = KindStruct
 			for _, f := range x.Field {
 				fType := p.dwarfMap[f.Type]
+				if fType == nil {
+					// Weird case: arrays of size 0 in structs, like
+					// Sysinfo_t.X_f. Synthesize a type so things later don't
+					// get sad.
+					if arr, ok := f.Type.(*dwarf.ArrayType); ok && arr.Count == 0 {
+						fType = &Type{
+							Name:  f.Type.String(),
+							Kind:  KindArray,
+							Count: arr.Count,
+							Elem:  p.dwarfMap[arr.Type],
+						}
+					} else {
+						panic(fmt.Sprintf(
+							"found a nil ftype for field %s.%s, type %s (%s) on ",
+							x.StructName, f.Name, f.Type, reflect.TypeOf(f.Type)))
+					}
+				}
 
 				// Work around issue 21094. There's no guarantee that the
 				// pointer type is in the DWARF, so just invent a Type.
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go
index 0b33ca6..d233296 100644
--- a/internal/gocore/gocore_test.go
+++ b/internal/gocore/gocore_test.go
@@ -272,4 +272,6 @@
 	loadExampleVersion(t, "1.13.zip")
 	loadExampleVersion(t, "1.13.3.zip")
 	loadExampleVersion(t, "1.14.zip")
+	loadExampleVersion(t, "1.16.zip")
+	loadExampleVersion(t, "1.17.zip")
 }
diff --git a/internal/gocore/module.go b/internal/gocore/module.go
index 2f5e632..aacbd0c 100644
--- a/internal/gocore/module.go
+++ b/internal/gocore/module.go
@@ -14,6 +14,7 @@
 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
+	p             *Process     // The parent process of this module.
 }
 
 func (p *Process) readModules() {
@@ -29,12 +30,18 @@
 }
 
 func (p *Process) readModule(r region) *module {
-	m := &module{r: r}
+	m := &module{p: p, 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")
+	var pctab, funcnametab region
+	if p.minorVersion >= 16 {
+		// In 1.16, pclntable was split up into pctab and funcnametab.
+		pctab = r.Field("pctab")
+		funcnametab = r.Field("funcnametab")
+	}
 	ftab := r.Field("ftab")
 	n := ftab.SliceLen() - 1 // last slot is a dummy, just holds entry
 	for i := int64(0); i < n; i++ {
@@ -42,7 +49,12 @@
 		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)
+		var f *Func
+		if p.minorVersion >= 16 {
+			f = m.readFunc(fr, pctab, funcnametab)
+		} else {
+			f = m.readFunc(fr, pcln, pcln)
+		}
 		if f.entry != min {
 			panic(fmt.Errorf("entry %x and min %x don't match for %s", f.entry, min, f.name))
 		}
@@ -55,34 +67,52 @@
 // 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 {
+func (m *module) readFunc(r region, pctab region, funcnametab 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)
+	f.name = r.p.proc.ReadCString(funcnametab.SliceIndex(int64(r.Field("nameoff").Int32())).a)
+	pcsp := r.Field("pcsp")
+	var pcspIdx int64
+	if m.p.minorVersion >= 16 {
+		// In 1.16, pcsp changed to be a uint32 from an int32.
+		pcspIdx = int64(pcsp.Uint32())
+	} else {
+		pcspIdx = int64(pcsp.Int32())
+	}
+	f.frameSize.read(r.p.proc, pctab.SliceIndex(pcspIdx).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++ {
+	// In 1.16, npcdata changed to be a uint32 from an int32.
+	npcdata := r.Field("npcdata")
+	var n uint32
+	if m.p.minorVersion >= 16 {
+		// In 1.16, pcsp changed to be a uint32 from an int32.
+		n = uint32(npcdata.Uint32())
+	} else {
+		n = uint32(npcdata.Int32())
+	}
+	nfd := r.Field("nfuncdata")
+	a := nfd.a.Add(nfd.typ.Size)
+
+	for i := uint32(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())
 
-	if nfd := r.Field("nfuncdata"); nfd.typ.Size == 1 { // go 1.12 and beyond, this is a uint8
-		n = int32(nfd.Uint8())
+	if nfd.typ.Size == 1 { // go 1.12 and beyond, this is a uint8
+		n = uint32(nfd.Uint8())
 	} else { // go 1.11 and earlier, this is an int32
-		n = nfd.Int32()
+		n = uint32(nfd.Int32())
 	}
-	for i := int32(0); i < n; i++ {
+	for i := uint32(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)
+		f.stackMap.read(r.p.proc, pctab.SliceIndex(int64(f.pcdata[stackmap])).a)
 	} else {
 		f.stackMap.setEmpty()
 	}
diff --git a/internal/gocore/process.go b/internal/gocore/process.go
index 25d083b..c46a731 100644
--- a/internal/gocore/process.go
+++ b/internal/gocore/process.go
@@ -8,6 +8,7 @@
 	"debug/dwarf"
 	"fmt"
 	"math/bits"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -54,6 +55,7 @@
 	stats *Stats
 
 	buildVersion string
+	minorVersion int
 
 	globals []*Root
 
@@ -153,6 +155,15 @@
 
 	// Read all the data that depend on runtime globals.
 	p.buildVersion = p.rtGlobals["buildVersion"].String()
+	versionComponents := strings.Split(p.buildVersion, ".")
+	if len(versionComponents) < 2 {
+		panic("malformed version " + p.buildVersion)
+	}
+	p.minorVersion, err = strconv.Atoi(versionComponents[1])
+	if err != nil {
+		panic("malformed version " + p.buildVersion)
+	}
+
 	p.readModules()
 	p.readHeap()
 	p.readGs()
@@ -258,10 +269,16 @@
 				// Copy out ptr/nonptr bits
 				n := bitmap.ArrayLen()
 				for i := int64(0); i < n; i++ {
+					// The nth byte is composed of 4 object bits and 4 live/dead
+					// bits. We ignore the 4 live/dead bits, which are on the
+					// high order side of the byte.
+					//
+					// See mbitmap.go for more information on the format of
+					// the bitmap field of heapArena.
 					m := bitmap.ArrayIndex(i).Uint8()
-					for j := int64(0); j < 8; j++ {
+					for j := int64(0); j < 4; j++ {
 						if m>>uint(j)&1 != 0 {
-							p.setHeapPtr(min.Add((i*8 + j) * ptrSize))
+							p.setHeapPtr(min.Add((i*4 + j) * ptrSize))
 						}
 					}
 				}
@@ -316,7 +333,10 @@
 			panic("weird mapping " + m.Perm().String())
 		}
 	}
-	if mheap.HasField("curArena") { // go1.13.3 and up
+	if p.minorVersion < 17 && mheap.HasField("curArena") {
+		// go1.13.3 and up has curArena.
+		// In Go 1.17, we ... don't need to do this any longer. See patch
+		// bd6aeca9686d5e672ffda1ea0cfeac7a3e7a20a4
 		// Subtract from the heap unallocated space
 		// in the current arena.
 		ca := mheap.Field("curArena")
diff --git a/internal/gocore/testdata/1.16.zip b/internal/gocore/testdata/1.16.zip
new file mode 100644
index 0000000..05fb11a
--- /dev/null
+++ b/internal/gocore/testdata/1.16.zip
Binary files differ
diff --git a/internal/gocore/testdata/1.17.zip b/internal/gocore/testdata/1.17.zip
new file mode 100644
index 0000000..f96ee79
--- /dev/null
+++ b/internal/gocore/testdata/1.17.zip
Binary files differ
diff --git a/internal/gocore/type.go b/internal/gocore/type.go
index 4f564fc..4d1b8a5 100644
--- a/internal/gocore/type.go
+++ b/internal/gocore/type.go
@@ -466,6 +466,11 @@
 		for len(work) > 0 {
 			c := work[len(work)-1]
 			work = work[:len(work)-1]
+			switch c.t.Kind {
+			case KindBool, KindInt, KindUint, KindFloat, KindComplex:
+				// Don't do O(n) function calls for big primitive slices
+				continue
+			}
 			for i := int64(0); i < c.r; i++ {
 				p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add)
 			}