x/debug: support getting values of map type.

Calling program.Value to get the value of a map returns a program.Map
struct, which contains the type identifier, address, and length of the
map.

Users can then call program.MapElement to get variable references for
each element's key and value.  The values of those variables can be
found by calling program.Value again.

Change-Id: Ib9d5d9d5ca00a121e4b13b16c3a637c6dc168119
Reviewed-on: https://go-review.googlesource.com/13657
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index b2ba5d7..4660ec4 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -62,6 +62,7 @@
 	`main.Z_interface_typed_nil`: `("*main.FooStruct", <nil>)`,
 	`main.Z_map`:                 `map[-21:3.54321]`,
 	`main.Z_map_2`:               `map[1024:1]`,
+	`main.Z_map_3`:               `map[1024:1 512:-1]`,
 	`main.Z_map_empty`:           `map[]`,
 	`main.Z_map_nil`:             `map[]`,
 	`main.Z_pointer`:             `0xX`,
@@ -433,4 +434,71 @@
 		}
 		return nil
 	})
+
+	checkValue("main.Z_map_empty", func(val program.Value) error {
+		m, ok := val.(program.Map)
+		if !ok {
+			return fmt.Errorf("got %T(%v) expected Map", val, val)
+		}
+		if m.Length != 0 {
+			return fmt.Errorf("got map length %d expected 0", m.Length)
+		}
+		return nil
+	})
+
+	checkValue("main.Z_map_nil", func(val program.Value) error {
+		m, ok := val.(program.Map)
+		if !ok {
+			return fmt.Errorf("got %T(%v) expected Map", val, val)
+		}
+		if m.Length != 0 {
+			return fmt.Errorf("got map length %d expected 0", m.Length)
+		}
+		return nil
+	})
+
+	checkValue("main.Z_map_3", func(val program.Value) error {
+		m, ok := val.(program.Map)
+		if !ok {
+			return fmt.Errorf("got %T(%v) expected Map", val, val)
+		}
+		if m.Length != 2 {
+			return fmt.Errorf("got map length %d expected 2", m.Length)
+		}
+		keyVar0, valVar0, err := prog.MapElement(m, 0)
+		if err != nil {
+			return err
+		}
+		keyVar1, valVar1, err := prog.MapElement(m, 1)
+		if err != nil {
+			return err
+		}
+		key0, err := prog.Value(keyVar0)
+		if err != nil {
+			return err
+		}
+		key1, err := prog.Value(keyVar1)
+		if err != nil {
+			return err
+		}
+		val0, err := prog.Value(valVar0)
+		if err != nil {
+			return err
+		}
+		val1, err := prog.Value(valVar1)
+		if err != nil {
+			return err
+		}
+		// The map should contain 1024,1 and 512,-1 in some order.
+		ok1 := key0 == int16(1024) && val0 == int8(1) && key1 == int16(512) && val1 == int8(-1)
+		ok2 := key1 == int16(1024) && val1 == int8(1) && key0 == int16(512) && val0 == int8(-1)
+		if !ok1 && !ok2 {
+			return fmt.Errorf("got values (%d,%d) and (%d,%d), expected (1024,1) and (512,-1) in some order", key0, val0, key1, val1)
+		}
+		_, _, err = prog.MapElement(m, 2)
+		if err == nil {
+			return fmt.Errorf("MapElement: reading at a bad index succeeded, expected error")
+		}
+		return nil
+	})
 }
diff --git a/ogle/demo/tracee/main.go b/ogle/demo/tracee/main.go
index 4fbf63b..7051b30 100644
--- a/ogle/demo/tracee/main.go
+++ b/ogle/demo/tracee/main.go
@@ -57,6 +57,7 @@
 	Z_interface_nil       FooInterface
 	Z_map                 map[int8]float32 = map[int8]float32{-21: 3.54321}
 	Z_map_2               map[int16]int8   = map[int16]int8{1024: 1}
+	Z_map_3               map[int16]int8   = map[int16]int8{1024: 1, 512: -1}
 	Z_map_empty           map[int8]float32 = map[int8]float32{}
 	Z_map_nil             map[int8]float32
 	Z_pointer             *FooStruct = &Z_struct
