x/debug: generalize map-reading code and move it from print.go to peek.go so it can be used elsewhere.
Change the printer to output maps in a similar format to fmt.Print.
Change-Id: Ie53d83fd769a3d0c175c3d67d89efc12ffe17826
Reviewed-on: https://go-review.googlesource.com/12349
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index 855f3cf..3b9e35b 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -60,10 +60,10 @@
`main.Z_interface`: `("*main.FooStruct", 0xX)`,
`main.Z_interface_nil`: `(<nil>, <nil>)`,
`main.Z_interface_typed_nil`: `("*main.FooStruct", <nil>)`,
- `main.Z_map`: `{-21:3.54321}`,
- `main.Z_map_2`: `{1024:1}`,
- `main.Z_map_empty`: `{}`,
- `main.Z_map_nil`: `<nil>`,
+ `main.Z_map`: `map[-21:3.54321]`,
+ `main.Z_map_2`: `map[1024:1]`,
+ `main.Z_map_empty`: `map[]`,
+ `main.Z_map_nil`: `map[]`,
`main.Z_pointer`: `0xX`,
`main.Z_pointer_nil`: `0x0`,
`main.Z_slice`: `[]uint8{115, 108, 105, 99, 101}`,
diff --git a/ogle/program/server/peek.go b/ogle/program/server/peek.go
index a2f03ce..d3c16b2 100644
--- a/ogle/program/server/peek.go
+++ b/ogle/program/server/peek.go
@@ -7,6 +7,7 @@
package server
import (
+ "errors"
"fmt"
"golang.org/x/debug/dwarf"
@@ -120,3 +121,129 @@
}
return s.peekInt(addr+uint64(f.ByteOffset), it.ByteSize)
}
+
+// peekMapValues reads a map at the given address and calls fn with the addresses for each (key, value) pair.
+// If fn returns false, peekMapValues stops.
+func (s *Server) peekMapValues(t *dwarf.MapType, a uint64, fn func(keyAddr, valAddr uint64, keyType, valType dwarf.Type) bool) error {
+ pt, ok := t.Type.(*dwarf.PtrType)
+ if !ok {
+ return errors.New("bad map type: not a pointer")
+ }
+ st, ok := pt.Type.(*dwarf.StructType)
+ if !ok {
+ return errors.New("bad map type: not a pointer to a struct")
+ }
+ // a is the address of a pointer to a struct. Get the pointer's value.
+ a, err := s.peekPtr(a)
+ if err != nil {
+ return fmt.Errorf("reading map pointer: %s", err)
+ }
+ if a == 0 {
+ // The pointer was nil, so the map is empty.
+ return nil
+ }
+ // Gather information about the struct type and the map bucket type.
+ b, err := s.peekUintStructField(st, a, "B")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ buckets, err := s.peekPtrStructField(st, a, "buckets")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ oldbuckets, err := s.peekPtrStructField(st, a, "oldbuckets")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ bf, err := getField(st, "buckets")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ bucketPtrType, ok := bf.Type.(*dwarf.PtrType)
+ if !ok {
+ return errors.New("bad map bucket type: not a pointer")
+ }
+ bt, ok := bucketPtrType.Type.(*dwarf.StructType)
+ if !ok {
+ return errors.New("bad map bucket type: not a pointer to a struct")
+ }
+ bucketSize := uint64(bucketPtrType.Type.Size())
+ tophashField, err := getField(bt, "tophash")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ bucketCnt := uint64(tophashField.Type.Size())
+ tophashFieldOffset := uint64(tophashField.ByteOffset)
+ keysField, err := getField(bt, "keys")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ keysType, ok := keysField.Type.(*dwarf.ArrayType)
+ if !ok {
+ return errors.New(`bad map bucket type: "keys" is not an array`)
+ }
+ keyType := keysType.Type
+ keysStride := uint64(keysType.StrideBitSize / 8)
+ keysFieldOffset := uint64(keysField.ByteOffset)
+ valuesField, err := getField(bt, "values")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ valuesType, ok := valuesField.Type.(*dwarf.ArrayType)
+ if !ok {
+ return errors.New(`bad map bucket type: "values" is not an array`)
+ }
+ valueType := valuesType.Type
+ valuesStride := uint64(valuesType.StrideBitSize / 8)
+ valuesFieldOffset := uint64(valuesField.ByteOffset)
+ overflowField, err := getField(bt, "overflow")
+ if err != nil {
+ return fmt.Errorf("reading map: %s", err)
+ }
+ overflowFieldOffset := uint64(overflowField.ByteOffset)
+
+ // Iterate through the two arrays of buckets.
+ bucketArrays := [2]struct {
+ addr uint64
+ size uint64
+ }{
+ {buckets, 1 << b},
+ {oldbuckets, 1 << (b - 1)},
+ }
+ for _, bucketArray := range bucketArrays {
+ if bucketArray.addr == 0 {
+ continue
+ }
+ for i := uint64(0); i < bucketArray.size; i++ {
+ bucketAddr := bucketArray.addr + i*bucketSize
+ // Iterate through the linked list of buckets.
+ // TODO: check for repeated bucket pointers.
+ for bucketAddr != 0 {
+ // Iterate through each entry in the bucket.
+ for j := uint64(0); j < bucketCnt; j++ {
+ tophash, err := s.peekUint8(bucketAddr + tophashFieldOffset + j)
+ if err != nil {
+ return errors.New("reading map: " + err.Error())
+ }
+ // From runtime/hashmap.go
+ const minTopHash = 4
+ if tophash < minTopHash {
+ continue
+ }
+ keyAddr := bucketAddr + keysFieldOffset + j*keysStride
+ valAddr := bucketAddr + valuesFieldOffset + j*valuesStride
+ if !fn(keyAddr, valAddr, keyType, valueType) {
+ return nil
+ }
+ }
+ var err error
+ bucketAddr, err = s.peekPtr(bucketAddr + overflowFieldOffset)
+ if err != nil {
+ return errors.New("reading map: " + err.Error())
+ }
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/ogle/program/server/print.go b/ogle/program/server/print.go
index b133dd3..3d671ae 100644
--- a/ogle/program/server/print.go
+++ b/ogle/program/server/print.go
@@ -391,168 +391,28 @@
const maxMapValuesToPrint = 8
func (p *Printer) printMapAt(typ *dwarf.MapType, a uint64) {
- // Maps are pointers to structs.
- pt, ok := typ.Type.(*dwarf.PtrType)
- if !ok {
- p.errorf("bad map type: not a pointer")
- return
- }
- st, ok := pt.Type.(*dwarf.StructType)
- if !ok {
- p.errorf("bad map type: not a pointer to a struct")
- return
- }
- a, err := p.server.peekPtr(a)
- if err != nil {
- p.errorf("reading map pointer: %s", err)
- return
- }
- if a == 0 {
- p.printf("<nil>")
- return
- }
- b, err := p.server.peekUintStructField(st, a, "B")
- if err != nil {
- p.errorf("reading map: %s", err)
- return
- }
- buckets, err := p.server.peekPtrStructField(st, a, "buckets")
- if err != nil {
- p.errorf("reading map: %s", err)
- return
- }
- oldbuckets, err := p.server.peekPtrStructField(st, a, "oldbuckets")
- if err != nil {
- p.errorf("reading map: %s", err)
- return
- }
-
- p.printf("{")
- // Limit how many values are printed per map.
- numValues := uint64(0)
- {
- bf, err := getField(st, "buckets")
- if err != nil {
- p.errorf("%s", err)
- } else {
- p.printMapBucketsAt(bf.Type, buckets, 1<<b, &numValues)
+ count := 0
+ fn := func(keyAddr, valAddr uint64, keyType, valType dwarf.Type) (stop bool) {
+ count++
+ if count > maxMapValuesToPrint {
+ return false
}
- }
- if b > 0 {
- bf, err := getField(st, "oldbuckets")
- if err != nil {
- p.errorf("%s", err)
- } else {
- p.printMapBucketsAt(bf.Type, oldbuckets, 1<<(b-1), &numValues)
+ if count > 1 {
+ p.printf(" ")
}
+ p.printValueAt(keyType, keyAddr)
+ p.printf(":")
+ p.printValueAt(valType, valAddr)
+ return true
}
- p.printf("}")
-}
-
-func (p *Printer) printMapBucketsAt(t dwarf.Type, a, numBuckets uint64, numValues *uint64) {
- if *numValues > maxMapValuesToPrint {
- return
+ p.printf("map[")
+ if err := p.server.peekMapValues(typ, a, fn); err != nil {
+ p.errorf("reading map values: %s", err)
}
- if a == 0 {
- return
+ if count > maxMapValuesToPrint {
+ p.printf(" ...")
}
- // From runtime/hashmap.go
- const minTopHash = 4
- // t is a pointer to a struct.
- bucketPtrType, ok := t.(*dwarf.PtrType)
- if !ok {
- p.errorf("bad map bucket type: not a pointer")
- return
- }
- bt, ok := bucketPtrType.Type.(*dwarf.StructType)
- if !ok {
- p.errorf("bad map bucket type: not a pointer to a struct")
- return
- }
- bucketSize, ok := p.sizeof(bucketPtrType.Type)
- if !ok {
- p.errorf("can't get bucket size")
- return
- }
- tophashField, err := getField(bt, "tophash")
- if err != nil {
- p.errorf("%s", err)
- return
- }
- bucketCnt, ok := p.sizeof(tophashField.Type)
- if !ok {
- p.errorf("can't get tophash size")
- return
- }
- keysField, err := getField(bt, "keys")
- if err != nil {
- p.errorf("%s", err)
- return
- }
- keysType, ok := keysField.Type.(*dwarf.ArrayType)
- if !ok {
- p.errorf(`bad map bucket type: "keys" is not an array`)
- return
- }
- keysStride, ok := p.arrayStride(keysType)
- if !ok {
- p.errorf("unknown key size")
- keysStride = 1
- }
- valuesField, err := getField(bt, "values")
- if err != nil {
- p.errorf("%s", err)
- return
- }
- valuesType, ok := valuesField.Type.(*dwarf.ArrayType)
- if !ok {
- p.errorf(`bad map bucket type: "values" is not an array`)
- return
- }
- valuesStride, ok := p.arrayStride(valuesType)
- if !ok {
- p.errorf("unknown value size")
- valuesStride = 1
- }
-
- for i := uint64(0); i < numBuckets; i++ {
- bucketAddr := a + i*bucketSize
- // TODO: check for repeated bucket pointers.
- for bucketAddr != 0 {
- for j := uint64(0); j < bucketCnt; j++ {
- tophash, err := p.server.peekUint8(bucketAddr + uint64(tophashField.ByteOffset) + j)
- if err != nil {
- p.errorf("reading map: ", err)
- return
- }
- if tophash < minTopHash {
- continue
- }
-
- // Limit how many values are printed per map.
- *numValues++
- if *numValues > maxMapValuesToPrint {
- p.printf(", ...")
- return
- }
- if *numValues > 1 {
- p.printf(", ")
- }
-
- p.printValueAt(keysType.Type,
- bucketAddr+uint64(keysField.ByteOffset)+j*keysStride)
- p.printf(":")
- p.printValueAt(valuesType.Type,
- bucketAddr+uint64(valuesField.ByteOffset)+j*valuesStride)
- }
-
- bucketAddr, err = p.server.peekPtrStructField(bt, bucketAddr, "overflow")
- if err != nil {
- p.errorf("reading map: ", err)
- return
- }
- }
- }
+ p.printf("]")
}
func (p *Printer) printChannelAt(ct *dwarf.ChanType, a uint64) {