debug: add VarByName and Value methods to Program.

VarByName takes the name of a global variable and returns a Var which
refers to that variable.

Value takes a Var and peeks into the debugged program's memory to get
the value of the corresponding variable.

Adds Var and Value types to program.go, to represent the results of the
above methods.

In symbol.go, adds functions EntryLocation and EntryTypeOffset that
parses the values in the Location and Type attributes of a DWARF entry.

Change-Id: I9ce50b5161790648fd158f8e1e10be704d715559
Reviewed-on: https://go-review.googlesource.com/10242
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/dwarf/symbol.go b/dwarf/symbol.go
index 47e263b..6ddc836 100644
--- a/dwarf/symbol.go
+++ b/dwarf/symbol.go
@@ -65,11 +65,20 @@
 func (d *Data) LookupVariable(name string) (uint64, error) {
 	entry, err := d.lookupEntry(name, TagVariable)
 	if err != nil {
-		return 0, err
+		return 0, fmt.Errorf("variable %s: %s", name, err)
 	}
-	loc, _ := entry.Val(AttrLocation).([]byte)
+	loc, err := d.EntryLocation(entry)
+	if err != nil {
+		return 0, fmt.Errorf("variable %s: %s", name, err)
+	}
+	return loc, nil
+}
+
+// EntryLocation returns the address of the object referred to by the given Entry.
+func (d *Data) EntryLocation(e *Entry) (uint64, error) {
+	loc, _ := e.Val(AttrLocation).([]byte)
 	if len(loc) == 0 {
-		return 0, fmt.Errorf("name %q has no Location attribute", name)
+		return 0, fmt.Errorf("DWARF entry has no Location attribute")
 	}
 	// TODO: implement the DWARF Location bytecode. What we have here only
 	// recognizes a program with a single literal opAddr bytecode.
@@ -85,7 +94,20 @@
 			return d.order.Uint64(loc[1:]), nil
 		}
 	}
-	return 0, fmt.Errorf("DWARF: unimplemented Location op")
+	return 0, fmt.Errorf("DWARF entry has an unimplemented Location op")
+}
+
+// EntryTypeOffset returns the offset in the given Entry's type attribute.
+func (d *Data) EntryTypeOffset(e *Entry) (Offset, error) {
+	v := e.Val(AttrType)
+	if v == nil {
+		return 0, fmt.Errorf("DWARF entry has no Type attribute")
+	}
+	off, ok := v.(Offset)
+	if !ok {
+		return 0, fmt.Errorf("DWARF entry has an invalid Type attribute")
+	}
+	return off, nil
 }
 
 // LookupPC returns the name of a symbol at the specified PC.
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index 7c5faa3..6231783 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -14,10 +14,22 @@
 	"os/exec"
 	"testing"
 
+	"golang.org/x/debug/ogle/program"
 	"golang.org/x/debug/ogle/program/client"
 )
 
-var expected_vars = map[string]string{
+var expectedVarValues = map[string]interface{}{
+	`main.Z_int16`:  int16(-32321),
+	`main.Z_int32`:  int32(-1987654321),
+	`main.Z_int64`:  int64(-9012345678987654321),
+	`main.Z_int8`:   int8(-121),
+	`main.Z_uint16`: uint16(54321),
+	`main.Z_uint32`: uint32(3217654321),
+	`main.Z_uint64`: uint64(12345678900987654321),
+	`main.Z_uint8`:  uint8(231),
+}
+
+var expectedVars = map[string]string{
 	`main.Z_array`:               `[5]int8{-121, 121, 3, 2, 1}`,
 	`main.Z_array_empty`:         `[0]int8{}`,
 	`main.Z_bool_false`:          `false`,
@@ -126,7 +138,11 @@
 	defer os.Remove(traceeBinary)
 
 	client.OgleproxyCmd = proxyBinary
-	prog, err := client.Run("localhost", traceeBinary)
+	var (
+		prog program.Program
+		err  error
+	)
+	prog, err = client.Run("localhost", traceeBinary)
 	if err != nil {
 		log.Fatalf("Run: %v", err)
 	}
@@ -159,7 +175,7 @@
 	}
 
 	// Evaluate each of the variables found above, and check they match
-	// expected_vars.
+	// expectedVars.
 	seen := make(map[string]bool)
 	for _, v := range varnames {
 		val, err := prog.Eval("val:" + v)
@@ -174,7 +190,7 @@
 			if len(val) != 1 {
 				log.Fatalf("Should be one value for %s\n", v)
 			}
-			expected, ok := expected_vars[v]
+			expected, ok := expectedVars[v]
 			if !ok {
 				log.Fatalf("Unexpected variable %s\n", v)
 			} else {
@@ -184,7 +200,7 @@
 			}
 		}
 	}
