ogle/program: add Frames protocol, to print function arguments.

LGTM=r
R=r
https://golang.org/cl/81580044
diff --git a/program/client/client.go b/program/client/client.go
index 1477677..bc585b1 100644
--- a/program/client/client.go
+++ b/program/client/client.go
@@ -213,6 +213,15 @@
 	return resp.Result, err
 }
 
+func (p *Program) Frames(count int) ([]program.Frame, error) {
+	req := proxyrpc.FramesRequest{
+		Count: count,
+	}
+	var resp proxyrpc.FramesResponse
+	err := p.client.Call("Server.Frames", &req, &resp)
+	return resp.Frames, err
+}
+
 // File implements the program.File interface, providing access
 // to file-like resources associated with the target program.
 type File struct {
diff --git a/program/program.go b/program/program.go
index 55657d6..bc71091 100644
--- a/program/program.go
+++ b/program/program.go
@@ -81,6 +81,10 @@
 	//		Returns a one-element list holding the name of the
 	//		symbol ("main.foo") at that address (hex, octal, decimal).
 	Eval(expr string) ([]string, error)
+
+	// Frames returns up to count stack frames from where the program
+	// is currently stopped.
+	Frames(count int) ([]Frame, error)
 }
 
 // The File interface provides access to file-like resources in the program.
@@ -97,3 +101,7 @@
 type Status struct {
 	PC, SP uint64
 }
+
+type Frame struct {
+	S string
+}
diff --git a/program/proxyrpc/proxyrpc.go b/program/proxyrpc/proxyrpc.go
index 16fb9f1..eeef340 100644
--- a/program/proxyrpc/proxyrpc.go
+++ b/program/proxyrpc/proxyrpc.go
@@ -80,3 +80,11 @@
 type EvalResponse struct {
 	Result []string
 }
+
+type FramesRequest struct {
+	Count int
+}
+
+type FramesResponse struct {
+	Frames []program.Frame
+}
diff --git a/program/server/dwarf.go b/program/server/dwarf.go
index 44321bc..838f621 100644
--- a/program/server/dwarf.go
+++ b/program/server/dwarf.go
@@ -75,11 +75,28 @@
 }
 
 func (s *Server) lookupPC(pc uint64) (string, error) {
+	entry, err := s.entryForPC(pc)
+	if err != nil {
+		return "", err
+	}
+	nameAttr := lookupAttr(entry, dwarf.AttrName)
+	if nameAttr == nil {
+		// TODO: this shouldn't be possible.
+		return "", fmt.Errorf("TODO")
+	}
+	name, ok := nameAttr.(string)
+	if !ok {
+		return "", fmt.Errorf("name for PC %#x is not a string", pc)
+	}
+	return name, nil
+}
+
+func (s *Server) entryForPC(pc uint64) (*dwarf.Entry, error) {
 	r := s.dwarfData.Reader()
 	for {
 		entry, err := r.Next()
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 		if entry == nil {
 			// TODO: why don't we get an error here.
@@ -93,18 +110,9 @@
 		if !lok || !hok || pc < lowpc || highpc <= pc {
 			continue
 		}
-		nameAttr := lookupAttr(entry, dwarf.AttrName)
-		if nameAttr == nil {
-			// TODO: this shouldn't be possible.
-			continue
-		}
-		name, ok := nameAttr.(string)
-		if !ok {
-			return "", fmt.Errorf("name for PC %#x is not a string", pc)
-		}
-		return name, nil
+		return entry, nil
 	}
-	return "", fmt.Errorf("PC %#x not found", pc)
+	return nil, fmt.Errorf("PC %#x not found", pc)
 }
 
 func lookupAttr(e *dwarf.Entry, a dwarf.Attr) interface{} {
@@ -116,21 +124,22 @@
 	return nil
 }
 
-func evalLocation(v []uint8) string {
+// TODO: signedness? Return (x int64, ok bool)??
+func evalLocation(v []uint8) int64 {
 	if len(v) == 0 {
-		return "<nil>"
+		return 0
 	}
 	if v[0] != 0x9C { // DW_OP_call_frame_cfa
-		return "UNK0"
+		return 0
 	}
 	if len(v) == 1 {
-		return "0"
+		return 0
 	}
 	v = v[1:]
 	if v[0] != 0x11 { // DW_OP_consts
-		return "UNK1"
+		return 0
 	}
-	return fmt.Sprintf("%x", sleb128(v[1:]))
+	return sleb128(v[1:])
 }
 
 func uleb128(v []uint8) (u uint64) {
diff --git a/program/server/server.go b/program/server/server.go
index 372d172..745e5ac 100644
--- a/program/server/server.go
+++ b/program/server/server.go
@@ -7,6 +7,7 @@
 package server
 
 import (
+	"encoding/binary"
 	"fmt"
 	"os"
 	"regexp"
@@ -328,3 +329,82 @@
 
 	return addr, nil
 }
+
+func (s *Server) Frames(req *proxyrpc.FramesRequest, resp *proxyrpc.FramesResponse) error {
+	// TODO: verify that we're stopped.
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	if req.Count != 1 {
+		// TODO: implement.
+		return fmt.Errorf("Frames.Count != 1 is not implemented")
+	}
+
+	// TODO: we're assuming we're at a function's entry point (LowPC).
+
+	regs := syscall.PtraceRegs{}
+	err := s.ptraceGetRegs(s.proc.Pid, &regs)
+	if err != nil {
+		return err
+	}
+	fp := regs.Rsp + 8 // TODO: 8 for the return address is amd64 specific.
+
+	// TODO: the -1 on the next line backs up the INT3 breakpoint. Should it be there?
+	entry, err := s.entryForPC(regs.Rip - 1)
+	if err != nil {
+		return err
+	}
+
+	var buf [8]byte
+	frame := program.Frame{}
+	r := s.dwarfData.Reader()
+	r.Seek(entry.Offset)
+	for {
+		entry, err := r.Next()
+		if err != nil {
+			return err
+		}
+		if entry.Tag == 0 {
+			break
+		}
+		if entry.Tag != dwarf.TagFormalParameter {
+			continue
+		}
+		if entry.Children {
+			// TODO: handle this??
+			return fmt.Errorf("FormalParameter has children, expected none")
+		}
+		// TODO: the returned frame should be structured instead of a hacked up string.
+		location := uintptr(0)
+		for _, f := range entry.Field {
+			switch f.Attr {
+			case dwarf.AttrLocation:
+				offset := evalLocation(f.Val.([]uint8))
+				location = uintptr(int64(fp) + offset)
+				frame.S += fmt.Sprintf("(%d(FP))", offset)
+			case dwarf.AttrName:
+				frame.S += " " + f.Val.(string)
+			case dwarf.AttrType:
+				t, err := s.dwarfData.Type(f.Val.(dwarf.Offset))
+				if err == nil {
+					frame.S += fmt.Sprintf("[%v]", t)
+				}
+				// TODO: don't assume amd64.
+				if t.String() != "int" && t.Size() == 8 {
+					break
+				}
+				if location == 0 {
+					return fmt.Errorf("no location for FormalParameter")
+				}
+				err = s.ptracePeek(s.proc.Pid, location, buf[:8])
+				if err != nil {
+					return err
+				}
+				// TODO: don't assume little-endian.
+				frame.S += fmt.Sprintf("==%#x", binary.LittleEndian.Uint64(buf[:8]))
+			}
+		}
+	}
+	resp.Frames = append(resp.Frames, frame)
+	return nil
+}