@@ -79,7 +80,7 @@
 	fmt.Println(Z_channel, Z_channel_buffered, Z_channel_nil)
 	fmt.Println(Z_func_bar, Z_func_int8_r_int8, Z_func_int8_r_pint8)
 	fmt.Println(Z_interface, Z_interface_nil, Z_interface_typed_nil)
-	fmt.Println(Z_map, Z_map_2, Z_map_empty, Z_map_nil)
+	fmt.Println(Z_map, Z_map_2, Z_map_3, Z_map_empty, Z_map_nil)
 	fmt.Println(Z_pointer, Z_pointer_nil)
 	fmt.Println(Z_slice, Z_slice_2, Z_slice_nil)
 	fmt.Println(Z_string, Z_struct)
diff --git a/ogle/program/client/client.go b/ogle/program/client/client.go
index 2464a6d..6841767 100644
--- a/ogle/program/client/client.go
+++ b/ogle/program/client/client.go
@@ -246,6 +246,13 @@
 	return resp.Value, err
 }
 
+func (p *Program) MapElement(m program.Map, index uint64) (program.Var, program.Var, error) {
+	req := proxyrpc.MapElementRequest{Map: m, Index: index}
+	var resp proxyrpc.MapElementResponse
+	err := p.client.Call("Server.MapElement", &req, &resp)
+	return resp.Key, resp.Value, err
+}
+
 // File implements the program.File interface, providing access
 // to file-like resources associated with the target program.
 type File struct {
diff --git a/ogle/program/local/local.go b/ogle/program/local/local.go
index 02c7d0c..cbbb681 100644
--- a/ogle/program/local/local.go
+++ b/ogle/program/local/local.go
@@ -131,6 +131,13 @@
 	return resp.Value, err
 }
 
+func (l *Local) MapElement(m program.Map, index uint64) (program.Var, program.Var, error) {
+	req := proxyrpc.MapElementRequest{Map: m, Index: index}
+	var resp proxyrpc.MapElementResponse
+	err := l.s.MapElement(&req, &resp)
+	return resp.Key, resp.Value, err
+}
+
 // File implements the program.File interface, providing access
 // to file-like resources associated with the target program.
 type File struct {
diff --git a/ogle/program/program.go b/ogle/program/program.go
index 9e45c26..367c7d7 100644
--- a/ogle/program/program.go
+++ b/ogle/program/program.go
@@ -87,6 +87,10 @@
 
 	// Value gets the value of a variable by reading the program's memory.
 	Value(v Var) (Value, error)
+
+	// MapElement returns Vars for the key and value of a map element specified by
+	// a 0-based index.
+	MapElement(m Map, index uint64) (Var, Var, error)
 }
 
 // A reference to a variable in a program.
@@ -129,6 +133,13 @@
 	Capacity uint64
 }
 
+// Map is a Var representing a map.
+type Map struct {
+	TypeID  uint64
+	Address uint64
+	Length  uint64 // Number of elements in the map.
+}
+
 // Struct is a Var representing a struct.
 type Struct struct {
 	Fields []StructField
diff --git a/ogle/program/proxyrpc/proxyrpc.go b/ogle/program/proxyrpc/proxyrpc.go
index 568fdea..dc2de15 100644
--- a/ogle/program/proxyrpc/proxyrpc.go
+++ b/ogle/program/proxyrpc/proxyrpc.go
@@ -18,6 +18,7 @@
 	gob.Register(program.Array{})
 	gob.Register(program.Struct{})
 	gob.Register(program.Slice{})
+	gob.Register(program.Map{})
 }
 
 // For regularity, each method has a unique Request and a Response type even
@@ -133,3 +134,13 @@
 type ValueResponse struct {
 	Value program.Value
 }
