603 lines
16 KiB
Go
603 lines
16 KiB
Go
package proc
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
|
"github.com/go-delve/delve/pkg/goversion"
|
|
)
|
|
|
|
type mapIterator interface {
|
|
next() bool
|
|
key() *Variable
|
|
value() *Variable
|
|
}
|
|
|
|
func (v *Variable) mapIterator(maxNumBuckets uint64) mapIterator {
|
|
mt := v.RealType.(*godwarf.MapType)
|
|
sv := v.clone()
|
|
sv.RealType = resolveTypedef(&(sv.RealType.(*godwarf.MapType).TypedefType))
|
|
sv = sv.maybeDereference()
|
|
v.Base = sv.Addr
|
|
|
|
maptype, ok := sv.RealType.(*godwarf.StructType)
|
|
if !ok {
|
|
v.Unreadable = errors.New("wrong real type for map")
|
|
return nil
|
|
}
|
|
|
|
isptr := func(typ godwarf.Type) bool {
|
|
_, isptr := typ.(*godwarf.PtrType)
|
|
return isptr
|
|
}
|
|
|
|
it := &mapIteratorClassic{v: v, bidx: 0, b: nil, idx: 0, maxNumBuckets: maxNumBuckets, keyTypeIsPtr: isptr(mt.KeyType), elemTypeIsPtr: isptr(mt.ElemType)}
|
|
itswiss := &mapIteratorSwiss{v: v, maxNumGroups: maxNumBuckets, keyTypeIsPtr: isptr(mt.KeyType), elemTypeIsPtr: isptr(mt.ElemType)}
|
|
|
|
if sv.Addr == 0 {
|
|
it.numbuckets = 0
|
|
return it
|
|
}
|
|
|
|
v.mem = cacheMemory(v.mem, v.Base, int(v.RealType.Size()))
|
|
|
|
for _, f := range maptype.Field {
|
|
var err error
|
|
field, _ := sv.toField(f)
|
|
switch f.Name {
|
|
// Classic map fields
|
|
case "count": // +rtype -fieldof hmap int
|
|
v.Len, err = field.asInt()
|
|
case "B": // +rtype -fieldof hmap uint8
|
|
var b uint64
|
|
b, err = field.asUint()
|
|
it.numbuckets = 1 << b
|
|
it.oldmask = (1 << (b - 1)) - 1
|
|
case "buckets": // +rtype -fieldof hmap unsafe.Pointer
|
|
it.buckets = field.maybeDereference()
|
|
case "oldbuckets": // +rtype -fieldof hmap unsafe.Pointer
|
|
it.oldbuckets = field.maybeDereference()
|
|
|
|
// Swisstable map fields
|
|
case "used":
|
|
var n uint64
|
|
n, err = field.asUint()
|
|
v.Len = int64(n)
|
|
case "dirPtr":
|
|
itswiss.dirPtr = field
|
|
case "dirLen":
|
|
itswiss.dirLen, err = field.asInt()
|
|
}
|
|
if err != nil {
|
|
v.Unreadable = err
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if it.buckets == nil && itswiss.dirPtr != nil {
|
|
itswiss.loadTypes()
|
|
return itswiss
|
|
}
|
|
|
|
if it.buckets == nil || it.oldbuckets == nil || it.buckets.Kind != reflect.Struct || it.oldbuckets.Kind != reflect.Struct {
|
|
v.Unreadable = errMapBucketsNotStruct
|
|
return nil
|
|
}
|
|
|
|
it.hashTophashEmptyOne = hashTophashEmptyZero
|
|
it.hashMinTopHash = hashMinTopHashGo111
|
|
if producer := v.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 12) {
|
|
it.hashTophashEmptyOne = hashTophashEmptyOne
|
|
it.hashMinTopHash = hashMinTopHashGo112
|
|
}
|
|
|
|
return it
|
|
}
|
|
|
|
// Classic Maps ///////////////////////////////////////////////////////////////
|
|
|
|
type mapIteratorClassic struct {
|
|
v *Variable
|
|
numbuckets uint64
|
|
oldmask uint64
|
|
buckets *Variable
|
|
oldbuckets *Variable
|
|
b *Variable
|
|
bidx uint64
|
|
|
|
keyTypeIsPtr, elemTypeIsPtr bool
|
|
|
|
tophashes *Variable
|
|
keys *Variable
|
|
values *Variable
|
|
overflow *Variable
|
|
|
|
maxNumBuckets uint64 // maximum number of buckets to scan
|
|
|
|
idx int64
|
|
|
|
hashTophashEmptyOne uint64 // Go 1.12 and later has two sentinel tophash values for an empty cell, this is the second one (the first one hashTophashEmptyZero, the same as Go 1.11 and earlier)
|
|
hashMinTopHash uint64 // minimum value of tophash for a cell that isn't either evacuated or empty
|
|
}
|
|
|
|
var errMapBucketContentsNotArray = errors.New("malformed map type: keys, values or tophash of a bucket is not an array")
|
|
var errMapBucketContentsInconsistentLen = errors.New("malformed map type: inconsistent array length in bucket")
|
|
var errMapBucketsNotStruct = errors.New("malformed map type: buckets, oldbuckets or overflow field not a struct")
|
|
|
|
func (it *mapIteratorClassic) nextBucket() bool {
|
|
if it.overflow != nil && it.overflow.Addr > 0 {
|
|
it.b = it.overflow
|
|
} else {
|
|
it.b = nil
|
|
|
|
if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets {
|
|
return false
|
|
}
|
|
|
|
for it.bidx < it.numbuckets {
|
|
it.b = it.buckets.clone()
|
|
it.b.Addr += uint64(it.buckets.DwarfType.Size()) * it.bidx
|
|
|
|
if it.oldbuckets.Addr <= 0 {
|
|
break
|
|
}
|
|
|
|
// if oldbuckets is not nil we are iterating through a map that is in
|
|
// the middle of a grow.
|
|
// if the bucket we are looking at hasn't been filled in we iterate
|
|
// instead through its corresponding "oldbucket" (i.e. the bucket the
|
|
// elements of this bucket are coming from) but only if this is the first
|
|
// of the two buckets being created from the same oldbucket (otherwise we
|
|
// would print some keys twice)
|
|
|
|
oldbidx := it.bidx & it.oldmask
|
|
oldb := it.oldbuckets.clone()
|
|
oldb.Addr += uint64(it.oldbuckets.DwarfType.Size()) * oldbidx
|
|
|
|
if it.mapEvacuated(oldb) {
|
|
break
|
|
}
|
|
|
|
if oldbidx == it.bidx {
|
|
it.b = oldb
|
|
break
|
|
}
|
|
|
|
// oldbucket origin for current bucket has not been evacuated but we have already
|
|
// iterated over it so we should just skip it
|
|
it.b = nil
|
|
it.bidx++
|
|
}
|
|
|
|
if it.b == nil {
|
|
return false
|
|
}
|
|
it.bidx++
|
|
}
|
|
|
|
if it.b.Addr <= 0 {
|
|
return false
|
|
}
|
|
|
|
it.b.mem = cacheMemory(it.b.mem, it.b.Addr, int(it.b.RealType.Size()))
|
|
|
|
it.tophashes = nil
|
|
it.keys = nil
|
|
it.values = nil
|
|
it.overflow = nil
|
|
|
|
for _, f := range it.b.DwarfType.(*godwarf.StructType).Field {
|
|
field, err := it.b.toField(f)
|
|
if err != nil {
|
|
it.v.Unreadable = err
|
|
return false
|
|
}
|
|
if field.Unreadable != nil {
|
|
it.v.Unreadable = field.Unreadable
|
|
return false
|
|
}
|
|
|
|
switch f.Name {
|
|
case "tophash": // +rtype -fieldof bmap [8]uint8
|
|
it.tophashes = field
|
|
case "keys":
|
|
it.keys = field
|
|
case "values":
|
|
it.values = field
|
|
case "overflow":
|
|
it.overflow = field.maybeDereference()
|
|
}
|
|
}
|
|
|
|
// sanity checks
|
|
if it.tophashes == nil || it.keys == nil || it.values == nil {
|
|
it.v.Unreadable = errors.New("malformed map type")
|
|
return false
|
|
}
|
|
|
|
if it.tophashes.Kind != reflect.Array || it.keys.Kind != reflect.Array || it.values.Kind != reflect.Array {
|
|
it.v.Unreadable = errMapBucketContentsNotArray
|
|
return false
|
|
}
|
|
|
|
if it.tophashes.Len != it.keys.Len {
|
|
it.v.Unreadable = errMapBucketContentsInconsistentLen
|
|
return false
|
|
}
|
|
|
|
if it.values.fieldType.Size() > 0 && it.tophashes.Len != it.values.Len {
|
|
// if the type of the value is zero-sized (i.e. struct{}) then the values
|
|
// array's length is zero.
|
|
it.v.Unreadable = errMapBucketContentsInconsistentLen
|
|
return false
|
|
}
|
|
|
|
if it.overflow.Kind != reflect.Struct {
|
|
it.v.Unreadable = errMapBucketsNotStruct
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (it *mapIteratorClassic) next() bool {
|
|
for {
|
|
if it.b == nil || it.idx >= it.tophashes.Len {
|
|
r := it.nextBucket()
|
|
if !r {
|
|
return false
|
|
}
|
|
it.idx = 0
|
|
}
|
|
tophash, _ := it.tophashes.sliceAccess(int(it.idx))
|
|
h, err := tophash.asUint()
|
|
if err != nil {
|
|
it.v.Unreadable = fmt.Errorf("unreadable tophash: %v", err)
|
|
return false
|
|
}
|
|
it.idx++
|
|
if h != hashTophashEmptyZero && h != it.hashTophashEmptyOne {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (it *mapIteratorClassic) key() *Variable {
|
|
k, _ := it.keys.sliceAccess(int(it.idx - 1))
|
|
if k.Kind == reflect.Ptr && !it.keyTypeIsPtr {
|
|
k = k.maybeDereference()
|
|
}
|
|
return k
|
|
}
|
|
|
|
func (it *mapIteratorClassic) value() *Variable {
|
|
if it.values.fieldType.Size() <= 0 {
|
|
return it.v.newVariable("", it.values.Addr, it.values.fieldType, DereferenceMemory(it.v.mem))
|
|
}
|
|
|
|
v, _ := it.values.sliceAccess(int(it.idx - 1))
|
|
if v.Kind == reflect.Ptr && !it.elemTypeIsPtr {
|
|
v = v.maybeDereference()
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func (it *mapIteratorClassic) mapEvacuated(b *Variable) bool {
|
|
if b.Addr == 0 {
|
|
return true
|
|
}
|
|
for _, f := range b.DwarfType.(*godwarf.StructType).Field {
|
|
if f.Name != "tophash" {
|
|
continue
|
|
}
|
|
tophashes, _ := b.toField(f)
|
|
tophash0var, _ := tophashes.sliceAccess(0)
|
|
tophash0, err := tophash0var.asUint()
|
|
if err != nil {
|
|
return true
|
|
}
|
|
//TODO: this needs to be > hashTophashEmptyOne for go >= 1.12
|
|
return tophash0 > it.hashTophashEmptyOne && tophash0 < it.hashMinTopHash
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Swisstable Maps ///////////////////////////////////////////////////////////////
|
|
|
|
const (
|
|
swissTableCtrlEmpty = 0b10000000 // +rtype go1.24 internal/runtime/maps.ctrlEmpty
|
|
)
|
|
|
|
type mapIteratorSwiss struct {
|
|
v *Variable
|
|
dirPtr *Variable
|
|
dirLen int64
|
|
maxNumGroups uint64 // Maximum number of groups we will visit
|
|
|
|
keyTypeIsPtr, elemTypeIsPtr bool
|
|
tableType, groupType *godwarf.StructType
|
|
|
|
tableFieldIndex, tableFieldGroups, groupsFieldLengthMask, groupsFieldData, groupFieldCtrl, groupFieldSlots, slotFieldKey, slotFieldElem *godwarf.StructField
|
|
|
|
dirIdx int64
|
|
tab *swissTable
|
|
|
|
groupIdx uint64
|
|
group *swissGroup
|
|
|
|
slotIdx uint32
|
|
|
|
groupCount uint64 // Total count of visited groups except for current table
|
|
|
|
curKey, curValue *Variable
|
|
}
|
|
|
|
type swissTable struct {
|
|
index int64
|
|
groups *Variable
|
|
}
|
|
|
|
type swissGroup struct {
|
|
slots *Variable
|
|
ctrls []byte
|
|
}
|
|
|
|
var errSwissTableCouldNotLoad = errors.New("could not load one of the tables")
|
|
var errSwissMapBadType = errors.New("swiss table type does not have some required fields")
|
|
var errSwissMapBadTableField = errors.New("swiss table bad table field")
|
|
var errSwissMapBadGroupTypeErr = errors.New("bad swiss map type, group type lacks some required fields")
|
|
|
|
// loadTypes determines the correct type for it.dirPtr: the linker records
|
|
// this type as **table but in reality it is either *[dirLen]*table for
|
|
// large maps or *group for small maps, when it.dirLen == 0.
|
|
func (it *mapIteratorSwiss) loadTypes() {
|
|
tableptrptrtyp, ok := it.dirPtr.DwarfType.(*godwarf.PtrType)
|
|
if !ok {
|
|
it.v.Unreadable = errSwissMapBadTableField
|
|
return
|
|
}
|
|
tableptrtyp, ok := tableptrptrtyp.Type.(*godwarf.PtrType)
|
|
if !ok {
|
|
it.v.Unreadable = errSwissMapBadTableField
|
|
return
|
|
}
|
|
it.tableType, ok = tableptrtyp.Type.(*godwarf.StructType)
|
|
if !ok {
|
|
it.v.Unreadable = errSwissMapBadTableField
|
|
return
|
|
}
|
|
for _, field := range it.tableType.Field {
|
|
switch field.Name {
|
|
case "index":
|
|
it.tableFieldIndex = field
|
|
case "groups":
|
|
it.tableFieldGroups = field
|
|
groupstyp, ok := field.Type.(*godwarf.StructType)
|
|
if ok {
|
|
for _, field := range groupstyp.Field {
|
|
switch field.Name {
|
|
case "data":
|
|
it.groupsFieldData = field
|
|
typ, ok := field.Type.(*godwarf.PtrType)
|
|
if ok {
|
|
it.groupType, _ = resolveTypedef(typ.Type).(*godwarf.StructType)
|
|
}
|
|
case "lengthMask":
|
|
it.groupsFieldLengthMask = field
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if it.groupType == nil || it.tableFieldIndex == nil || it.tableFieldGroups == nil || it.groupsFieldLengthMask == nil {
|
|
it.v.Unreadable = errSwissMapBadType
|
|
return
|
|
}
|
|
for _, field := range it.groupType.Field {
|
|
switch field.Name {
|
|
case "ctrl":
|
|
it.groupFieldCtrl = field
|
|
case "slots":
|
|
it.groupFieldSlots = field
|
|
}
|
|
}
|
|
if it.groupFieldCtrl == nil || it.groupFieldSlots == nil {
|
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
|
return
|
|
}
|
|
|
|
slotsType, ok := resolveTypedef(it.groupFieldSlots.Type).(*godwarf.ArrayType)
|
|
if !ok {
|
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
|
return
|
|
}
|
|
slotType, ok := slotsType.Type.(*godwarf.StructType)
|
|
if !ok {
|
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
|
return
|
|
}
|
|
for _, field := range slotType.Field {
|
|
switch field.Name {
|
|
case "key":
|
|
it.slotFieldKey = field
|
|
case "elem":
|
|
it.slotFieldElem = field
|
|
}
|
|
}
|
|
if it.slotFieldKey == nil || it.slotFieldElem == nil {
|
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
|
return
|
|
}
|
|
|
|
if it.dirLen <= 0 {
|
|
// small maps, convert it.dirPtr to be of type *group, then dereference it
|
|
it.dirPtr.DwarfType = pointerTo(fakeArrayType(1, it.groupType), it.v.bi.Arch)
|
|
it.dirPtr.RealType = it.dirPtr.DwarfType
|
|
it.dirPtr = it.dirPtr.maybeDereference()
|
|
it.dirLen = 1
|
|
it.tab = &swissTable{groups: it.dirPtr} // so that we don't try to load this later on
|
|
return
|
|
}
|
|
|
|
// normal map, convert it.dirPtr to be of type *[dirLen]*table, then dereference it
|
|
|
|
it.dirPtr.DwarfType = pointerTo(fakeArrayType(uint64(it.dirLen), tableptrtyp), it.v.bi.Arch)
|
|
it.dirPtr.RealType = it.dirPtr.DwarfType
|
|
it.dirPtr = it.dirPtr.maybeDereference()
|
|
}
|
|
|
|
// derived from $GOROOT/src/internal/runtime/maps/table.go and $GOROOT/src/runtime/runtime-gdb.py
|
|
func (it *mapIteratorSwiss) next() bool {
|
|
if it.v.Unreadable != nil {
|
|
return false
|
|
}
|
|
for it.dirIdx < it.dirLen {
|
|
if it.tab == nil {
|
|
it.loadCurrentTable()
|
|
if it.tab == nil {
|
|
return false
|
|
}
|
|
if it.tab.index != it.dirIdx {
|
|
it.nextTable()
|
|
continue
|
|
}
|
|
}
|
|
|
|
for ; it.groupIdx < uint64(it.tab.groups.Len); it.nextGroup() {
|
|
if it.maxNumGroups > 0 && it.groupIdx+it.groupCount >= it.maxNumGroups {
|
|
return false
|
|
}
|
|
if it.group == nil {
|
|
it.loadCurrentGroup()
|
|
if it.group == nil {
|
|
return false
|
|
}
|
|
}
|
|
|
|
for ; it.slotIdx < uint32(it.group.slots.Len); it.slotIdx++ {
|
|
if it.slotIsEmptyOrDeleted(it.slotIdx) {
|
|
continue
|
|
}
|
|
|
|
cur, err := it.group.slots.sliceAccess(int(it.slotIdx))
|
|
if err != nil {
|
|
it.v.Unreadable = fmt.Errorf("error accessing swiss map in table %d, group %d, slot %d", it.dirIdx, it.groupIdx, it.slotIdx)
|
|
return false
|
|
}
|
|
|
|
var err1, err2 error
|
|
it.curKey, err1 = cur.toField(it.slotFieldKey)
|
|
it.curValue, err2 = cur.toField(it.slotFieldElem)
|
|
if err1 != nil || err2 != nil {
|
|
it.v.Unreadable = fmt.Errorf("error accessing swiss map slot: %v %v", err1, err2)
|
|
return false
|
|
}
|
|
|
|
// If the type we expect is non-pointer but we read a pointer type it
|
|
// means that the key (or the value) is stored indirectly into the map
|
|
// because it is too big. We dereference it here so that the type of the
|
|
// key (or value) matches the type on the map definition.
|
|
if it.curKey.Kind == reflect.Ptr && !it.keyTypeIsPtr {
|
|
it.curKey = it.curKey.maybeDereference()
|
|
}
|
|
if it.curValue.Kind == reflect.Ptr && !it.elemTypeIsPtr {
|
|
it.curValue = it.curValue.maybeDereference()
|
|
}
|
|
|
|
it.slotIdx++
|
|
return true
|
|
}
|
|
|
|
it.slotIdx = 0
|
|
}
|
|
|
|
it.groupCount += it.groupIdx
|
|
it.groupIdx = 0
|
|
it.group = nil
|
|
it.nextTable()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (it *mapIteratorSwiss) nextTable() {
|
|
it.dirIdx++
|
|
it.tab = nil
|
|
}
|
|
|
|
func (it *mapIteratorSwiss) nextGroup() {
|
|
it.groupIdx++
|
|
it.group = nil
|
|
}
|
|
|
|
// loadCurrentTable loads the table at index it.dirIdx into it.tab
|
|
func (it *mapIteratorSwiss) loadCurrentTable() {
|
|
tab, err := it.dirPtr.sliceAccess(int(it.dirIdx))
|
|
if err != nil || tab == nil || tab.Unreadable != nil {
|
|
it.v.Unreadable = errSwissTableCouldNotLoad
|
|
return
|
|
}
|
|
|
|
tab = tab.maybeDereference()
|
|
|
|
r := &swissTable{}
|
|
|
|
field, _ := tab.toField(it.tableFieldIndex)
|
|
r.index, err = field.asInt()
|
|
if err != nil {
|
|
it.v.Unreadable = fmt.Errorf("could not load swiss table index: %v", err)
|
|
return
|
|
}
|
|
|
|
groups, _ := tab.toField(it.tableFieldGroups)
|
|
r.groups, _ = groups.toField(it.groupsFieldData)
|
|
|
|
field, _ = groups.toField(it.groupsFieldLengthMask)
|
|
groupsLengthMask, err := field.asUint()
|
|
if err != nil {
|
|
it.v.Unreadable = fmt.Errorf("could not load swiss table group lengthMask: %v", err)
|
|
return
|
|
}
|
|
|
|
// convert the type of groups from *group to *[len]group so that it's easier to use
|
|
r.groups.DwarfType = pointerTo(fakeArrayType(groupsLengthMask+1, it.groupType), it.v.bi.Arch)
|
|
r.groups.RealType = r.groups.DwarfType
|
|
r.groups = r.groups.maybeDereference()
|
|
|
|
it.tab = r
|
|
}
|
|
|
|
// loadCurrentGroup loads the group at index it.groupIdx of it.tab into it.group
|
|
func (it *mapIteratorSwiss) loadCurrentGroup() {
|
|
group, err := it.tab.groups.sliceAccess(int(it.groupIdx))
|
|
if err != nil {
|
|
it.v.Unreadable = fmt.Errorf("could not load swiss map group: %v", err)
|
|
return
|
|
}
|
|
g := &swissGroup{}
|
|
g.slots, _ = group.toField(it.groupFieldSlots)
|
|
ctrl, _ := group.toField(it.groupFieldCtrl)
|
|
g.ctrls = make([]byte, ctrl.DwarfType.Size())
|
|
_, err = ctrl.mem.ReadMemory(g.ctrls, ctrl.Addr)
|
|
if err != nil {
|
|
it.v.Unreadable = err
|
|
return
|
|
}
|
|
it.group = g
|
|
}
|
|
|
|
func (it *mapIteratorSwiss) key() *Variable {
|
|
return it.curKey
|
|
}
|
|
|
|
func (it *mapIteratorSwiss) value() *Variable {
|
|
return it.curValue
|
|
}
|
|
|
|
func (it *mapIteratorSwiss) slotIsEmptyOrDeleted(k uint32) bool {
|
|
//TODO: check that this hasn't changed after it's merged and the TODO is deleted
|
|
return it.group.ctrls[k]&swissTableCtrlEmpty == swissTableCtrlEmpty
|
|
}
|