*: add option to re-record recorded targets (#1702)
Adds a '-r' option to the 'restart' command (and to the Restart API) that re-records the target when using rr. Also moves the code to delete the trace directory inside the gdbserial package.
This commit is contained in:
parent
1381454362
commit
6b20e880e2
@ -49,7 +49,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) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart)
|
restart(Position, ResetArgs, NewArgs, Rerecord) | 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)
|
||||||
|
11
_fixtures/testrerecord.go
Normal file
11
_fixtures/testrerecord.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
fmt.Println(t)
|
||||||
|
}
|
@ -560,13 +560,6 @@ func connect(addr string, clientConn net.Conn, conf *config.Config, kind execute
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if client.Recorded() && (kind == executingGeneratedFile || kind == executingGeneratedTest) {
|
|
||||||
// When using the rr backend remove the trace directory if we built the
|
|
||||||
// executable
|
|
||||||
if tracedir, err := client.TraceDirectory(); err == nil {
|
|
||||||
defer SafeRemoveAll(tracedir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
term := terminal.New(client, conf)
|
term := terminal.New(client, conf)
|
||||||
term.InitFile = InitFile
|
term.InitFile = InitFile
|
||||||
status, err := term.Run()
|
status, err := term.Run()
|
||||||
@ -741,28 +734,3 @@ func gocommand(command string, args ...string) error {
|
|||||||
goBuild.Stderr = os.Stderr
|
goBuild.Stderr = os.Stderr
|
||||||
return goBuild.Run()
|
return goBuild.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SafeRemoveAll removes dir and its contents but only as long as dir does
|
|
||||||
// not contain directories.
|
|
||||||
func SafeRemoveAll(dir string) {
|
|
||||||
dh, err := os.Open(dir)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dh.Close()
|
|
||||||
fis, err := dh.Readdir(-1)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, fi := range fis {
|
|
||||||
if fi.IsDir() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, fi := range fis {
|
|
||||||
if err := os.Remove(filepath.Join(dir, fi.Name())); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Remove(dir)
|
|
||||||
}
|
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-delve/delve/cmd/dlv/cmds"
|
|
||||||
protest "github.com/go-delve/delve/pkg/proc/test"
|
protest "github.com/go-delve/delve/pkg/proc/test"
|
||||||
"github.com/go-delve/delve/pkg/terminal"
|
"github.com/go-delve/delve/pkg/terminal"
|
||||||
"github.com/go-delve/delve/service/rpc2"
|
"github.com/go-delve/delve/service/rpc2"
|
||||||
@ -284,7 +283,7 @@ func TestGeneratedDoc(t *testing.T) {
|
|||||||
// Checks gen-usage-docs.go
|
// Checks gen-usage-docs.go
|
||||||
tempDir, err := ioutil.TempDir(os.TempDir(), "test-gen-doc")
|
tempDir, err := ioutil.TempDir(os.TempDir(), "test-gen-doc")
|
||||||
assertNoError(err, t, "TempDir")
|
assertNoError(err, t, "TempDir")
|
||||||
defer cmds.SafeRemoveAll(tempDir)
|
defer protest.SafeRemoveAll(tempDir)
|
||||||
cmd := exec.Command("go", "run", "scripts/gen-usage-docs.go", tempDir)
|
cmd := exec.Command("go", "run", "scripts/gen-usage-docs.go", tempDir)
|
||||||
cmd.Dir = projectRoot()
|
cmd.Dir = projectRoot()
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
|
@ -124,6 +124,8 @@ type Process struct {
|
|||||||
process *os.Process
|
process *os.Process
|
||||||
waitChan chan *os.ProcessState
|
waitChan chan *os.ProcessState
|
||||||
|
|
||||||
|
onDetach func() // called after a successful detach
|
||||||
|
|
||||||
common proc.CommonProcess
|
common proc.CommonProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,6 +868,9 @@ func (p *Process) Detach(kill bool) error {
|
|||||||
p.process = nil
|
p.process = nil
|
||||||
}
|
}
|
||||||
p.detached = true
|
p.detached = true
|
||||||
|
if p.onDetach != nil {
|
||||||
|
p.onDetach()
|
||||||
|
}
|
||||||
return p.bi.Close()
|
return p.bi.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
@ -53,7 +54,7 @@ func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
|
|||||||
|
|
||||||
// Replay starts an instance of rr in replay mode, with the specified trace
|
// Replay starts an instance of rr in replay mode, with the specified trace
|
||||||
// directory, and connects to it.
|
// directory, and connects to it.
|
||||||
func Replay(tracedir string, quiet bool, debugInfoDirs []string) (*Process, error) {
|
func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*Process, error) {
|
||||||
if err := checkRRAvailabe(); err != nil {
|
if err := checkRRAvailabe(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -82,6 +83,11 @@ func Replay(tracedir string, quiet bool, debugInfoDirs []string) (*Process, erro
|
|||||||
|
|
||||||
p := New(rrcmd.Process)
|
p := New(rrcmd.Process)
|
||||||
p.tracedir = tracedir
|
p.tracedir = tracedir
|
||||||
|
if deleteOnDetach {
|
||||||
|
p.onDetach = func() {
|
||||||
|
safeRemoveAll(p.tracedir)
|
||||||
|
}
|
||||||
|
}
|
||||||
err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
|
err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rrcmd.Process.Kill()
|
rrcmd.Process.Kill()
|
||||||
@ -262,6 +268,31 @@ func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string
|
|||||||
if tracedir == "" {
|
if tracedir == "" {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
p, err = Replay(tracedir, quiet, debugInfoDirs)
|
p, err = Replay(tracedir, quiet, true, debugInfoDirs)
|
||||||
return p, tracedir, err
|
return p, tracedir, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safeRemoveAll removes dir and its contents but only as long as dir does
|
||||||
|
// not contain directories.
|
||||||
|
func safeRemoveAll(dir string) {
|
||||||
|
dh, err := os.Open(dir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dh.Close()
|
||||||
|
fis, err := dh.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, fi := range fis {
|
||||||
|
if fi.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, fi := range fis {
|
||||||
|
if err := os.Remove(filepath.Join(dir, fi.Name())); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Remove(dir)
|
||||||
|
}
|
||||||
|
@ -38,9 +38,6 @@ func withTestRecording(name string, t testing.TB, fn func(p *gdbserial.Process,
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
p.Detach(true)
|
p.Detach(true)
|
||||||
if tracedir != "" {
|
|
||||||
protest.SafeRemoveAll(tracedir)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fn(p, fixture)
|
fn(p, fixture)
|
||||||
|
@ -84,9 +84,6 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
p.Detach(true)
|
p.Detach(true)
|
||||||
if tracedir != "" {
|
|
||||||
protest.SafeRemoveAll(tracedir)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fn(p, fixture)
|
fn(p, fixture)
|
||||||
|
@ -128,12 +128,18 @@ A tracepoint is a breakpoint that does not stop the execution of the program, in
|
|||||||
See also: "help on", "help cond" and "help clear"`},
|
See also: "help on", "help cond" and "help clear"`},
|
||||||
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
|
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
|
||||||
|
|
||||||
restart [checkpoint]
|
For recorded targets the command takes the following forms:
|
||||||
restart [-noargs] newargv...
|
|
||||||
|
|
||||||
For recorded processes restarts from the start or from the specified
|
restart resets ot the start of the recording
|
||||||
checkpoint. For normal processes restarts the process, optionally changing
|
restart [checkpoint] resets the recording to the given checkpoint
|
||||||
the arguments. With -noargs, the process starts with an empty commandline.
|
restart -r [newargv...] re-records the target process
|
||||||
|
|
||||||
|
For live targets the command takes the following forms:
|
||||||
|
|
||||||
|
restart [newargv...] restarts the process
|
||||||
|
|
||||||
|
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.
|
||||||
`},
|
`},
|
||||||
{aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
|
{aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
|
||||||
{aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."},
|
{aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."},
|
||||||
@ -856,21 +862,51 @@ func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
|
|||||||
prefix, formatLocation(g.StartLoc))
|
prefix, formatLocation(g.StartLoc))
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseArgs(args string) ([]string, error) {
|
func restart(t *Term, ctx callContext, args string) error {
|
||||||
if args == "" {
|
if t.client.Recorded() {
|
||||||
return nil, nil
|
return restartRecorded(t, ctx, args)
|
||||||
}
|
}
|
||||||
v, err := argv.Argv([]rune(args), argv.ParseEnv(os.Environ()),
|
|
||||||
func(s []rune, _ map[string]string) ([]rune, error) {
|
return restartLive(t, ctx, args)
|
||||||
return nil, fmt.Errorf("Backtick not supported in '%s'", string(s))
|
}
|
||||||
})
|
|
||||||
|
func restartRecorded(t *Term, ctx callContext, args string) error {
|
||||||
|
v := strings.SplitN(args, " ", 2)
|
||||||
|
|
||||||
|
rerecord := false
|
||||||
|
resetArgs := false
|
||||||
|
newArgv := []string{}
|
||||||
|
restartPos := ""
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
if v[0] == "-r" {
|
||||||
|
rerecord = true
|
||||||
|
if len(v) == 2 {
|
||||||
|
var err error
|
||||||
|
resetArgs, newArgv, err = parseNewArgv(v[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(v) > 1 {
|
||||||
|
return fmt.Errorf("too many arguments to restart")
|
||||||
|
}
|
||||||
|
restartPos = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := restartIntl(t, rerecord, restartPos, resetArgs, newArgv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := t.client.GetState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
if len(v) != 1 {
|
printcontext(t, state)
|
||||||
return nil, fmt.Errorf("Illegal commandline '%s'", args)
|
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
|
||||||
}
|
return nil
|
||||||
return v[0], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseOptionalCount parses an optional count argument.
|
// parseOptionalCount parses an optional count argument.
|
||||||
@ -882,51 +918,58 @@ func parseOptionalCount(arg string) (int64, error) {
|
|||||||
return strconv.ParseInt(arg, 0, 64)
|
return strconv.ParseInt(arg, 0, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restart(t *Term, ctx callContext, args string) error {
|
func restartLive(t *Term, ctx callContext, args string) error {
|
||||||
v, err := parseArgs(args)
|
resetArgs, newArgv, err := parseNewArgv(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var restartPos string
|
|
||||||
var resetArgs bool
|
if err := restartIntl(t, false, "", resetArgs, newArgv); err != nil {
|
||||||
if t.client.Recorded() {
|
|
||||||
if len(v) > 1 {
|
|
||||||
return fmt.Errorf("restart: illegal position '%v'", v)
|
|
||||||
}
|
|
||||||
if len(v) == 1 {
|
|
||||||
restartPos = v[0]
|
|
||||||
v = nil
|
|
||||||
}
|
|
||||||
} else if len(v) > 0 {
|
|
||||||
resetArgs = true
|
|
||||||
if v[0] == "-noargs" {
|
|
||||||
if len(v) > 1 {
|
|
||||||
return fmt.Errorf("restart: -noargs does not take any arg")
|
|
||||||
}
|
|
||||||
v = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
discarded, err := t.client.RestartFrom(restartPos, resetArgs, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !t.client.Recorded() {
|
|
||||||
fmt.Println("Process restarted with PID", t.client.ProcessPid())
|
fmt.Println("Process restarted with PID", t.client.ProcessPid())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error {
|
||||||
|
discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
for i := range discarded {
|
for i := range discarded {
|
||||||
fmt.Printf("Discarded %s at %s: %v\n", formatBreakpointName(discarded[i].Breakpoint, false), formatBreakpointLocation(discarded[i].Breakpoint), discarded[i].Reason)
|
fmt.Printf("Discarded %s at %s: %v\n", formatBreakpointName(discarded[i].Breakpoint, false), formatBreakpointLocation(discarded[i].Breakpoint), discarded[i].Reason)
|
||||||
}
|
}
|
||||||
if t.client.Recorded() {
|
|
||||||
state, err := t.client.GetState()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
printcontext(t, state)
|
|
||||||
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseNewArgv(args string) (resetArgs bool, newArgv []string, err error) {
|
||||||
|
if args == "" {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
v, err := argv.Argv([]rune(args), argv.ParseEnv(os.Environ()),
|
||||||
|
func(s []rune, _ map[string]string) ([]rune, error) {
|
||||||
|
return nil, fmt.Errorf("Backtick not supported in '%s'", string(s))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
if len(v) != 1 {
|
||||||
|
return false, nil, fmt.Errorf("Illegal commandline '%s'", args)
|
||||||
|
}
|
||||||
|
w := v[0]
|
||||||
|
if len(w) == 0 {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
if w[0] == "-noargs" {
|
||||||
|
if len(w) > 1 {
|
||||||
|
return false, nil, fmt.Errorf("Too many arguments to restart")
|
||||||
|
}
|
||||||
|
return true, nil, nil
|
||||||
|
}
|
||||||
|
return true, w, nil
|
||||||
|
}
|
||||||
|
|
||||||
func printcontextNoState(t *Term) {
|
func printcontextNoState(t *Term) {
|
||||||
state, _ := t.client.GetState()
|
state, _ := t.client.GetState()
|
||||||
if state == nil || state.CurrentThread == nil {
|
if state == nil || state.CurrentThread == nil {
|
||||||
|
@ -161,11 +161,7 @@ func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.Build
|
|||||||
}
|
}
|
||||||
client := rpc2.NewClient(listener.Addr().String())
|
client := rpc2.NewClient(listener.Addr().String())
|
||||||
defer func() {
|
defer func() {
|
||||||
dir, _ := client.TraceDirectory()
|
|
||||||
client.Detach(true)
|
client.Detach(true)
|
||||||
if dir != "" {
|
|
||||||
test.SafeRemoveAll(dir)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ft := &FakeTerminal{
|
ft := &FakeTerminal{
|
||||||
|
@ -998,6 +998,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
|||||||
return starlark.None, decorateError(thread, err)
|
return starlark.None, decorateError(thread, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(args) > 3 && args[3] != starlark.None {
|
||||||
|
err := unmarshalStarlarkValue(args[3], &rpcArgs.Rerecord, "Rerecord")
|
||||||
|
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) {
|
||||||
@ -1007,6 +1013,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
|||||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.ResetArgs, "ResetArgs")
|
err = unmarshalStarlarkValue(kv[1], &rpcArgs.ResetArgs, "ResetArgs")
|
||||||
case "NewArgs":
|
case "NewArgs":
|
||||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewArgs, "NewArgs")
|
err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewArgs, "NewArgs")
|
||||||
|
case "Rerecord":
|
||||||
|
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord")
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ type Client interface {
|
|||||||
// Restarts program.
|
// Restarts program.
|
||||||
Restart() ([]api.DiscardedBreakpoint, error)
|
Restart() ([]api.DiscardedBreakpoint, error)
|
||||||
// Restarts program from the specified position.
|
// Restarts program from the specified position.
|
||||||
RestartFrom(pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error)
|
RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error)
|
||||||
|
|
||||||
// GetState returns the current debugger state.
|
// GetState returns the current debugger state.
|
||||||
GetState() (*api.DebuggerState, error)
|
GetState() (*api.DebuggerState, error)
|
||||||
|
@ -106,7 +106,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
switch d.config.Backend {
|
switch d.config.Backend {
|
||||||
case "rr":
|
case "rr":
|
||||||
d.log.Infof("opening trace %s", d.config.CoreFile)
|
d.log.Infof("opening trace %s", d.config.CoreFile)
|
||||||
p, err = gdbserial.Replay(d.config.CoreFile, false, d.config.DebugInfoDirectories)
|
p, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories)
|
||||||
default:
|
default:
|
||||||
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
|
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
|
||||||
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories)
|
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories)
|
||||||
@ -140,6 +140,18 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// canRestart returns true if the target was started with Launch and can be restarted
|
||||||
|
func (d *Debugger) canRestart() bool {
|
||||||
|
switch {
|
||||||
|
case d.config.AttachPid > 0:
|
||||||
|
return false
|
||||||
|
case d.config.CoreFile != "":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Debugger) checkGoVersion() error {
|
func (d *Debugger) checkGoVersion() error {
|
||||||
if !d.config.CheckGoVersion {
|
if !d.config.CheckGoVersion {
|
||||||
return nil
|
return nil
|
||||||
@ -273,16 +285,19 @@ func (d *Debugger) detach(kill bool) error {
|
|||||||
return d.target.Detach(kill)
|
return d.target.Detach(kill)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrCanNotRestart = errors.New("can not restart this target")
|
||||||
|
|
||||||
// Restart will restart the target process, first killing
|
// Restart will restart the target process, first killing
|
||||||
// and then exec'ing it again.
|
// and then exec'ing it again.
|
||||||
// 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(pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) {
|
func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) {
|
||||||
d.processMutex.Lock()
|
d.processMutex.Lock()
|
||||||
defer d.processMutex.Unlock()
|
defer d.processMutex.Unlock()
|
||||||
|
|
||||||
if recorded, _ := d.target.Recorded(); recorded {
|
recorded, _ := d.target.Recorded()
|
||||||
|
if recorded && !rerecord {
|
||||||
return nil, d.target.Restart(pos)
|
return nil, d.target.Restart(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +305,11 @@ func (d *Debugger) Restart(pos string, resetArgs bool, newArgs []string) ([]api.
|
|||||||
return nil, proc.ErrNotRecorded
|
return nil, proc.ErrNotRecorded
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid, _ := d.target.Valid(); valid {
|
if !d.canRestart() {
|
||||||
|
return nil, ErrCanNotRestart
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, _ := d.target.Valid(); valid && !recorded {
|
||||||
// Ensure the process is in a PTRACE_STOP.
|
// Ensure the process is in a PTRACE_STOP.
|
||||||
if err := stopProcess(d.ProcessPid()); err != nil {
|
if err := stopProcess(d.ProcessPid()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -40,7 +40,7 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
|
|||||||
if s.config.AttachPid != 0 {
|
if s.config.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, nil)
|
_, err := s.debugger.Restart(false, "", false, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,13 +62,13 @@ func (c *RPCClient) Detach(kill bool) error {
|
|||||||
|
|
||||||
func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) {
|
func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) {
|
||||||
out := new(RestartOut)
|
out := new(RestartOut)
|
||||||
err := c.call("Restart", RestartIn{"", false, nil}, out)
|
err := c.call("Restart", RestartIn{"", false, nil, false}, out)
|
||||||
return out.DiscardedBreakpoints, err
|
return out.DiscardedBreakpoints, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) RestartFrom(pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) {
|
func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) {
|
||||||
out := new(RestartOut)
|
out := new(RestartOut)
|
||||||
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs}, out)
|
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord}, out)
|
||||||
return out.DiscardedBreakpoints, err
|
return out.DiscardedBreakpoints, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,9 @@ type RestartIn struct {
|
|||||||
// NewArgs are arguments to launch a new process. They replace only the
|
// NewArgs are arguments to launch a new process. They replace only the
|
||||||
// argv[1] and later. Argv[0] cannot be changed.
|
// argv[1] and later. Argv[0] cannot be changed.
|
||||||
NewArgs []string
|
NewArgs []string
|
||||||
|
|
||||||
|
// When Rerecord is set the target will be rerecorded
|
||||||
|
Rerecord bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RestartOut struct {
|
type RestartOut struct {
|
||||||
@ -85,7 +88,7 @@ func (s *RPCServer) Restart(arg RestartIn, out *RestartOut) error {
|
|||||||
return errors.New("cannot restart process Delve did not create")
|
return errors.New("cannot restart process Delve did not create")
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Position, arg.ResetArgs, arg.NewArgs)
|
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,11 +77,7 @@ func withTestClient2Extended(name string, t *testing.T, fn func(c service.Client
|
|||||||
clientConn, fixture := startServer(name, t)
|
clientConn, fixture := startServer(name, t)
|
||||||
client := rpc2.NewClientFromConn(clientConn)
|
client := rpc2.NewClientFromConn(clientConn)
|
||||||
defer func() {
|
defer func() {
|
||||||
dir, _ := client.TraceDirectory()
|
|
||||||
client.Detach(true)
|
client.Detach(true)
|
||||||
if dir != "" {
|
|
||||||
protest.SafeRemoveAll(dir)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fn(client, fixture)
|
fn(client, fixture)
|
||||||
@ -1147,17 +1143,22 @@ func TestSkipPrologue2(t *testing.T) {
|
|||||||
func TestIssue419(t *testing.T) {
|
func TestIssue419(t *testing.T) {
|
||||||
// Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously
|
// Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously
|
||||||
// try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe
|
// try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe
|
||||||
|
finish := make(chan struct{})
|
||||||
withTestClient2("issue419", t, func(c service.Client) {
|
withTestClient2("issue419", t, func(c service.Client) {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer close(finish)
|
||||||
rand.Seed(time.Now().Unix())
|
rand.Seed(time.Now().Unix())
|
||||||
d := time.Duration(rand.Intn(4) + 1)
|
d := time.Duration(rand.Intn(4) + 1)
|
||||||
time.Sleep(d * time.Second)
|
time.Sleep(d * time.Second)
|
||||||
|
t.Logf("halt")
|
||||||
_, err := c.Halt()
|
_, err := c.Halt()
|
||||||
assertNoError(err, t, "RequestManualStop()")
|
assertNoError(err, t, "RequestManualStop()")
|
||||||
}()
|
}()
|
||||||
statech := c.Continue()
|
statech := c.Continue()
|
||||||
state := <-statech
|
state := <-statech
|
||||||
assertNoError(state.Err, t, "Continue()")
|
assertNoError(state.Err, t, "Continue()")
|
||||||
|
t.Logf("done")
|
||||||
|
<-finish
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1730,3 +1731,54 @@ func TestIssue1703(t *testing.T) {
|
|||||||
t.Logf("text: %#v\n", text)
|
t.Logf("text: %#v\n", text)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRerecord(t *testing.T) {
|
||||||
|
protest.AllowRecording(t)
|
||||||
|
if testBackend != "rr" {
|
||||||
|
t.Skip("only valid for recorded targets")
|
||||||
|
}
|
||||||
|
withTestClient2("testrerecord", t, func(c service.Client) {
|
||||||
|
fp := testProgPath(t, "testrerecord")
|
||||||
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 10})
|
||||||
|
assertNoError(err, t, "CreateBreakpoin")
|
||||||
|
|
||||||
|
gett := func() int {
|
||||||
|
state := <-c.Continue()
|
||||||
|
if state.Err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
vart, err := c.EvalVariable(api.EvalScope{-1, 0, 0}, "t", normalLoadConfig)
|
||||||
|
assertNoError(err, t, "EvalVariable")
|
||||||
|
if vart.Unreadable != "" {
|
||||||
|
t.Fatalf("Could not read variable 't': %s\n", vart.Unreadable)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Value of t is %s\n", vart.Value)
|
||||||
|
|
||||||
|
vartval, err := strconv.Atoi(vart.Value)
|
||||||
|
assertNoError(err, t, "Parsing value of variable t")
|
||||||
|
return vartval
|
||||||
|
}
|
||||||
|
|
||||||
|
t0 := gett()
|
||||||
|
|
||||||
|
_, err = c.RestartFrom(false, "", false, nil)
|
||||||
|
assertNoError(err, t, "First restart")
|
||||||
|
t1 := gett()
|
||||||
|
|
||||||
|
if t0 != t1 {
|
||||||
|
t.Fatalf("Expected same value for t after restarting (without rerecording) %d %d", t0, t1)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second) // make sure that we're not running inside the same second
|
||||||
|
|
||||||
|
_, err = c.RestartFrom(true, "", false, nil)
|
||||||
|
assertNoError(err, t, "Second restart")
|
||||||
|
t2 := gett()
|
||||||
|
|
||||||
|
if t0 == t2 {
|
||||||
|
t.Fatalf("Expected new value for t after restarting (with rerecording) %d %d", t0, t2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -138,9 +138,6 @@ func withTestProcessArgs(name string, t *testing.T, wd string, args []string, bu
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
p.Detach(true)
|
p.Detach(true)
|
||||||
if tracedir != "" {
|
|
||||||
protest.SafeRemoveAll(tracedir)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fn(p, fixture)
|
fn(p, fixture)
|
||||||
|
Loading…
Reference in New Issue
Block a user