parent
d2b8d57053
commit
2f7612d4af
4
Makefile
4
Makefile
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user