internal/gocore: refactor heapArena reading There are a lot of different historical changes to the heap arena structure, making Process.readHeap difficult to follow. Refactor to split out version-specific logic to individual methods. Reading allocation headers is not yet supported by this CL. For golang/go#64049. Change-Id: Ia4198bd8492d8426c7a2cc8671b78c49ab6b7161 Reviewed-on: https://go-review.googlesource.com/c/debug/+/541131 Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/gocore/process.go b/internal/gocore/process.go index 7c2ed8f..52853d5 100644 --- a/internal/gocore/process.go +++ b/internal/gocore/process.go
@@ -177,10 +177,12 @@ return p, nil } +// arena is a summary of the size of components of a heapArena. type arena struct { heapMin core.Address heapMax core.Address + // Optional. bitmapMin core.Address bitmapMax core.Address @@ -199,37 +201,13 @@ func (p *Process) readHeap() { ptrSize := p.proc.PtrSize() - logPtrSize := p.proc.LogPtrSize() p.pageTable = map[core.Address]*pageTableEntry{} mheap := p.rtGlobals["mheap_"] var arenas []arena if mheap.HasField("spans") { // go 1.9 or 1.10. There is a single arena. - 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())) - spanTableStart := mheap.Field("spans").SlicePtr().Address() - spanTableEnd := spanTableStart.Add(mheap.Field("spans").SliceCap() * ptrSize) - arenas = append(arenas, arena{ - heapMin: arenaStart, - heapMax: arenaEnd, - bitmapMin: bitmapStart, - bitmapMax: bitmapEnd, - spanTableMin: spanTableStart, - spanTableMax: spanTableEnd, - }) - - // Copy pointer bits to heap info. - // Note that the pointer bits are stored backwards. - for a := arenaStart; a < arenaUsed; a = a.Add(ptrSize) { - off := a.Sub(arenaStart) >> logPtrSize - if p.proc.ReadUint8(bitmapEnd.Add(-(off>>2)-1))>>uint(off&3)&1 != 0 { - p.setHeapPtr(a) - } - } + arenas = append(arenas, p.readArena19(mheap)) } else { // go 1.11+. Has multiple arenas. arenaSize := p.rtConstants["heapArenaBytes"] @@ -240,6 +218,7 @@ if ptrSize == 4 && arenaBaseOffset != 0 { panic("arenaBaseOffset must be 0 for 32-bit inferior") } + level1Table := mheap.Field("arenas") level1size := level1Table.ArrayLen() for level1 := int64(0); level1 < level1size; level1++ { @@ -258,54 +237,8 @@ min := core.Address(arenaSize*(level2+level1*level2size) - arenaBaseOffset) max := min.Add(arenaSize) - var bitmap region - var oneBitBitmap bool - if a.HasField("heapArenaPtrScalar") { // go 1.22 - bitmap = a.Field("heapArenaPtrScalar").Field("bitmap") - oneBitBitmap = true - } else { - bitmap = a.Field("bitmap") - oneBitBitmap = a.HasField("noMorePtrs") // Starting in 1.20. - } - spans := a.Field("spans") - arenas = append(arenas, arena{ - heapMin: min, - heapMax: max, - bitmapMin: bitmap.a, - bitmapMax: bitmap.a.Add(bitmap.ArrayLen()), - spanTableMin: spans.a, - spanTableMax: spans.a.Add(spans.ArrayLen() * ptrSize), - }) - - // Copy out ptr/nonptr bits - n := bitmap.ArrayLen() - for i := int64(0); i < n; i++ { - if oneBitBitmap { - // The array uses 1 bit per word of heap. See mbitmap.go for - // more information. - m := bitmap.ArrayIndex(i).Uintptr() - bits := 8 * ptrSize - for j := int64(0); j < bits; j++ { - if m>>uint(j)&1 != 0 { - p.setHeapPtr(min.Add((i*bits + j) * ptrSize)) - } - } - } else { - // 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 < 4; j++ { - if m>>uint(j)&1 != 0 { - p.setHeapPtr(min.Add((i*4 + j) * ptrSize)) - } - } - } - } + arenas = append(arenas, p.readArena(a, min, max)) } } } @@ -313,6 +246,112 @@ p.readSpans(mheap, arenas) } +// Read the global arena. Go 1.9 or 1.10 only, which has a single arena. Record +// heap pointers and return the arena size summary. +func (p *Process) readArena19(mheap region) arena { + ptrSize := p.proc.PtrSize() + logPtrSize := p.proc.LogPtrSize() + + 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())) + spanTableStart := mheap.Field("spans").SlicePtr().Address() + spanTableEnd := spanTableStart.Add(mheap.Field("spans").SliceCap() * ptrSize) + + // Copy pointer bits to heap info. + // Note that the pointer bits are stored backwards. + for a := arenaStart; a < arenaUsed; a = a.Add(ptrSize) { + off := a.Sub(arenaStart) >> logPtrSize + if p.proc.ReadUint8(bitmapEnd.Add(-(off>>2)-1))>>uint(off&3)&1 != 0 { + p.setHeapPtr(a) + } + } + + return arena{ + heapMin: arenaStart, + heapMax: arenaEnd, + bitmapMin: bitmapStart, + bitmapMax: bitmapEnd, + spanTableMin: spanTableStart, + spanTableMax: spanTableEnd, + } +} + +// Read a single heapArena. Go 1.11+, which has multiple areans. Record heap +// pointers and return the arena size summary. +func (p *Process) readArena(a region, min, max core.Address) arena { + ptrSize := p.proc.PtrSize() + + var bitmap region + if a.HasField("bitmap") { // Before go 1.22 + bitmap = a.Field("bitmap") + if oneBitBitmap := a.HasField("noMorePtrs"); oneBitBitmap { // Starting in go 1.20 + p.readOneBitBitmap(bitmap, min) + } else { + p.readMultiBitBitmap(bitmap, min) + } + } else if a.HasField("heapArenaPtrScalar") && a.Field("heapArenaPtrScalar").HasField("bitmap") { // go 1.22 without allocation headers + // TODO: This configuration only existed between CL 537978 and CL + // 538217 and was never released. Prune support. + bitmap = a.Field("heapArenaPtrScalar").Field("bitmap") + p.readOneBitBitmap(bitmap, min) + } else { // go 1.22 with allocation headers + panic("unimplemented") + } + + spans := a.Field("spans") + arena := arena{ + heapMin: min, + heapMax: max, + spanTableMin: spans.a, + spanTableMax: spans.a.Add(spans.ArrayLen() * ptrSize), + } + if bitmap.a != 0 { + arena.bitmapMin = bitmap.a + arena.bitmapMax = bitmap.a.Add(bitmap.ArrayLen()) + } + return arena +} + +// Read a one-bit bitmap (Go 1.20+), recording the heap pointers. +func (p *Process) readOneBitBitmap(bitmap region, min core.Address) { + ptrSize := p.proc.PtrSize() + n := bitmap.ArrayLen() + for i := int64(0); i < n; i++ { + // The array uses 1 bit per word of heap. See mbitmap.go for + // more information. + m := bitmap.ArrayIndex(i).Uintptr() + bits := 8 * ptrSize + for j := int64(0); j < bits; j++ { + if m>>uint(j)&1 != 0 { + p.setHeapPtr(min.Add((i*bits + j) * ptrSize)) + } + } + } +} + +// Read a multi-bit bitmap (Go 1.11-1.20), recording the heap pointers. +func (p *Process) readMultiBitBitmap(bitmap region, min core.Address) { + ptrSize := p.proc.PtrSize() + 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 < 4; j++ { + if m>>uint(j)&1 != 0 { + p.setHeapPtr(min.Add((i*4 + j) * ptrSize)) + } + } + } +} + func (p *Process) readSpans(mheap region, arenas []arena) { var all int64 var text int64