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, ®s)
+ 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
+}