ogle/program: first cut of breakpoint support.
LGTM=r
R=r
https://golang.org/cl/78530044
diff --git a/program/client/client.go b/program/client/client.go
index 3b3a3df..c376ae9 100644
--- a/program/client/client.go
+++ b/program/client/client.go
@@ -159,7 +159,15 @@
}
func (p *Program) Run(start bool) (program.Status, error) {
- panic("unimplemented")
+ req := proxyrpc.RunRequest{
+ Start: start,
+ }
+ var resp proxyrpc.RunResponse
+ err := p.client.Call("Server.Run", &req, &resp)
+ if err != nil {
+ return program.Status{}, err
+ }
+ return resp.Status, nil
}
func (p *Program) Stop() (program.Status, error) {
@@ -167,7 +175,13 @@
}
func (p *Program) Resume() (program.Status, error) {
- panic("unimplemented")
+ req := proxyrpc.ResumeRequest{}
+ var resp proxyrpc.ResumeResponse
+ err := p.client.Call("Server.Resume", &req, &resp)
+ if err != nil {
+ return program.Status{}, err
+ }
+ return resp.Status, nil
}
func (p *Program) Kill() (program.Status, error) {
@@ -175,7 +189,11 @@
}
func (p *Program) Breakpoint(address string) error {
- panic("unimplemented")
+ req := proxyrpc.BreakpointRequest{
+ Address: address,
+ }
+ var resp proxyrpc.BreakpointResponse
+ return p.client.Call("Server.Breakpoint", &req, &resp)
}
func (p *Program) DeleteBreakpoint(address string) error {
diff --git a/program/program.go b/program/program.go
index d27da54..c91fc47 100644
--- a/program/program.go
+++ b/program/program.go
@@ -87,5 +87,5 @@
}
type Status struct {
- // TBD
+ PC, SP uint64
}
diff --git a/program/proxyrpc/proxyrpc.go b/program/proxyrpc/proxyrpc.go
index b2a2977..320bba6 100644
--- a/program/proxyrpc/proxyrpc.go
+++ b/program/proxyrpc/proxyrpc.go
@@ -6,6 +6,8 @@
// used to the ogleproxy.
package proxyrpc
+import "code.google.com/p/ogle/program"
+
// For regularity, each method has a unique Request and a Response type even
// when not strictly necessary.
@@ -49,6 +51,28 @@
FD int
}
+type RunRequest struct {
+ Start bool
+}
+
+type RunResponse struct {
+ Status program.Status
+}
+
+type ResumeRequest struct {
+}
+
+type ResumeResponse struct {
+ Status program.Status
+}
+
+type BreakpointRequest struct {
+ Address string
+}
+
+type BreakpointResponse struct {
+}
+
type EvalRequest struct {
Expr string
}
diff --git a/program/server/ptrace.go b/program/server/ptrace.go
new file mode 100644
index 0000000..6b6ad39
--- /dev/null
+++ b/program/server/ptrace.go
@@ -0,0 +1,96 @@
+// Copyright 2014 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
+
+// TODO: syscall.PTRACE_O_TRACECLONE shenanigans to trace multi-threaded
+// programs.
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "syscall"
+)
+
+// ptraceRun runs all the closures from fc on a dedicated OS thread. Errors
+// are returned on ec. Both channels must be unbuffered, to ensure that the
+// resultant error is sent back to the same goroutine that sent the closure.
+func ptraceRun(fc chan func() error, ec chan error) {
+ if cap(fc) != 0 || cap(ec) != 0 {
+ panic("ptraceRun was given unbuffered channels")
+ }
+ runtime.LockOSThread()
+ for f := range fc {
+ ec <- f()
+ }
+}
+
+func (s *Server) startProcess(name string, argv []string, attr *os.ProcAttr) (proc *os.Process, err error) {
+ s.fc <- func() error {
+ var err1 error
+ proc, err1 = os.StartProcess(name, argv, attr)
+ return err1
+ }
+ return proc, <-s.ec
+}
+
+func (s *Server) ptraceCont(pid int, signal int) (err error) {
+ s.fc <- func() error {
+ return syscall.PtraceCont(pid, signal)
+ }
+ return <-s.ec
+}
+
+func (s *Server) ptraceGetRegs(pid int, regsout *syscall.PtraceRegs) (err error) {
+ s.fc <- func() error {
+ return syscall.PtraceGetRegs(pid, regsout)
+ }
+ return <-s.ec
+}
+
+func (s *Server) ptracePeek(pid int, addr uintptr, out []byte) (err error) {
+ s.fc <- func() error {
+ n, err := syscall.PtracePeekText(pid, addr, out)
+ if err != nil {
+ return err
+ }
+ if n != len(out) {
+ return fmt.Errorf("ptracePeek: peeked %d bytes, want %d", n, len(out))
+ }
+ return nil
+ }
+ return <-s.ec
+}
+
+func (s *Server) ptracePoke(pid int, addr uintptr, data []byte) (err error) {
+ s.fc <- func() error {
+ n, err := syscall.PtracePokeText(pid, addr, data)
+ if err != nil {
+ return err
+ }
+ if n != len(data) {
+ return fmt.Errorf("ptracePoke: poked %d bytes, want %d", n, len(data))
+ }
+ return nil
+ }
+ return <-s.ec
+}
+
+func (s *Server) ptraceSingleStep(pid int) (err error) {
+ s.fc <- func() error {
+ return syscall.PtraceSingleStep(pid)
+ }
+ return <-s.ec
+}
+
+func (s *Server) wait() (err error) {
+ var status syscall.WaitStatus
+ s.fc <- func() error {
+ _, err1 := syscall.Wait4(-1, &status, 0, nil)
+ return err1
+ }
+ // TODO: do something with status.
+ return <-s.ec
+}
diff --git a/program/server/server.go b/program/server/server.go
index 6160966..eed4c11 100644
--- a/program/server/server.go
+++ b/program/server/server.go
@@ -9,6 +9,9 @@
import (
"fmt"
"os"
+ "strconv"
+ "sync"
+ "syscall"
"debug/elf"
"debug/macho"
@@ -19,12 +22,24 @@
"code.google.com/p/ogle/program/proxyrpc"
)
+type breakpoint struct {
+ pc uint64
+ origInstr byte // TODO: don't be amd64-specific.
+}
+
type Server struct {
executable string // Name of executable.
lines *gosym.LineTable
symbols *gosym.Table
- // TODO: Needs mutex.
- files []*file // Index == file descriptor.
+
+ mu sync.Mutex
+
+ fc chan func() error
+ ec chan error
+
+ proc *os.Process
+ breakpoints map[uint64]breakpoint
+ files []*file // Index == file descriptor.
}
// New parses the executable and builds local data structures for answering requests.
@@ -45,10 +60,14 @@
return nil, err
}
srv := &Server{
- executable: executable,
- lines: lines,
- symbols: symbols,
+ executable: executable,
+ lines: lines,
+ symbols: symbols,
+ fc: make(chan func() error),
+ ec: make(chan error),
+ breakpoints: make(map[uint64]breakpoint),
}
+ go ptraceRun(srv.fc, srv.ec)
return srv, nil
}
@@ -117,6 +136,9 @@
}
func (s *Server) Open(req *proxyrpc.OpenRequest, resp *proxyrpc.OpenResponse) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
// TODO: Better simulation. For now we just open the named OS file.
var flag int
switch req.Mode {
@@ -151,6 +173,9 @@
}
func (s *Server) ReadAt(req *proxyrpc.ReadAtRequest, resp *proxyrpc.ReadAtResponse) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
fd := req.FD
if fd < 0 || len(s.files) <= fd || s.files[fd] == nil {
return fmt.Errorf("ReadAt: bad file descriptor %d", fd)
@@ -163,6 +188,9 @@
}
func (s *Server) Close(req *proxyrpc.CloseRequest, resp *proxyrpc.CloseResponse) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
fd := req.FD
if fd < 0 || fd >= len(s.files) || s.files[fd] == nil {
return fmt.Errorf("Close: bad file descriptor %d", fd)
@@ -173,14 +201,139 @@
return err
}
-func (s *Server) Eval(req *proxyrpc.EvalRequest, resp *proxyrpc.EvalResponse) error {
- expr := req.Expr
+func (s *Server) Run(req *proxyrpc.RunRequest, resp *proxyrpc.RunResponse) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ if s.proc != nil {
+ s.proc.Kill()
+ s.proc = nil
+ }
+ p, err := s.startProcess(s.executable, nil, &os.ProcAttr{
+ Files: []*os.File{
+ nil, // TODO: be able to feed the target's stdin.
+ os.Stderr, // TODO: be able to capture the target's stdout.
+ os.Stderr,
+ },
+ Sys: &syscall.SysProcAttr{
+ Ptrace: !req.Start,
+ },
+ })
+ if err != nil {
+ return err
+ }
+ s.proc = p
+
+ if !req.Start {
+ // TODO: wait until /proc/{s.proc.Pid}/status says "State: t (tracing stop)".
+ }
+ return nil
+}
+
+func (s *Server) Resume(req *proxyrpc.ResumeRequest, resp *proxyrpc.ResumeResponse) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ err := s.ptraceCont(s.proc.Pid, 0)
+ if err != nil {
+ return err
+ }
+
+ err = s.wait()
+ if err != nil {
+ return err
+ }
+
+ regs := syscall.PtraceRegs{}
+ err = s.ptraceGetRegs(s.proc.Pid, ®s)
+ if err != nil {
+ return err
+ }
+
+ resp.Status.PC = regs.Rip
+ resp.Status.SP = regs.Rsp
+
+ // If we're stopped on a breakpoint, restore the original code,
+ // step through a single instruction, and reset the breakpoint.
+ // TODO: should this happen here or just before the ptraceCont call?
+ bp, ok := s.breakpoints[regs.Rip-1] // TODO: -1 because on amd64, INT 3 is 1 byte (0xcc).
+ if ok {
+ pc := uintptr(regs.Rip - 1)
+ err := s.ptracePoke(s.proc.Pid, pc, []byte{bp.origInstr})
+ if err != nil {
+ return fmt.Errorf("ptracePoke: %v", err)
+ }
+
+ err = s.ptraceSingleStep(s.proc.Pid)
+ if err != nil {
+ return fmt.Errorf("ptraceSingleStep: %v", err)
+ }
+
+ buf := make([]byte, 1)
+ buf[0] = 0xcc // INT 3 instruction on x86.
+ err = s.ptracePoke(s.proc.Pid, pc, buf)
+ if err != nil {
+ return fmt.Errorf("ptracePoke: %v", err)
+ }
+ }
+
+ return nil
+}
+
+func (s *Server) Breakpoint(req *proxyrpc.BreakpointRequest, resp *proxyrpc.BreakpointResponse) (err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ addr, err := s.eval(req.Address)
+ if err != nil {
+ return fmt.Errorf("could not parse %q: %v", req.Address, err)
+ }
+ pc, err := strconv.ParseUint(addr, 0, 0)
+ if err != nil {
+ return fmt.Errorf("ParseUint: %v", err)
+ }
+
+ buf := make([]byte, 1)
+ err = s.ptracePeek(s.proc.Pid, uintptr(pc), buf)
+ if err != nil {
+ return fmt.Errorf("ptracePoke: %v", err)
+ }
+
+ s.breakpoints[pc] = breakpoint{pc: pc, origInstr: buf[0]}
+
+ buf[0] = 0xcc // INT 3 instruction on x86.
+ err = s.ptracePoke(s.proc.Pid, uintptr(pc), buf)
+ if err != nil {
+ return fmt.Errorf("ptracePoke: %v", err)
+ }
+
+ return nil
+}
+
+func (s *Server) Eval(req *proxyrpc.EvalRequest, resp *proxyrpc.EvalResponse) (err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ resp.Result, err = s.eval(req.Expr)
+ return err
+}
+
+func (s *Server) eval(expr string) (string, error) {
// Simple version: Turn symbol into address. Must be function.
// TODO: Why is Table.Syms always empty?
sym := s.symbols.LookupFunc(expr)
- if sym == nil {
- return fmt.Errorf("symbol %q not found")
+ if sym != nil {
+ return fmt.Sprintf("%#x", sym.Value), nil
}
- resp.Result = fmt.Sprintf("%#x", sym.Value)
- return nil
+
+ addr, err := strconv.ParseUint(expr, 0, 0)
+ if err != nil {
+ return "", fmt.Errorf("address %q not found: %v", expr, err)
+ }
+
+ fun := s.symbols.PCToFunc(addr)
+ if fun == nil {
+ return "", fmt.Errorf("address %q has no func", expr)
+ }
+ return fun.Sym.Name, nil
}