Skip to content

Commit 0bd8c7d

Browse files
jordanlewisdominikh
authored andcommitted
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: c66ea5a GitHub-Pull-Request: #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>
1 parent c6cfd1b commit 0bd8c7d

File tree

8 files changed

+94
-19
lines changed

8 files changed

+94
-19
lines changed

cmd/viewcore/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,14 +297,14 @@ func runRoot(cmd *cobra.Command, args []string) {
297297
cmd.Usage()
298298
return
299299
}
300+
// Interactive mode.
301+
cfg.interactive = true
302+
300303
p, _, err := readCore()
301304
if err != nil {
302305
exitf("%v\n", err)
303306
}
304307

305-
// Interactive mode.
306-
cfg.interactive = true
307-
308308
// Create a dummy root to run in shell.
309309
root := &cobra.Command{}
310310
// Make all subcommands of viewcore available in the shell.

internal/gocore/dwarf.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package gocore
77
import (
88
"debug/dwarf"
99
"fmt"
10+
"reflect"
1011
"regexp"
1112
"sort"
1213
"strings"
@@ -58,6 +59,23 @@ func (p *Process) readDWARFTypes() {
5859
t.Kind = KindStruct
5960
for _, f := range x.Field {
6061
fType := p.dwarfMap[f.Type]
62+
if fType == nil {
63+
// Weird case: arrays of size 0 in structs, like
64+
// Sysinfo_t.X_f. Synthesize a type so things later don't
65+
// get sad.
66+
if arr, ok := f.Type.(*dwarf.ArrayType); ok && arr.Count == 0 {
67+
fType = &Type{
68+
Name: f.Type.String(),
69+
Kind: KindArray,
70+
Count: arr.Count,
71+
Elem: p.dwarfMap[arr.Type],
72+
}
73+
} else {
74+
panic(fmt.Sprintf(
75+
"found a nil ftype for field %s.%s, type %s (%s) on ",
76+
x.StructName, f.Name, f.Type, reflect.TypeOf(f.Type)))
77+
}
78+
}
6179

6280
// Work around issue 21094. There's no guarantee that the
6381
// pointer type is in the DWARF, so just invent a Type.

internal/gocore/gocore_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,6 @@ func TestVersions(t *testing.T) {
272272
loadExampleVersion(t, "1.13.zip")
273273
loadExampleVersion(t, "1.13.3.zip")
274274
loadExampleVersion(t, "1.14.zip")
275+
loadExampleVersion(t, "1.16.zip")
276+
loadExampleVersion(t, "1.17.zip")
275277
}

internal/gocore/module.go

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
type module struct {
1515
r region // inferior region holding a runtime.moduledata
1616
types, etypes core.Address // range that holds all the runtime._type data in this module
17+
p *Process // The parent process of this module.
1718
}
1819

1920
func (p *Process) readModules() {
@@ -29,20 +30,31 @@ func (p *Process) readModules() {
2930
}
3031

3132
func (p *Process) readModule(r region) *module {
32-
m := &module{r: r}
33+
m := &module{p: p, r: r}
3334
m.types = core.Address(r.Field("types").Uintptr())
3435
m.etypes = core.Address(r.Field("etypes").Uintptr())
3536

3637
// Read the pc->function table
3738
pcln := r.Field("pclntable")
39+
var pctab, funcnametab region
40+
if p.minorVersion >= 16 {
41+
// In 1.16, pclntable was split up into pctab and funcnametab.
42+
pctab = r.Field("pctab")
43+
funcnametab = r.Field("funcnametab")
44+
}
3845
ftab := r.Field("ftab")
3946
n := ftab.SliceLen() - 1 // last slot is a dummy, just holds entry
4047
for i := int64(0); i < n; i++ {
4148
ft := ftab.SliceIndex(i)
4249
min := core.Address(ft.Field("entry").Uintptr())
4350
max := core.Address(ftab.SliceIndex(i + 1).Field("entry").Uintptr())
4451
fr := pcln.SliceIndex(int64(ft.Field("funcoff").Uintptr())).Cast("runtime._func")
45-
f := m.readFunc(fr, pcln)
52+
var f *Func
53+
if p.minorVersion >= 16 {
54+
f = m.readFunc(fr, pctab, funcnametab)
55+
} else {
56+
f = m.readFunc(fr, pcln, pcln)
57+
}
4658
if f.entry != min {
4759
panic(fmt.Errorf("entry %x and min %x don't match for %s", f.entry, min, f.name))
4860
}
@@ -55,34 +67,52 @@ func (p *Process) readModule(r region) *module {
5567
// readFunc parses a runtime._func and returns a *Func.
5668
// r must have type runtime._func.
5769
// pcln must have type []byte and represent the module's pcln table region.
58-
func (m *module) readFunc(r region, pcln region) *Func {
70+
func (m *module) readFunc(r region, pctab region, funcnametab region) *Func {
5971
f := &Func{module: m, r: r}
6072
f.entry = core.Address(r.Field("entry").Uintptr())
61-
f.name = r.p.proc.ReadCString(pcln.SliceIndex(int64(r.Field("nameoff").Int32())).a)
62-
f.frameSize.read(r.p.proc, pcln.SliceIndex(int64(r.Field("pcsp").Int32())).a)
73+
f.name = r.p.proc.ReadCString(funcnametab.SliceIndex(int64(r.Field("nameoff").Int32())).a)
74+
pcsp := r.Field("pcsp")
75+
var pcspIdx int64
76+
if m.p.minorVersion >= 16 {
77+
// In 1.16, pcsp changed to be a uint32 from an int32.
78+
pcspIdx = int64(pcsp.Uint32())
79+
} else {
80+
pcspIdx = int64(pcsp.Int32())
81+
}
82+
f.frameSize.read(r.p.proc, pctab.SliceIndex(pcspIdx).a)
6383

6484
// Parse pcdata and funcdata, which are laid out beyond the end of the _func.
65-
a := r.a.Add(int64(r.p.findType("runtime._func").Size))
66-
n := r.Field("npcdata").Int32()
67-
for i := int32(0); i < n; i++ {
85+
// In 1.16, npcdata changed to be a uint32 from an int32.
86+
npcdata := r.Field("npcdata")
87+
var n uint32
88+
if m.p.minorVersion >= 16 {
89+
// In 1.16, pcsp changed to be a uint32 from an int32.
90+
n = uint32(npcdata.Uint32())
91+
} else {
92+
n = uint32(npcdata.Int32())
93+
}
94+
nfd := r.Field("nfuncdata")
95+
a := nfd.a.Add(nfd.typ.Size)
96+
97+
for i := uint32(0); i < n; i++ {
6898
f.pcdata = append(f.pcdata, r.p.proc.ReadInt32(a))
6999
a = a.Add(4)
70100
}
71101
a = a.Align(r.p.proc.PtrSize())
72102

73-
if nfd := r.Field("nfuncdata"); nfd.typ.Size == 1 { // go 1.12 and beyond, this is a uint8
74-
n = int32(nfd.Uint8())
103+
if nfd.typ.Size == 1 { // go 1.12 and beyond, this is a uint8
104+
n = uint32(nfd.Uint8())
75105
} else { // go 1.11 and earlier, this is an int32
76-
n = nfd.Int32()
106+
n = uint32(nfd.Int32())
77107
}
78-
for i := int32(0); i < n; i++ {
108+
for i := uint32(0); i < n; i++ {
79109
f.funcdata = append(f.funcdata, r.p.proc.ReadPtr(a))
80110
a = a.Add(r.p.proc.PtrSize())
81111
}
82112

83113
// Read pcln tables we need.
84114
if stackmap := int(r.p.rtConstants["_PCDATA_StackMapIndex"]); stackmap < len(f.pcdata) {
85-
f.stackMap.read(r.p.proc, pcln.SliceIndex(int64(f.pcdata[stackmap])).a)
115+
f.stackMap.read(r.p.proc, pctab.SliceIndex(int64(f.pcdata[stackmap])).a)
86116
} else {
87117
f.stackMap.setEmpty()
88118
}

internal/gocore/process.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"debug/dwarf"
99
"fmt"
1010
"math/bits"
11+
"strconv"
1112
"strings"
1213
"sync"
1314

@@ -54,6 +55,7 @@ type Process struct {
5455
stats *Stats
5556

5657
buildVersion string
58+
minorVersion int
5759

5860
globals []*Root
5961

@@ -153,6 +155,15 @@ func Core(proc *core.Process) (p *Process, err error) {
153155

154156
// Read all the data that depend on runtime globals.
155157
p.buildVersion = p.rtGlobals["buildVersion"].String()
158+
versionComponents := strings.Split(p.buildVersion, ".")
159+
if len(versionComponents) < 2 {
160+
panic("malformed version " + p.buildVersion)
161+
}
162+
p.minorVersion, err = strconv.Atoi(versionComponents[1])
163+
if err != nil {
164+
panic("malformed version " + p.buildVersion)
165+
}
166+
156167
p.readModules()
157168
p.readHeap()
158169
p.readGs()
@@ -258,10 +269,16 @@ func (p *Process) readHeap() {
258269
// Copy out ptr/nonptr bits
259270
n := bitmap.ArrayLen()
260271
for i := int64(0); i < n; i++ {
272+
// The nth byte is composed of 4 object bits and 4 live/dead
273+
// bits. We ignore the 4 live/dead bits, which are on the
274+
// high order side of the byte.
275+
//
276+
// See mbitmap.go for more information on the format of
277+
// the bitmap field of heapArena.
261278
m := bitmap.ArrayIndex(i).Uint8()
262-
for j := int64(0); j < 8; j++ {
279+
for j := int64(0); j < 4; j++ {
263280
if m>>uint(j)&1 != 0 {
264-
p.setHeapPtr(min.Add((i*8 + j) * ptrSize))
281+
p.setHeapPtr(min.Add((i*4 + j) * ptrSize))
265282
}
266283
}
267284
}
@@ -316,7 +333,10 @@ func (p *Process) readSpans(mheap region, arenas []arena) {
316333
panic("weird mapping " + m.Perm().String())
317334
}
318335
}
319-
if mheap.HasField("curArena") { // go1.13.3 and up
336+
if p.minorVersion < 17 && mheap.HasField("curArena") {
337+
// go1.13.3 and up has curArena.
338+
// In Go 1.17, we ... don't need to do this any longer. See patch
339+
// bd6aeca9686d5e672ffda1ea0cfeac7a3e7a20a4
320340
// Subtract from the heap unallocated space
321341
// in the current arena.
322342
ca := mheap.Field("curArena")

internal/gocore/testdata/1.16.zip

801 KB
Binary file not shown.

internal/gocore/testdata/1.17.zip

736 KB
Binary file not shown.

internal/gocore/type.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,11 @@ func (p *Process) typeHeap() {
466466
for len(work) > 0 {
467467
c := work[len(work)-1]
468468
work = work[:len(work)-1]
469+
switch c.t.Kind {
470+
case KindBool, KindInt, KindUint, KindFloat, KindComplex:
471+
// Don't do O(n) function calls for big primitive slices
472+
continue
473+
}
469474
for i := int64(0); i < c.r; i++ {
470475
p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add)
471476
}

0 commit comments

Comments
 (0)