+
+type MapElementRequest struct {
+	Map   program.Map
+	Index uint64
+}
+
+type MapElementResponse struct {
+	Key   program.Var
+	Value program.Var
+}
diff --git a/ogle/program/server/peek.go b/ogle/program/server/peek.go
index d3c16b2..e61a974 100644
--- a/ogle/program/server/peek.go
+++ b/ogle/program/server/peek.go
@@ -122,21 +122,32 @@
 	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 {
+// peekMapLocationAndType returns the address and DWARF type of the underlying
+// struct of a map variable.
+func (s *Server) peekMapLocationAndType(t *dwarf.MapType, a uint64) (uint64, *dwarf.StructType, error) {
+	// Maps are pointers to structs.
 	pt, ok := t.Type.(*dwarf.PtrType)
 	if !ok {
-		return errors.New("bad map type: not a pointer")
+		return 0, nil, 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")
+		return 0, nil, 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)
+		return 0, nil, fmt.Errorf("reading map pointer: %s", err)
+	}
+	return a, st, nil
+}
+
+// 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 {
+	a, st, err := s.peekMapLocationAndType(t, a)
+	if err != nil {
+		return err
 	}
 	if a == 0 {
 		// The pointer was nil, so the map is empty.
@@ -247,3 +258,20 @@
 
 	return nil
 }
+
+// peekMapLength returns the number of elements in a map at the given address.
+func (s *Server) peekMapLength(t *dwarf.MapType, a uint64) (uint64, error) {
+	a, st, err := s.peekMapLocationAndType(t, a)
+	if err != nil {
+		return 0, err
+	}
+	if a == 0 {
+		// The pointer was nil, so the map is empty.
+		return 0, nil
+	}
+	length, err := s.peekUintOrIntStructField(st, a, "count")
+	if err != nil {
+		return 0, fmt.Errorf("reading map: %s", err)
+	}
+	return uint64(length), nil
+}
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go
index ac9dc92..4b44581 100644
--- a/ogle/program/server/server.go
+++ b/ogle/program/server/server.go
@@ -172,6 +172,8 @@
 		c.errc <- s.handleVarByName(req, c.resp.(*proxyrpc.VarByNameResponse))
 	case *proxyrpc.ValueRequest:
 		c.errc <- s.handleValue(req, c.resp.(*proxyrpc.ValueResponse))
+	case *proxyrpc.MapElementRequest:
+		c.errc <- s.handleMapElement(req, c.resp.(*proxyrpc.MapElementResponse))
 	default:
 		panic(fmt.Sprintf("unexpected call request type %T", c.req))
 	}
@@ -782,3 +784,39 @@
 	resp.Value, err = s.value(t, req.Var.Address)
 	return err
 }
+
+func (s *Server) MapElement(req *proxyrpc.MapElementRequest, resp *proxyrpc.MapElementResponse) error {
+	return s.call(s.otherc, req, resp)
+}
+
+func (s *Server) handleMapElement(req *proxyrpc.MapElementRequest, resp *proxyrpc.MapElementResponse) error {
+	t, err := s.dwarfData.Type(dwarf.Offset(req.Map.TypeID))
+	if err != nil {
+		return err
+	}
+	m, ok := t.(*dwarf.MapType)
+	if !ok {
+		return fmt.Errorf("variable is not a map")
+	}
+	var count uint64
+	// fn will be called for each element of the map.
+	// When we reach the requested element, we fill in *resp and stop.
+	// TODO: cache locations of elements.
+	fn := func(keyAddr, valAddr uint64, keyType, valType dwarf.Type) bool {
+		count++
+		if count == req.Index+1 {
+			resp.Key = program.Var{TypeID: uint64(keyType.Common().Offset), Address: keyAddr}
+			resp.Value = program.Var{TypeID: uint64(valType.Common().Offset), Address: valAddr}
+			return false
+		}
+		return true
+	}
+	if err := s.peekMapValues(m, req.Map.Address, fn); err != nil {
+		return err
+	}
+	if count <= req.Index {
+		// There weren't enough elements.
+		return fmt.Errorf("map has no element %d", req.Index)
+	}
+	return nil
+}
diff --git a/ogle/program/server/value.go b/ogle/program/server/value.go
index bbb1e4e..dea3b85 100644
--- a/ogle/program/server/value.go
+++ b/ogle/program/server/value.go
@@ -170,6 +170,16 @@
 		return program.Struct{fields}, nil
 	case *dwarf.TypedefType:
 		return s.value(t.Type, addr)
+	case *dwarf.MapType:
+		length, err := s.peekMapLength(t, addr)
+		if err != nil {
+			return nil, err
+		}
+		return program.Map{
+			TypeID:  uint64(t.Common().Offset),
+			Address: addr,
+			Length:  length,
+		}, nil
 		// TODO: more types
 	}
 	return nil, fmt.Errorf("Unsupported type %T", t)