blob: 481a04d36d156fd9b91b421deae24acaac3e3a7d [file] [log] [blame]
// 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/internal/core"
)
// An Object represents a single reachable object in the Go heap.
// Unreachable (garbage) objects are not represented as Objects.
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 { // 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 heap info.
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
//
// BUG: If the process dumps core with a thread in mallocgc at the point
// where an object is reachable from the stack but it hasn't been zeroed
// yet, it's possible for us to observe stale pointers here and follow them.
// Not a huge deal given that we'll just ignore outright bad pointers, but
// we may accidentally mark some objects as live erroneously.
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.
n = 0
p.ForEachObject(func(x Object) bool {
h := p.findHeapInfo(p.Addr(x))
if h.firstIdx == -1 {
h.firstIdx = n
}
n++
return true
})
if n != p.nObj {
panic("object count wrong")
}
// 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 {
return p.findHeapInfo(a).IsPtr(a, p.proc.PtrSize())
}
// IsPtr reports whether the inferior at address a contains a pointer.
func (p *Process) IsPtr(a core.Address) bool {
h := p.findHeapInfo(a)
if h != nil {
return h.IsPtr(a, p.proc.PtrSize())
}
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 {
// 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 { // free or garbage
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 _, k := range p.pages {
pt := p.pageTable[k]
for i := range pt {
h := &pt[i]
m := h.mark
for m != 0 {
j := bits.TrailingZeros64(m)
m &= m - 1
x := Object(k)*pageTableSize*heapInfoSize + Object(i)*heapInfoSize + Object(j)*8
if !fn(x) {
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.
func (p *Process) Type(x Object) (*Type, int64) {
p.typeHeap()
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, or -1 if none
// For 64-bit inferiors, ptr[0] contains 64 pointer bits, one
// for every 8 bytes. On 32-bit inferiors, ptr contains 128
// pointer bits, one for every 4 bytes.
ptr [2]uint64
}
func (h *heapInfo) IsPtr(a core.Address, ptrSize int64) bool {
if ptrSize == 8 {
i := uint(a%heapInfoSize) / 8
return h.ptr[0]>>i&1 != 0
}
i := a % heapInfoSize / 4
return h.ptr[i/64]>>(i%64)&1 != 0
}
// setHeapPtr records that the memory at heap address a contains a pointer.
func (p *Process) setHeapPtr(a core.Address) {
h := p.allocHeapInfo(a)
if p.proc.PtrSize() == 8 {
i := uint(a%heapInfoSize) / 8
h.ptr[0] |= uint64(1) << i
return
}
i := a % heapInfoSize / 4
h.ptr[i/64] |= uint64(1) << (i % 64)
}
// Heap info structures cover 9 bits of address.
// A page table entry covers 20 bits of address (1MB).
const pageTableSize = 1 << 11
type pageTableEntry [pageTableSize]heapInfo
// findHeapInfo finds the heapInfo structure for a.
// Returns nil if a is not a heap address.
func (p *Process) findHeapInfo(a core.Address) *heapInfo {
k := a / heapInfoSize / pageTableSize
i := a / heapInfoSize % pageTableSize
t := p.pageTable[k]
if t == nil {
return nil
}
h := &t[i]
if h.base == 0 {
return nil
}
return h
}
// Same as findHeapInfo, but allocates the heapInfo if it
// hasn't been allocated yet.
func (p *Process) allocHeapInfo(a core.Address) *heapInfo {
k := a / heapInfoSize / pageTableSize
i := a / heapInfoSize % pageTableSize
t := p.pageTable[k]
if t == nil {
t = new(pageTableEntry)
for j := 0; j < pageTableSize; j++ {
t[j].firstIdx = -1
}
p.pageTable[k] = t
p.pages = append(p.pages, k)
}
return &t[i]
}