terminal/command: Add 'reload' command (#1971)

* terminal/command: Add 'reload' command

These changes add the 'reload' command, which allows us to rebuild the project
and start the debugging session again. Currently, if the project's code is
updated while debugging it, Delve shows the new source code, but it's still
running the old one. With 'reload', the whole binary is rebuilt, and the
process starts again.

Fixes #1551

* Remove unnecessary print

Changes to be committed:
      modified:   pkg/terminal/command.go

* Add tests and refactor the code

Changes to be committed:
      modified:   cmd/dlv/cmds/commands.go
      modified:   go.mod
      modified:   pkg/terminal/command.go
      modified:   service/config.go
      modified:   service/debugger/debugger.go
      modified:   service/test/integration2_test.go

* Fix typo in the comment

Changes to be committed:
      modified:   service/debugger/debugger.go

* Fix typo in the name of the variables

The variables are local therefore the capitalization is not needed

Changes to be committed:
      modified:   cmd/dlv/cmds/commands.go

* Call GoTestBuild

Also, remove the := to avoid redeclaration

* Change the Kind in the tests

Change from debugger.ExecutingGeneratedTest to
debugger.ExecutingGeneratedFile for consistency.
We are generating a real binary instead of a test
one so ExecutingGeneratedFile makes more sense here.

Changes to be committed:
      modified:   service/test/integration2_test.go

* Avoid breakpoints based on addresses

Changes to be committed:
      modified:   service/debugger/debugger.go

* Update the rebuild behaviour

There are a few cases where we can't rebuild the binary because we don't
know how it was build.

Changes to be committed:
      modified:   service/debugger/debugger.go

* Fix typos and update documentation

Changes to be committed:
      modified:   Documentation/cli/README.md
      modified:   pkg/terminal/command.go
      modified:   service/config.go
      modified:   service/debugger/debugger.go

* Fix typo

* Remove variables

They were added to the debugger.Config

* Rename variable

Rename Kind to ExecuteKind to make it more accurate

Changes to be committed:
      modified:   cmd/dlv/cmds/commands.go
      modified:   service/debugger/debugger.go
      modified:   service/test/integration2_test.go
This commit is contained in:
Álex Sáez 2020-06-05 20:03:09 +02:00 committed by GitHub
parent 292f5c69f0
commit 95e7cafd0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 182 additions and 41 deletions

@ -13,6 +13,7 @@ Command | Description
[call](#call) | Resumes process, injecting a function call (EXPERIMENTAL!!!) [call](#call) | Resumes process, injecting a function call (EXPERIMENTAL!!!)
[continue](#continue) | Run until breakpoint or program termination. [continue](#continue) | Run until breakpoint or program termination.
[next](#next) | Step over to next source line. [next](#next) | Step over to next source line.
[rebuild](#rebuild) | Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
[restart](#restart) | Restart process. [restart](#restart) | Restart process.
[rev](#rev) | Reverses the execution of the target program for the command specified. [rev](#rev) | Reverses the execution of the target program for the command specified.
[rewind](#rewind) | Run backwards until breakpoint or program termination. [rewind](#rewind) | Run backwards until breakpoint or program termination.
@ -394,6 +395,10 @@ See [Documentation/cli/expr.md](//github.com/go-delve/delve/tree/master/Document
Aliases: p Aliases: p
## rebuild
Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
## regs ## regs
Print contents of CPU registers. Print contents of CPU registers.

@ -51,7 +51,7 @@ threads() | Equivalent to API call [ListThreads](https://godoc.org/github.com/go
types(Filter) | Equivalent to API call [ListTypes](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTypes) types(Filter) | Equivalent to API call [ListTypes](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTypes)
process_pid() | Equivalent to API call [ProcessPid](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ProcessPid) process_pid() | Equivalent to API call [ProcessPid](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ProcessPid)
recorded() | Equivalent to API call [Recorded](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Recorded) recorded() | Equivalent to API call [Recorded](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Recorded)
restart(Position, ResetArgs, NewArgs, Rerecord) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart) restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart)
set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set) set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set)
stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace) stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace)
state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State) state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State)

@ -210,7 +210,7 @@ or later, -gcflags="-N -l" on earlier versions of Go.`,
return nil return nil
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
os.Exit(execute(0, args, conf, "", executingExistingFile)) os.Exit(execute(0, args, conf, "", debugger.ExecutingExistingFile, args, buildFlags))
}, },
} }
execCommand.Flags().StringVar(&tty, "tty", "", "TTY to use for the target program") execCommand.Flags().StringVar(&tty, "tty", "", "TTY to use for the target program")
@ -312,7 +312,7 @@ https://github.com/mozilla/rr
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
backend = "rr" backend = "rr"
os.Exit(execute(0, []string{}, conf, args[0], executingOther)) os.Exit(execute(0, []string{}, conf, args[0], debugger.ExecutingOther, args, buildFlags))
}, },
} }
rootCommand.AddCommand(replayCommand) rootCommand.AddCommand(replayCommand)
@ -441,7 +441,7 @@ func debugCmd(cmd *cobra.Command, args []string) {
} }
defer gobuild.Remove(debugname) defer gobuild.Remove(debugname)
processArgs := append([]string{debugname}, targetArgs...) processArgs := append([]string{debugname}, targetArgs...)
return execute(0, processArgs, conf, "", executingGeneratedFile) return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedFile, dlvArgs, buildFlags)
}() }()
os.Exit(status) os.Exit(status)
} }
@ -591,7 +591,7 @@ func testCmd(cmd *cobra.Command, args []string) {
defer gobuild.Remove(debugname) defer gobuild.Remove(debugname)
processArgs := append([]string{debugname}, targetArgs...) processArgs := append([]string{debugname}, targetArgs...)
return execute(0, processArgs, conf, "", executingGeneratedTest) return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedTest, dlvArgs, buildFlags)
}() }()
os.Exit(status) os.Exit(status)
} }
@ -602,11 +602,11 @@ func attachCmd(cmd *cobra.Command, args []string) {
fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0])
os.Exit(1) os.Exit(1)
} }
os.Exit(execute(pid, args[1:], conf, "", executingOther)) os.Exit(execute(pid, args[1:], conf, "", debugger.ExecutingOther, args, buildFlags))
} }
func coreCmd(cmd *cobra.Command, args []string) { func coreCmd(cmd *cobra.Command, args []string) {
os.Exit(execute(0, []string{args[0]}, conf, args[1], executingOther)) os.Exit(execute(0, []string{args[0]}, conf, args[1], debugger.ExecutingOther, args, buildFlags))
} }
func connectCmd(cmd *cobra.Command, args []string) { func connectCmd(cmd *cobra.Command, args []string) {
@ -615,7 +615,7 @@ func connectCmd(cmd *cobra.Command, args []string) {
fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n") fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n")
os.Exit(1) os.Exit(1)
} }
os.Exit(connect(addr, nil, conf, executingOther)) os.Exit(connect(addr, nil, conf, debugger.ExecutingOther))
} }
// waitForDisconnectSignal is a blocking function that waits for either // waitForDisconnectSignal is a blocking function that waits for either
@ -656,7 +656,7 @@ func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
return args, []string{} return args, []string{}
} }
func connect(addr string, clientConn net.Conn, conf *config.Config, kind executeKind) int { func connect(addr string, clientConn net.Conn, conf *config.Config, kind debugger.ExecuteKind) int {
// Create and start a terminal - attach to running instance // Create and start a terminal - attach to running instance
var client *rpc2.RPCClient var client *rpc2.RPCClient
if clientConn != nil { if clientConn != nil {
@ -687,16 +687,7 @@ func connect(addr string, clientConn net.Conn, conf *config.Config, kind execute
return status return status
} }
type executeKind int func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind debugger.ExecuteKind, dlvArgs []string, buildFlags string) int {
const (
executingExistingFile = executeKind(iota)
executingGeneratedFile
executingGeneratedTest
executingOther
)
func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind executeKind) int {
if err := logflags.Setup(log, logOutput, logDest); err != nil { if err := logflags.Setup(log, logOutput, logDest); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return 1 return 1
@ -760,6 +751,9 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
Backend: backend, Backend: backend,
CoreFile: coreFile, CoreFile: coreFile,
Foreground: headless && tty == "", Foreground: headless && tty == "",
Packages: dlvArgs,
BuildFlags: buildFlags,
ExecuteKind: kind,
DebugInfoDirectories: conf.DebugInfoDirectories, DebugInfoDirectories: conf.DebugInfoDirectories,
CheckGoVersion: checkGoVersion, CheckGoVersion: checkGoVersion,
TTY: tty, TTY: tty,
@ -773,10 +767,10 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
if err == api.ErrNotExecutable { if err == api.ErrNotExecutable {
switch kind { switch kind {
case executingGeneratedFile: case debugger.ExecutingGeneratedFile:
fmt.Fprintln(os.Stderr, "Can not debug non-main package") fmt.Fprintln(os.Stderr, "Can not debug non-main package")
return 1 return 1
case executingExistingFile: case debugger.ExecutingExistingFile:
fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0]) fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0])
return 1 return 1
default: default:

4
go.sum

@ -48,8 +48,12 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSF
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY= golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY=
golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200415034506-5d8e1897c761 h1:FVw4lelfGRNPqB3C8qX1m+QyeM2vzToIwlFhEZX42y8=
golang.org/x/tools v0.0.0-20200415034506-5d8e1897c761/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -143,6 +143,7 @@ For live targets the command takes the following forms:
If newargv is omitted the process is restarted (or re-recorded) with the same argument vector. If newargv is omitted the process is restarted (or re-recorded) with the same argument vector.
If -noargs is specified instead, the argument vector is cleared. If -noargs is specified instead, the argument vector is cleared.
`}, `},
{aliases: []string{"rebuild"}, group: runCmds, cmdFn: c.rebuild, allowedPrefixes: revPrefix, helpMsg: "Rebuild the target executable and restarts it. It does not work if the executable was not built by delve."},
{aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: "Run until breakpoint or program termination."}, {aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: "Run until breakpoint or program termination."},
{aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, allowedPrefixes: revPrefix, helpMsg: "Single step through program."}, {aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, allowedPrefixes: revPrefix, helpMsg: "Single step through program."},
{aliases: []string{"step-instruction", "si"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."}, {aliases: []string{"step-instruction", "si"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."},
@ -1028,7 +1029,7 @@ func restartLive(t *Term, ctx callContext, args string) error {
} }
func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error { func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error {
discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv) discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, false)
if err != nil { if err != nil {
return err return err
} }
@ -1073,6 +1074,18 @@ func printcontextNoState(t *Term) {
printcontext(t, state) printcontext(t, state)
} }
func (c *Commands) rebuild(t *Term, ctx callContext, args string) error {
if ctx.Prefix == revPrefix {
return c.rewind(t, ctx, args)
}
defer t.onStop()
discarded, err := t.client.Restart(true)
if len(discarded) > 0 {
fmt.Printf("not all breakpoints could be restored.")
}
return err
}
func (c *Commands) cont(t *Term, ctx callContext, args string) error { func (c *Commands) cont(t *Term, ctx callContext, args string) error {
if ctx.Prefix == revPrefix { if ctx.Prefix == revPrefix {
return c.rewind(t, ctx, args) return c.rewind(t, ctx, args)

@ -1091,6 +1091,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
return starlark.None, decorateError(thread, err) return starlark.None, decorateError(thread, err)
} }
} }
if len(args) > 4 && args[4] != starlark.None {
err := unmarshalStarlarkValue(args[4], &rpcArgs.Rebuild, "Rebuild")
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
for _, kv := range kwargs { for _, kv := range kwargs {
var err error var err error
switch kv[0].(starlark.String) { switch kv[0].(starlark.String) {
@ -1102,6 +1108,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewArgs, "NewArgs") err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewArgs, "NewArgs")
case "Rerecord": case "Rerecord":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord") err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord")
case "Rebuild":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rebuild, "Rebuild")
default: default:
err = fmt.Errorf("unknown argument %q", kv[0]) err = fmt.Errorf("unknown argument %q", kv[0])
} }

@ -18,10 +18,10 @@ type Client interface {
// 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. // Restarts program. Set true if you want to rebuild the process we are debugging.
Restart() ([]api.DiscardedBreakpoint, error) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error)
// Restarts program from the specified position. // Restarts program from the specified position.
RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error)
// GetState returns the current debugger state. // GetState returns the current debugger state.
GetState() (*api.DebuggerState, error) GetState() (*api.DebuggerState, error)

@ -16,6 +16,7 @@ import (
"time" "time"
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/gobuild"
"github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/locspec" "github.com/go-delve/delve/pkg/locspec"
"github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
@ -63,6 +64,15 @@ type Debugger struct {
recordMutex sync.Mutex recordMutex sync.Mutex
} }
type ExecuteKind int
const (
ExecutingExistingFile = ExecuteKind(iota)
ExecutingGeneratedFile
ExecutingGeneratedTest
ExecutingOther
)
// Config provides the configuration to start a Debugger. // Config provides the configuration to start a Debugger.
// //
// Only one of ProcessArgs or AttachPid should be specified. If ProcessArgs is // Only one of ProcessArgs or AttachPid should be specified. If ProcessArgs is
@ -98,6 +108,15 @@ type Config struct {
// TTY is passed along to the target process on creation. Used to specify a // TTY is passed along to the target process on creation. Used to specify a
// TTY for that process. // TTY for that process.
TTY string TTY string
// Packages contains the packages that we are debugging.
Packages []string
// BuildFlags contains the flags passed to the compiler.
BuildFlags string
// ExecuteKind contains the kind of the executed program.
ExecuteKind ExecuteKind
} }
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the // New creates a new Debugger. ProcessArgs specify the commandline arguments for the
@ -390,7 +409,7 @@ func (d *Debugger) detach(kill bool) error {
// If the target process is a recording it will restart it from the given // If the target process is a recording it will restart it from the given
// position. If pos starts with 'c' it's a checkpoint ID, otherwise it's an // position. If pos starts with 'c' it's a checkpoint ID, otherwise it's an
// event number. If resetArgs is true, newArgs will replace the process args. // event number. If resetArgs is true, newArgs will replace the process args.
func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) { func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
@ -421,6 +440,25 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
} }
var p *proc.Target var p *proc.Target
var err error var err error
if rebuild {
switch d.config.ExecuteKind {
case ExecutingGeneratedFile:
err = gobuild.GoBuild(d.processArgs[0], d.config.Packages, d.config.BuildFlags)
if err != nil {
return nil, fmt.Errorf("could not rebuild process: %s", err)
}
case ExecutingGeneratedTest:
err = gobuild.GoTestBuild(d.processArgs[0], d.config.Packages, d.config.BuildFlags)
if err != nil {
return nil, fmt.Errorf("could not rebuild process: %s", err)
}
default:
// We cannot build a process that we didn't start, because we don't know how it was built.
return nil, fmt.Errorf("cannot rebuild a binary")
}
}
if recorded { if recorded {
run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false) run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false)
if err2 != nil { if err2 != nil {
@ -450,6 +488,10 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
} }
createLogicalBreakpoint(p, addrs, oldBp) createLogicalBreakpoint(p, addrs, oldBp)
} else { } else {
// Avoid setting a breakpoint based on address when rebuilding
if rebuild {
continue
}
newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil) newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil)
if err != nil { if err != nil {
return nil, err return nil, err

@ -46,7 +46,7 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
if s.config.Debugger.AttachPid != 0 { if s.config.Debugger.AttachPid != 0 {
return errors.New("cannot restart process Delve did not create") return errors.New("cannot restart process Delve did not create")
} }
_, err := s.debugger.Restart(false, "", false, nil) _, err := s.debugger.Restart(false, "", false, nil, false)
return err return err
} }

@ -60,15 +60,15 @@ func (c *RPCClient) Detach(kill bool) error {
return c.call("Detach", DetachIn{kill}, out) return c.call("Detach", DetachIn{kill}, out)
} }
func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) { func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut) out := new(RestartOut)
err := c.call("Restart", RestartIn{"", false, nil, false}, out) err := c.call("Restart", RestartIn{"", false, nil, false, rebuild}, out)
return out.DiscardedBreakpoints, err return out.DiscardedBreakpoints, err
} }
func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) { func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut) out := new(RestartOut)
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord}, out) err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild}, out)
return out.DiscardedBreakpoints, err return out.DiscardedBreakpoints, err
} }

@ -76,6 +76,9 @@ type RestartIn struct {
// When Rerecord is set the target will be rerecorded // When Rerecord is set the target will be rerecorded
Rerecord bool Rerecord bool
// When Rebuild is set the process will be build again
Rebuild bool
} }
type RestartOut struct { type RestartOut struct {
@ -90,7 +93,7 @@ func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) {
} }
var out RestartOut var out RestartOut
var err error var err error
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs) out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.Rebuild)
cb.Return(out, err) cb.Return(out, err)
} }

@ -3,6 +3,7 @@ package service_test
import ( import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"math/rand" "math/rand"
"net" "net"
"net/rpc" "net/rpc"
@ -74,6 +75,9 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
Debugger: debugger.Config{ Debugger: debugger.Config{
Backend: testBackend, Backend: testBackend,
CheckGoVersion: true, CheckGoVersion: true,
Packages: []string{fixture.Source},
BuildFlags: "", // build flags can be an empty string here because the only test that uses it, does not set special flags.
ExecuteKind: debugger.ExecutingGeneratedFile,
}, },
}) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
@ -110,6 +114,7 @@ func TestRunWithInvalidPath(t *testing.T) {
APIVersion: 2, APIVersion: 2,
Debugger: debugger.Config{ Debugger: debugger.Config{
Backend: testBackend, Backend: testBackend,
ExecuteKind: debugger.ExecutingGeneratedFile,
}, },
}) })
if err := server.Run(); err == nil { if err := server.Run(); err == nil {
@ -124,7 +129,7 @@ func TestRestart_afterExit(t *testing.T) {
if !state.Exited { if !state.Exited {
t.Fatal("expected initial process to have exited") t.Fatal("expected initial process to have exited")
} }
if _, err := c.Restart(); err != nil { if _, err := c.Restart(false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c.ProcessPid() == origPid { if c.ProcessPid() == origPid {
@ -154,7 +159,7 @@ func TestRestart_breakpointPreservation(t *testing.T) {
} }
t.Log("Restart") t.Log("Restart")
c.Restart() c.Restart(false)
stateCh = c.Continue() stateCh = c.Continue()
state = <-stateCh state = <-stateCh
if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint { if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint {
@ -178,7 +183,7 @@ func TestRestart_duringStop(t *testing.T) {
if state.CurrentThread.Breakpoint == nil { if state.CurrentThread.Breakpoint == nil {
t.Fatal("did not hit breakpoint") t.Fatal("did not hit breakpoint")
} }
if _, err := c.Restart(); err != nil { if _, err := c.Restart(false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c.ProcessPid() == origPid { if c.ProcessPid() == origPid {
@ -194,6 +199,72 @@ func TestRestart_duringStop(t *testing.T) {
}) })
} }
// This source is a slightly modified version of
// _fixtures/testenv.go. The only difference is that
// the name of the environment variable we are trying to
// read is named differently, so we can assert the code
// was actually changed in the test.
const modifiedSource = `package main
import (
"fmt"
"os"
"runtime"
)
func main() {
x := os.Getenv("SOMEMODIFIEDVAR")
runtime.Breakpoint()
fmt.Printf("SOMEMODIFIEDVAR=%s\n", x)
}
`
func TestRestart_rebuild(t *testing.T) {
// In the original fixture file the env var tested for is SOMEVAR.
os.Setenv("SOMEVAR", "bah")
withTestClient2Extended("testenv", t, 0, func(c service.Client, f protest.Fixture) {
<-c.Continue()
var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig)
assertNoError(err, t, "EvalVariable")
if var1.Value != "bah" {
t.Fatalf("expected 'bah' got %q", var1.Value)
}
fi, err := os.Stat(f.Source)
assertNoError(err, t, "Stat fixture.Source")
originalSource, err := ioutil.ReadFile(f.Source)
assertNoError(err, t, "Reading original source")
// Ensure we write the original source code back after the test exits.
defer ioutil.WriteFile(f.Source, originalSource, fi.Mode())
// Write modified source code to the fixture file.
err = ioutil.WriteFile(f.Source, []byte(modifiedSource), fi.Mode())
assertNoError(err, t, "Writing modified source")
// First set our new env var and ensure later that the
// modified source code picks it up.
os.Setenv("SOMEMODIFIEDVAR", "foobar")
// Restart the program, rebuilding from source.
_, err = c.Restart(true)
assertNoError(err, t, "Restart(true)")
<-c.Continue()
var1, err = c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig)
assertNoError(err, t, "EvalVariable")
if var1.Value != "foobar" {
t.Fatalf("expected 'foobar' got %q", var1.Value)
}
})
}
func TestClientServer_exit(t *testing.T) { func TestClientServer_exit(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestClient2("continuetestprog", t, func(c service.Client) { withTestClient2("continuetestprog", t, func(c service.Client) {
@ -1350,7 +1421,7 @@ func TestClientServer_RestartBreakpointPosition(t *testing.T) {
} }
_, err = c.Halt() _, err = c.Halt()
assertNoError(err, t, "Halt") assertNoError(err, t, "Halt")
_, err = c.Restart() _, err = c.Restart(false)
assertNoError(err, t, "Restart") assertNoError(err, t, "Restart")
bps, err := c.ListBreakpoints() bps, err := c.ListBreakpoints()
assertNoError(err, t, "ListBreakpoints") assertNoError(err, t, "ListBreakpoints")
@ -1575,6 +1646,7 @@ func TestAcceptMulticlient(t *testing.T) {
DisconnectChan: disconnectChan, DisconnectChan: disconnectChan,
Debugger: debugger.Config{ Debugger: debugger.Config{
Backend: testBackend, Backend: testBackend,
ExecuteKind: debugger.ExecutingGeneratedTest,
}, },
}) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
@ -1826,7 +1898,7 @@ func TestRerecord(t *testing.T) {
t0 := gett() t0 := gett()
_, err = c.RestartFrom(false, "", false, nil) _, err = c.RestartFrom(false, "", false, nil, false)
assertNoError(err, t, "First restart") assertNoError(err, t, "First restart")
t1 := gett() t1 := gett()
@ -1836,7 +1908,7 @@ func TestRerecord(t *testing.T) {
time.Sleep(2 * time.Second) // make sure that we're not running inside the same second time.Sleep(2 * time.Second) // make sure that we're not running inside the same second
_, err = c.RestartFrom(true, "", false, nil) _, err = c.RestartFrom(true, "", false, nil, false)
assertNoError(err, t, "Second restart") assertNoError(err, t, "Second restart")
t2 := gett() t2 := gett()
@ -1901,7 +1973,7 @@ func TestStopRecording(t *testing.T) {
// try rerecording // try rerecording
go func() { go func() {
c.RestartFrom(true, "", false, nil) c.RestartFrom(true, "", false, nil, false)
}() }()
time.Sleep(time.Second) // hopefully the re-recording started... time.Sleep(time.Second) // hopefully the re-recording started...