-	for v, e := range expected_vars {
+	for v, e := range expectedVars {
 		if !seen[v] {
 			log.Fatalf("Didn't get %s = %s\n", v, e)
 		}
@@ -223,4 +239,34 @@
 	if !ok {
 		t.Errorf("Stopped at %X expected one of %X.", status.PC, pcs2)
 	}
+
+	// Check we get the expected results calling VarByName then Value
+	// for the variables in expectedVarValues.
+	for name, exp := range expectedVarValues {
+		if v, err := prog.VarByName(name); err != nil {
+			t.Errorf("VarByName(%s): %s", name, err)
+		} else if val, err := prog.Value(v); err != nil {
+			t.Errorf("Value for %s: %s", name, err)
+		} else if val != exp {
+			t.Errorf("Value for %s: got %T(%v) want %T(%v)", name, val, val, exp, exp)
+		}
+	}
+
+	// Check some error cases for VarByName and Value.
+	if _, err = prog.VarByName("not a real name"); err == nil {
+		t.Error("VarByName for invalid name: expected error")
+	}
+	if _, err = prog.Value(program.Var{}); err == nil {
+		t.Error("Value of invalid var: expected error")
+	}
+	if v, err := prog.VarByName("main.Z_int16"); err != nil {
+		t.Error("VarByName(main.Z_int16) error:", err)
+	} else {
+		v.Address = 0
+		// v now has a valid type but a bad address.
+		_, err = prog.Value(v)
+		if err == nil {
+			t.Error("Value of invalid location: expected error")
+		}
+	}
 }
diff --git a/ogle/program/client/client.go b/ogle/program/client/client.go
index c84c49c..94e4a9d 100644
--- a/ogle/program/client/client.go
+++ b/ogle/program/client/client.go
@@ -227,6 +227,20 @@
 	return resp.Frames, err
 }
 
+func (p *Program) VarByName(name string) (program.Var, error) {
+	req := proxyrpc.VarByNameRequest{Name: name}
+	var resp proxyrpc.VarByNameResponse
+	err := p.client.Call("Server.VarByName", &req, &resp)
+	return resp.Var, err
+}
+
+func (p *Program) Value(v program.Var) (program.Value, error) {
+	req := proxyrpc.ValueRequest{Var: v}
+	var resp proxyrpc.ValueResponse
+	err := p.client.Call("Server.Value", &req, &resp)
+	return 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 b633e18..9b83dc0 100644
--- a/ogle/program/program.go
+++ b/ogle/program/program.go
@@ -86,8 +86,25 @@
 	// Frames returns up to count stack frames from where the program
 	// is currently stopped.
 	Frames(count int) ([]Frame, error)
+
+	// VarByName returns a Var referring to a global variable with the given name.
+	// TODO: local variables
+	VarByName(name string) (Var, error)
+
+	// Value gets the value of a variable by reading the program's memory.
+	Value(v Var) (Value, error)
 }
 
+// A reference to a variable in a program.
+// TODO: handle variables stored in registers
+type Var struct {
+	TypeID  uint64 // A type identifier, opaque to the user.
+	Address uint64 // The address of the variable.
+}
+
+// A value read from a remote program.
+type Value interface{}
+
 // The File interface provides access to file-like resources in the program.
 // It implements only ReaderAt and WriterAt, not Reader and Writer, because
 // random access is a far more common pattern for things like symbol tables,
diff --git a/ogle/program/proxyrpc/proxyrpc.go b/ogle/program/proxyrpc/proxyrpc.go
index eef2864..e8e8276 100644
--- a/ogle/program/proxyrpc/proxyrpc.go
+++ b/ogle/program/proxyrpc/proxyrpc.go
@@ -95,3 +95,19 @@
 type FramesResponse struct {
 	Frames []program.Frame
 }
