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) {