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 +}