parent
d2b8d57053
commit
2f7612d4af
4
Makefile
4
Makefile
@ -2,10 +2,6 @@
|
|||||||
UNAME = $(shell uname)
|
UNAME = $(shell uname)
|
||||||
PREFIX=github.com/derekparker/delve
|
PREFIX=github.com/derekparker/delve
|
||||||
|
|
||||||
ifndef $(RUN)
|
|
||||||
RUN = ".*"
|
|
||||||
endif
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build github.com/derekparker/delve/cmd/dlv
|
go build github.com/derekparker/delve/cmd/dlv
|
||||||
ifeq "$(UNAME)" "Darwin"
|
ifeq "$(UNAME)" "Darwin"
|
||||||
|
@ -7,9 +7,15 @@ import (
|
|||||||
// Client represents a debugger service client. All client methods are
|
// Client represents a debugger service client. All client methods are
|
||||||
// synchronous.
|
// synchronous.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
|
// Returns the pid of the process we are debugging.
|
||||||
|
ProcessPid() int
|
||||||
|
|
||||||
// Detach detaches the debugger, optionally killing the process.
|
// Detach detaches the debugger, optionally killing the process.
|
||||||
Detach(killProcess bool) error
|
Detach(killProcess bool) error
|
||||||
|
|
||||||
|
// Restarts program.
|
||||||
|
Restart() error
|
||||||
|
|
||||||
// GetState returns the current debugger state.
|
// GetState returns the current debugger state.
|
||||||
GetState() (*api.DebuggerState, error)
|
GetState() (*api.DebuggerState, error)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/derekparker/delve/proc"
|
"github.com/derekparker/delve/proc"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
|
sys "golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Debugger service.
|
// Debugger service.
|
||||||
@ -61,10 +62,35 @@ func New(config *Config) (*Debugger, error) {
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Debugger) ProcessPid() int {
|
||||||
|
return d.process.Pid
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Debugger) Detach(kill bool) error {
|
func (d *Debugger) Detach(kill bool) error {
|
||||||
return d.process.Detach(kill)
|
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) {
|
func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||||
var (
|
var (
|
||||||
state *api.DebuggerState
|
state *api.DebuggerState
|
||||||
|
@ -12,8 +12,9 @@ import (
|
|||||||
|
|
||||||
// Client is a RPC service.Client.
|
// Client is a RPC service.Client.
|
||||||
type RPCClient struct {
|
type RPCClient struct {
|
||||||
addr string
|
addr string
|
||||||
client *rpc.Client
|
processPid int
|
||||||
|
client *rpc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the implementation satisfies the interface.
|
// 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 {
|
func (c *RPCClient) Detach(kill bool) error {
|
||||||
return c.call("Detach", kill, nil)
|
return c.call("Detach", kill, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Restart() error {
|
||||||
|
return c.call("Restart", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
|
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
|
||||||
state := new(api.DebuggerState)
|
state := new(api.DebuggerState)
|
||||||
err := c.call("State", nil, state)
|
err := c.call("State", nil, state)
|
||||||
|
@ -67,10 +67,22 @@ func (s *RPCServer) Run() error {
|
|||||||
return nil
|
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 {
|
func (s *RPCServer) Detach(kill bool, ret *int) error {
|
||||||
return s.debugger.Detach(kill)
|
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 {
|
func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
|
||||||
st, err := s.debugger.State()
|
st, err := s.debugger.State()
|
||||||
if err != nil {
|
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) {
|
func TestClientServer_exit(t *testing.T) {
|
||||||
withTestClient("continuetestprog", t, func(c service.Client) {
|
withTestClient("continuetestprog", t, func(c service.Client) {
|
||||||
state, err := c.GetState()
|
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{"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{"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{"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{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
|
||||||
{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
|
{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
|
||||||
{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
|
{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)
|
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 {
|
func cont(client service.Client, args ...string) error {
|
||||||
stateChan := client.Continue()
|
stateChan := client.Continue()
|
||||||
for state := range stateChan {
|
for state := range stateChan {
|
||||||
|
@ -76,7 +76,7 @@ func (t *Term) Run() (error, int) {
|
|||||||
cmdstr, err := t.promptForInput()
|
cmdstr, err := t.promptForInput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
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
|
err, status = fmt.Errorf("Prompt for input failed.\n"), 1
|
||||||
break
|
break
|
||||||
|
Loading…
Reference in New Issue
Block a user