+
+type VarByNameRequest struct {
+	Name string
+}
+
+type VarByNameResponse struct {
+	Var program.Var
+}
+
+type ValueRequest struct {
+	Var program.Var
+}
+
+type ValueResponse struct {
+	Value program.Value
+}
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go
index 296e20e..3444a75 100644
--- a/ogle/program/server/server.go
+++ b/ogle/program/server/server.go
@@ -164,6 +164,10 @@
 		c.errc <- s.handleResume(req, c.resp.(*proxyrpc.ResumeResponse))
 	case *proxyrpc.RunRequest:
 		c.errc <- s.handleRun(req, c.resp.(*proxyrpc.RunResponse))
+	case *proxyrpc.VarByNameRequest:
+		c.errc <- s.handleVarByName(req, c.resp.(*proxyrpc.VarByNameResponse))
+	case *proxyrpc.ValueRequest:
+		c.errc <- s.handleValue(req, c.resp.(*proxyrpc.ValueResponse))
 	default:
 		panic(fmt.Sprintf("unexpected call request type %T", c.req))
 	}
@@ -697,3 +701,41 @@
 	}
 	return false
 }
+
+func (s *Server) VarByName(req *proxyrpc.VarByNameRequest, resp *proxyrpc.VarByNameResponse) error {
+	return s.call(s.otherc, req, resp)
+}
+
+func (s *Server) handleVarByName(req *proxyrpc.VarByNameRequest, resp *proxyrpc.VarByNameResponse) error {
+	entry, err := s.dwarfData.LookupEntry(req.Name)
+	if err != nil {
+		return fmt.Errorf("variable %s: %s", req.Name, err)
+	}
+
+	loc, err := s.dwarfData.EntryLocation(entry)
+	if err != nil {
+		return fmt.Errorf("variable %s: %s", req.Name, err)
+	}
+
+	off, err := s.dwarfData.EntryTypeOffset(entry)
+	if err != nil {
+		return fmt.Errorf("variable %s: %s", req.Name, err)
+	}
+
+	resp.Var.TypeID = uint64(off)
+	resp.Var.Address = loc
+	return nil
+}
+
+func (s *Server) Value(req *proxyrpc.ValueRequest, resp *proxyrpc.ValueResponse) error {
+	return s.call(s.otherc, req, resp)
+}
+
+func (s *Server) handleValue(req *proxyrpc.ValueRequest, resp *proxyrpc.ValueResponse) error {
+	t, err := s.dwarfData.Type(dwarf.Offset(req.Var.TypeID))
+	if err != nil {
+		return err
+	}
+	resp.Value, err = s.value(t, req.Var.Address)
+	return err
+}
diff --git a/ogle/program/server/value.go b/ogle/program/server/value.go
new file mode 100644
index 0000000..c273b07
--- /dev/null
+++ b/ogle/program/server/value.go
@@ -0,0 +1,67 @@
+// Copyright 2015 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 server
+
+import (
+	"fmt"
+	"golang.org/x/debug/dwarf"
+	"golang.org/x/debug/ogle/program"
+)
+
+// value peeks the program's memory at the given address, parsing it as a value of type t.
+func (s *Server) value(t dwarf.Type, addr uint64) (program.Value, error) {
+	// readInt reads the memory for an n-byte integer or unsigned integer.
+	readInt := func(n int64) ([]byte, error) {
+		switch n {
+		case 1, 2, 4, 8:
+		default:
+			return nil, fmt.Errorf("invalid size: %d", n)
+		}
+		buf := make([]byte, n)
+		if err := s.peek(uintptr(addr), buf); err != nil {
+			return nil, err
+		}
+		return buf, nil
+	}
+
+	switch t := t.(type) {
+	case *dwarf.IntType:
+		bs := t.Common().ByteSize
+		buf, err := readInt(bs)
+		if err != nil {
+			return nil, fmt.Errorf("reading integer: %s", err)
+		}
+		x := s.arch.IntN(buf)
+		switch bs {
+		case 1:
+			return int8(x), nil
+		case 2:
+			return int16(x), nil
+		case 4:
+			return int32(x), nil
+		case 8:
+			return int64(x), nil
+		}
+	case *dwarf.UintType:
+		bs := t.Common().ByteSize
+		buf, err := readInt(bs)
+		if err != nil {
+			return nil, fmt.Errorf("reading unsigned integer: %s", err)
+		}
+		x := s.arch.UintN(buf)
+		switch bs {
+		case 1:
+			return uint8(x), nil
+		case 2:
+			return uint16(x), nil
+		case 4:
+			return uint32(x), nil
+		case 8:
+			return uint64(x), nil
+		}
+		// TODO: more types
+	}
+	return nil, fmt.Errorf("Unsupported type %T", t)
+}