Add command to restart process

Fixes #95
This commit is contained in:
Derek Parker 2015-07-03 15:35:22 -05:00
parent d2b8d57053
commit 2f7612d4af
8 changed files with 130 additions and 7 deletions

@ -2,10 +2,6 @@
UNAME = $(shell uname)
PREFIX=github.com/derekparker/delve
ifndef $(RUN)
RUN = ".*"
endif
build:
go build github.com/derekparker/delve/cmd/dlv
ifeq "$(UNAME)" "Darwin"

@ -7,9 +7,15 @@ import (
// Client represents a debugger service client. All client methods are
// synchronous.
type Client interface {
// Returns the pid of the process we are debugging.
ProcessPid() int
// Detach detaches the debugger, optionally killing the process.
Detach(killProcess bool) error
// Restarts program.
Restart() error
// GetState returns the current debugger state.
GetState() (*api.DebuggerState, error)

@ -8,6 +8,7 @@ import (
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/service/api"
sys "golang.org/x/sys/unix"
)
// Debugger service.
@ -61,10 +62,35 @@ func New(config *Config) (*Debugger, error) {
return d, nil
}
func (d *Debugger) ProcessPid() int {
return d.process.Pid
}
func (d *Debugger) Detach(kill bool) error {
return d.process.Detach(kill)
}
func (d *Debugger) Restart() error {
if !d.process.Exited() {
if d.process.Running() {
d.process.Halt()
}
// Ensure the process is in a PTRACE_STOP.
if err := sys.Kill(d.ProcessPid(), sys.SIGSTOP); err != nil {
return err
}
if err := d.Detach(true); err != nil {
return err
}
}
p, err := proc.Launch(d.config.ProcessArgs)
if err != nil {
return fmt.Errorf("could not launch process: %s", err)
}
d.process = p
return nil
}
func (d *Debugger) State() (*api.DebuggerState, error) {
var (
state *api.DebuggerState

@ -12,8 +12,9 @@ import (
// Client is a RPC service.Client.
type RPCClient struct {
addr string
client *rpc.Client
addr string
processPid int
client *rpc.Client
}
// Ensure the implementation satisfies the interface.
@ -31,10 +32,20 @@ func NewClient(addr string) *RPCClient {
}
}
func (c *RPCClient) ProcessPid() int {
var pid int
c.call("ProcessPid", nil, &pid)
return pid
}
func (c *RPCClient) Detach(kill bool) error {
return c.call("Detach", kill, nil)
}
func (c *RPCClient) Restart() error {
return c.call("Restart", nil, nil)
}
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("State", nil, state)

@ -67,10 +67,22 @@ func (s *RPCServer) Run() error {
return nil
}
func (s *RPCServer) ProcessPid(arg1 interface{}, pid *int) error {
*pid = s.debugger.ProcessPid()
return nil
}
func (s *RPCServer) Detach(kill bool, ret *int) error {
return s.debugger.Detach(kill)
}
func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
if s.config.AttachPid != 0 {
return errors.New("cannot restart process Delve did not create")
}
return s.debugger.Restart()
}
func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
st, err := s.debugger.State()
if err != nil {

@ -60,6 +60,69 @@ func TestRunWithInvalidPath(t *testing.T) {
}
}
func TestRestart_afterExit(t *testing.T) {
withTestClient("continuetestprog", t, func(c service.Client) {
origPid := c.ProcessPid()
state := <-c.Continue()
if !state.Exited {
t.Fatal("expected initial process to have exited")
}
if err := c.Restart(); err != nil {
t.Fatal(err)
}
if c.ProcessPid() == origPid {
t.Fatal("did not spawn new process, has same PID")
}
state = <-c.Continue()
if !state.Exited {
t.Fatal("expected restarted process to have exited")
}
})
}
func TestRestart_duringStop(t *testing.T) {
withTestClient("continuetestprog", t, func(c service.Client) {
origPid := c.ProcessPid()
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main"})
if err != nil {
t.Fatal(err)
}
state := <-c.Continue()
if state.Breakpoint == nil {
t.Fatal("did not hit breakpoint")
}
if err := c.Restart(); err != nil {
t.Fatal(err)
}
if c.ProcessPid() == origPid {
t.Fatal("did not spawn new process, has same PID")
}
bps, err := c.ListBreakpoints()
if err != nil {
t.Fatal(err)
}
if len(bps) != 0 {
t.Fatal("breakpoint tabe not cleared")
}
state = <-c.Continue()
if !state.Exited {
t.Fatal("expected process to have exited")
}
})
}
func TestRestart_attachPid(t *testing.T) {
// Assert it does not work and returns error.
// We cannot restart a process we did not spawn.
server := rpc.NewServer(&service.Config{
Listener: nil,
AttachPid: 999,
}, false)
if err := server.Restart(nil, nil); err == nil {
t.Fatal("expected error on restart after attaching to pid but got none")
}
}
func TestClientServer_exit(t *testing.T) {
withTestClient("continuetestprog", t, func(c service.Client) {
state, err := c.GetState()

@ -48,6 +48,7 @@ func DebugCommands(client service.Client) *Commands {
{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <address> [-stack <n>|-goroutine|<variable name>]*\nSet break point at the entry point of a function, or at a specific file/line.\nWhen the breakpoint is reached the value of the specified variables will be printed, if -stack is specified the stack trace of the current goroutine will be printed, if -goroutine is specified informations about the current goroutine will be printed. Example: break foo.go:13"},
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break"},
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
@ -200,6 +201,14 @@ func formatGoroutine(g *api.Goroutine) string {
return fmt.Sprintf("%d - %s:%d %s (%#v)\n", g.ID, g.File, g.Line, fname, g.PC)
}
func restart(client service.Client, args ...string) error {
if err := client.Restart(); err != nil {
return err
}
fmt.Println("Process restarted with PID", client.ProcessPid())
return nil
}
func cont(client service.Client, args ...string) error {
stateChan := client.Continue()
for state := range stateChan {

@ -76,7 +76,7 @@ func (t *Term) Run() (error, int) {
cmdstr, err := t.promptForInput()
if err != nil {
if err == io.EOF {
err, status = handleExit(t.client, t)
return handleExit(t.client, t)
}
err, status = fmt.Errorf("Prompt for input failed.\n"), 1
break