proc/gdbserial: mozilla rr support (#804)

Implements #727
This commit is contained in:
Alessandro Arzilli 2017-05-06 00:17:52 +02:00 committed by Derek Parker
parent 1f1535802e
commit a843f7944e
37 changed files with 1843 additions and 138 deletions

@ -5,7 +5,10 @@ Command | Description
[args](#args) | Print function arguments.
[break](#break) | Sets a breakpoint.
[breakpoints](#breakpoints) | Print out info for active breakpoints.
[check](#check) | Creates a checkpoint at the current position.
[checkpoints](#checkpoints) | Print out info for existing checkpoints.
[clear](#clear) | Deletes breakpoint.
[clear-checkpoint](#clear-checkpoint) | Deletes checkpoint.
[clearall](#clearall) | Deletes multiple breakpoints.
[condition](#condition) | Set breakpoint condition.
[continue](#continue) | Run until breakpoint or program termination.
@ -22,7 +25,8 @@ Command | Description
[on](#on) | Executes a command when a breakpoint is hit.
[print](#print) | Evaluate an expression.
[regs](#regs) | Print contents of CPU registers.
[restart](#restart) | Restart process.
[restart](#restart) | Restart process from a checkpoint or event.
[rewind](#rewind) | Run backwards until breakpoint or program termination.
[set](#set) | Changes the value of a variable.
[source](#source) | Executes a file containing a list of delve commands
[sources](#sources) | Print list of source files.
@ -60,12 +64,30 @@ Print out info for active breakpoints.
Aliases: bp
## check
Creates a checkpoint at the current position.
checkpoint [where]
Aliases: checkpoint
## checkpoints
Print out info for existing checkpoints.
## clear
Deletes breakpoint.
clear <breakpoint name or id>
## clear-checkpoint
Deletes checkpoint.
checkpoint <id>
Aliases: clearcheck
## clearall
Deletes multiple breakpoints.
@ -202,10 +224,17 @@ Argument -a shows more registers.
## restart
Restart process.
Restart process from a checkpoint or event.
restart [event number or checkpoint id]
Aliases: r
## rewind
Run backwards until breakpoint or program termination.
Aliases: rw
## set
Changes the value of a variable.

@ -19,24 +19,32 @@ Pass flags to the program you are debugging using `--`, for example:
### Options
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv attach](dlv_attach.md) - Attach to running process and begin debugging.
* [dlv connect](dlv_connect.md) - Connect to a headless debug server.
* [dlv core](dlv_core.md) - Examine a core dump.
* [dlv debug](dlv_debug.md) - Compile and begin debugging main package in current directory, or the package specified.
* [dlv exec](dlv_exec.md) - Execute a precompiled binary, and begin a debug session.
* [dlv replay](dlv_replay.md) - Replays a rr trace.
* [dlv run](dlv_run.md) - Deprecated command. Use 'debug' instead.
* [dlv test](dlv_test.md) - Compile test binary and begin debugging program.
* [dlv trace](dlv_trace.md) - Compile and begin tracing program.
* [dlv version](dlv_version.md) - Prints version.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -13,23 +13,29 @@ option to let the process continue or kill it.
```
dlv attach pid
dlv attach pid [executable]
```
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -14,17 +14,23 @@ dlv connect addr
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -0,0 +1,40 @@
## dlv core
Examine a core dump.
### Synopsis
Examine a core dump.
The core command will open the specified core file and the associated
executable and let you examine the state of the process when the
core dump was taken.
```
dlv core <executable> <core>
```
### Options inherited from parent commands
```
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 5-May-2017

@ -19,17 +19,23 @@ dlv debug [package]
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -19,17 +19,23 @@ dlv exec [./path/to/binary]
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -0,0 +1,40 @@
## dlv replay
Replays a rr trace.
### Synopsis
Replays a rr trace.
The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed:
https://github.com/mozilla/rr
```
dlv replay [trace directory]
```
### Options inherited from parent commands
```
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 5-May-2017

@ -14,17 +14,23 @@ dlv run
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -19,17 +19,23 @@ dlv test [package]
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -19,24 +19,30 @@ dlv trace [package] regexp
### Options
```
-p, --pid=0: Pid to attach to.
-s, --stack=0: Show stack trace with given depth.
-p, --pid int Pid to attach to.
-s, --stack int Show stack trace with given depth.
```
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -14,17 +14,23 @@ dlv version
### Options inherited from parent commands
```
--accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version=1: Selects API version when headless.
--build-flags="": Build flags, to be passed to the compiler.
--headless[=false]: Run debug server only, in headless mode.
--init="": Init file, executed by the terminal client.
-l, --listen="localhost:0": Debugging server listen address.
--log[=false]: Enable debugging server logging.
--wd=".": Working directory for running the program.
--accept-multiclient Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
(default "default")
--build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "localhost:0")
--log Enable debugging server logging.
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
###### Auto generated by spf13/cobra on 15-Feb-2017
###### Auto generated by spf13/cobra on 5-May-2017

@ -73,6 +73,17 @@ ifneq "$(shell which lldb-server)" ""
@echo 'Testing LLDB backend (terminal)'
go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/pkg/terminal -backend=lldb
endif
ifneq "$(shell which rr)" ""
@echo
@echo 'Testing Mozilla RR backend (proc)'
go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/pkg/proc -backend=rr
@echo
@echo 'Testing Mozilla RR backend (integration)'
go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/service/test -backend=rr
@echo
@echo 'Testing Mozilla RR backend (terminal)'
go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/pkg/terminal -backend=rr
endif
test-proc-run:
go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.v -test.run="$(RUN)" -backend=$(BACKEND) $(PREFIX)/pkg/proc

@ -98,7 +98,9 @@ func New() *cobra.Command {
RootCommand.PersistentFlags().StringVar(&Backend, "backend", "default", `Backend selection:
default Uses lldb on macOS, native everywhere else.
native Native backend.
lldb Uses lldb-server or debugserver.`)
lldb Uses lldb-server or debugserver.
rr Uses mozilla rr (https://github.com/mozilla/rr).
`)
// 'attach' subcommand.
attachCommand := &cobra.Command{
@ -240,6 +242,29 @@ core dump was taken.`,
}
RootCommand.AddCommand(versionCommand)
if path, _ := exec.LookPath("rr"); path != "" {
replayCommand := &cobra.Command{
Use: "replay [trace directory]",
Short: "Replays a rr trace.",
Long: `Replays a rr trace.
The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed:
https://github.com/mozilla/rr
`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("you must provide a path to a binary")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
Backend = "rr"
os.Exit(execute(0, []string{}, conf, args[0], executingOther))
},
}
RootCommand.AddCommand(replayCommand)
}
return RootCommand
}

@ -194,6 +194,14 @@ func (p *Process) BinInfo() *proc.BinaryInfo {
return &p.bi
}
func (p *Process) Recorded() (bool, string) { return true, "" }
func (p *Process) Restart(string) error { return ErrContinueCore }
func (p *Process) Direction(proc.Direction) error { return ErrContinueCore }
func (p *Process) When() (string, error) { return "", nil }
func (p *Process) Checkpoint(string) (int, error) { return -1, ErrContinueCore }
func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, nil }
func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not found") }
func (thread *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
n, err = thread.p.core.ReadMemory(data, addr)
if err == nil && n != len(data) {

@ -92,6 +92,8 @@ const (
const heartbeatInterval = 10 * time.Second
var ErrDirChange = errors.New("direction change with internal breakpoints")
// Process implements proc.Process using a connection to a debugger stub
// that understands Gdb Remote Serial Protocol.
type Process struct {
@ -109,8 +111,9 @@ type Process struct {
breakpointIDCounter int
internalBreakpointIDCounter int
gcmdok bool // true if the stub supports g and G commands
threadStopInfo bool // true if the stub supports qThreadStopInfo
gcmdok bool // true if the stub supports g and G commands
threadStopInfo bool // true if the stub supports qThreadStopInfo
tracedir string // if attached to rr the path to the trace directory
loadGInstrAddr uint64 // address of the g loading instruction, zero if we couldn't allocate it
@ -173,6 +176,7 @@ func Connect(addr string, path string, pid int, attempts int) (*Process, error)
conn: conn,
maxTransmitAttempts: maxTransmitAttempts,
inbuf: make([]byte, 0, initialInputBufferSize),
direction: proc.Forward,
},
threads: make(map[int]*Thread),
@ -241,7 +245,7 @@ func Connect(addr string, path string, pid int, attempts int) (*Process, error)
if p.conn.pid <= 0 {
p.conn.pid, _, err = p.loadProcessInfo(0)
if err != nil {
if err != nil && !isProtocolErrorUnsupported(err) {
conn.Close()
p.bi.Close()
return nil, err
@ -415,6 +419,10 @@ func (p *Process) BinInfo() *proc.BinaryInfo {
return &p.bi
}
func (p *Process) Recorded() (bool, string) {
return p.tracedir != "", p.tracedir
}
func (p *Process) Pid() int {
return int(p.conn.pid)
}
@ -464,11 +472,13 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
return nil, &proc.ProcessExitedError{Pid: p.conn.pid}
}
// step threads stopped at any breakpoint over their breakpoint
for _, thread := range p.threads {
if thread.CurrentBreakpoint != nil {
if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil {
return nil, err
if p.conn.direction == proc.Forward {
// step threads stopped at any breakpoint over their breakpoint
for _, thread := range p.threads {
if thread.CurrentBreakpoint != nil {
if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil {
return nil, err
}
}
}
}
@ -593,11 +603,17 @@ func (p *Process) SwitchGoroutine(gid int) error {
}
func (p *Process) RequestManualStop() error {
if !p.conn.running {
return nil
}
p.ctrlC = true
return p.conn.sendCtrlC()
}
func (p *Process) Halt() error {
if p.exited {
return nil
}
p.ctrlC = true
return p.conn.sendCtrlC()
}
@ -634,6 +650,153 @@ func (p *Process) Detach(kill bool) error {
return p.bi.Close()
}
func (p *Process) Restart(pos string) error {
if p.tracedir == "" {
return proc.NotRecordedErr
}
p.exited = false
p.allGCache = nil
for _, th := range p.threads {
th.clearBreakpointState()
}
p.ctrlC = false
err := p.conn.restart(pos)
if err != nil {
return err
}
// for some reason we have to send a vCont;c after a vRun to make rr behave
// properly, because that's what gdb does.
_, _, err = p.conn.resume(0, nil)
if err != nil {
return err
}
err = p.updateThreadList(&threadUpdater{p: p})
if err != nil {
return err
}
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
for addr := range p.breakpoints {
p.conn.setBreakpoint(addr)
}
if err := p.setCurrentBreakpoints(); err != nil {
return err
}
return nil
}
func (p *Process) When() (string, error) {
if p.tracedir == "" {
return "", proc.NotRecordedErr
}
event, err := p.conn.qRRCmd("when")
if err != nil {
return "", err
}
return strings.TrimSpace(event), nil
}
const (
checkpointPrefix = "Checkpoint "
)
func (p *Process) Checkpoint(where string) (int, error) {
if p.tracedir == "" {
return -1, proc.NotRecordedErr
}
resp, err := p.conn.qRRCmd("checkpoint", where)
if err != nil {
return -1, err
}
if !strings.HasPrefix(resp, checkpointPrefix) {
return -1, fmt.Errorf("can not parse checkpoint response %q", resp)
}
idstr := resp[len(checkpointPrefix):]
space := strings.Index(idstr, " ")
if space < 0 {
return -1, fmt.Errorf("can not parse checkpoint response %q", resp)
}
idstr = idstr[:space]
cpid, err := strconv.Atoi(idstr)
if err != nil {
return -1, err
}
return cpid, nil
}
func (p *Process) Checkpoints() ([]proc.Checkpoint, error) {
if p.tracedir == "" {
return nil, proc.NotRecordedErr
}
resp, err := p.conn.qRRCmd("info checkpoints")
if err != nil {
return nil, err
}
lines := strings.Split(resp, "\n")
r := make([]proc.Checkpoint, 0, len(lines)-1)
for _, line := range lines[1:] {
if line == "" {
continue
}
fields := strings.Split(line, "\t")
if len(fields) != 3 {
return nil, fmt.Errorf("can not parse \"info checkpoints\" output line %q", line)
}
cpid, err := strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("can not parse \"info checkpoints\" output line %q: %v", line, err)
}
r = append(r, proc.Checkpoint{cpid, fields[1], fields[2]})
}
return r, nil
}
const deleteCheckpointPrefix = "Deleted checkpoint "
func (p *Process) ClearCheckpoint(id int) error {
if p.tracedir == "" {
return proc.NotRecordedErr
}
resp, err := p.conn.qRRCmd("delete checkpoint", strconv.Itoa(id))
if err != nil {
return err
}
if !strings.HasPrefix(resp, deleteCheckpointPrefix) {
return errors.New(resp)
}
return nil
}
func (p *Process) Direction(dir proc.Direction) error {
if p.tracedir == "" {
return proc.NotRecordedErr
}
if p.conn.conn == nil {
return proc.ProcessExitedError{Pid: p.conn.pid}
}
if p.conn.direction == dir {
return nil
}
for _, bp := range p.Breakpoints() {
if bp.Internal() {
return ErrDirChange
}
}
p.conn.direction = dir
return nil
}
func (p *Process) Breakpoints() map[uint64]*proc.Breakpoint {
return p.breakpoints
}
@ -762,6 +925,12 @@ func (tu *threadUpdater) Finish() {
tu.p.currentThread = nil
}
}
if tu.p.currentThread != nil {
if _, exists := tu.p.threads[tu.p.currentThread.ID]; !exists {
// current thread was removed
tu.p.currentThread = nil
}
}
if tu.p.currentThread == nil {
for _, thread := range tu.p.threads {
tu.p.currentThread = thread
@ -991,6 +1160,16 @@ func (t *Thread) reloadRegisters() error {
}
}
switch t.p.bi.GOOS {
case "linux":
if reg, hasFsBase := t.regs.regs[regnameFsBase]; hasFsBase {
t.regs.gaddr = 0
t.regs.tls = binary.LittleEndian.Uint64(reg.value)
t.regs.hasgaddr = false
return nil
}
}
if t.p.loadGInstrAddr > 0 {
return t.reloadGAlloc()
}

@ -25,6 +25,8 @@ type gdbConn struct {
running bool
direction proc.Direction // direction of execution
packetSize int // maximum packet size supported by stub
regsInfo []gdbRegisterInfo // list of registers
@ -38,10 +40,12 @@ type gdbConn struct {
}
const (
regnamePC = "rip"
regnameCX = "rcx"
regnameSP = "rsp"
regnameBP = "rbp"
regnamePC = "rip"
regnameCX = "rcx"
regnameSP = "rsp"
regnameBP = "rbp"
regnameFsBase = "fs_base"
regnameGsBase = "gs_base"
)
var ErrTooManyAttempts = errors.New("too many transmit attempts")
@ -269,6 +273,9 @@ func (conn *gdbConn) readRegisterInfo() (err error) {
fmt.Fprintf(&conn.outbuf, "$qRegisterInfo%x", regnum)
respbytes, err := conn.exec(conn.outbuf.Bytes(), "register info")
if err != nil {
if regnum == 0 {
return err
}
break
}
@ -410,7 +417,7 @@ func (conn *gdbConn) kill() error {
// kill. This is not an error.
conn.conn.Close()
conn.conn = nil
return nil
return proc.ProcessExitedError{Pid: conn.pid}
}
if err != nil {
return err
@ -515,11 +522,19 @@ func (conn *gdbConn) writeRegister(threadID string, regnum int, data []byte) err
// resume executes a 'vCont' command on all threads with action 'c' if sig
// is 0 or 'C' if it isn't.
func (conn *gdbConn) resume(sig uint8, tu *threadUpdater) (string, uint8, error) {
conn.outbuf.Reset()
if sig == 0 {
fmt.Fprintf(&conn.outbuf, "$vCont;c")
if conn.direction == proc.Forward {
conn.outbuf.Reset()
if sig == 0 {
fmt.Fprintf(&conn.outbuf, "$vCont;c")
} else {
fmt.Fprintf(&conn.outbuf, "$vCont;C%02x", sig)
}
} else {
fmt.Fprintf(&conn.outbuf, "$vCont;C%02x", sig)
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
return "", 0, err
}
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$bc")
}
if err := conn.send(conn.outbuf.Bytes()); err != nil {
return "", 0, err
@ -533,8 +548,16 @@ func (conn *gdbConn) resume(sig uint8, tu *threadUpdater) (string, uint8, error)
// step executes a 'vCont' command on the specified thread with 's' action.
func (conn *gdbConn) step(threadID string, tu *threadUpdater) (string, uint8, error) {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$vCont;s:%s", threadID)
if conn.direction == proc.Forward {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$vCont;s:%s", threadID)
} else {
if err := conn.selectThread('c', threadID, "step"); err != nil {
return "", 0, err
}
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$bs")
}
if err := conn.send(conn.outbuf.Bytes()); err != nil {
return "", 0, err
}
@ -808,15 +831,19 @@ func (conn *gdbConn) readMemory(data []byte, addr uintptr) error {
return nil
}
func writeAsciiBytes(w io.Writer, data []byte) {
for _, b := range data {
fmt.Fprintf(w, "%02x", b)
}
}
// executes 'M' (write memory) command
func (conn *gdbConn) writeMemory(addr uintptr, data []byte) (written int, err error) {
conn.outbuf.Reset()
//TODO(aarzilli): do not send packets larger than conn.PacketSize
fmt.Fprintf(&conn.outbuf, "$M%x,%x:", addr, len(data))
for _, b := range data {
fmt.Fprintf(&conn.outbuf, "%02x", b)
}
writeAsciiBytes(&conn.outbuf, data)
_, err = conn.exec(conn.outbuf.Bytes(), "memory write")
if err != nil {
@ -851,6 +878,41 @@ func (conn *gdbConn) threadStopInfo(threadID string) (sig uint8, reason string,
return sp.sig, sp.reason, nil
}
// restart executes a 'vRun' command.
func (conn *gdbConn) restart(pos string) error {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$vRun;")
if pos != "" {
fmt.Fprintf(&conn.outbuf, ";")
writeAsciiBytes(&conn.outbuf, []byte(pos))
}
_, err := conn.exec(conn.outbuf.Bytes(), "restart")
return err
}
// qRRCmd executes a qRRCmd command
func (conn *gdbConn) qRRCmd(args ...string) (string, error) {
if len(args) == 0 {
panic("must specify at least one argument for qRRCmd")
}
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$qRRCmd")
for _, arg := range args {
fmt.Fprintf(&conn.outbuf, ":")
writeAsciiBytes(&conn.outbuf, []byte(arg))
}
resp, err := conn.exec(conn.outbuf.Bytes(), "qRRCmd")
if err != nil {
return "", err
}
data := make([]byte, 0, len(resp)/2)
for i := 0; i < len(resp); i += 2 {
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
data = append(data, uint8(n))
}
return string(data), nil
}
// exec executes a message to the stub and reads a response.
// The details of the wire protocol are described here:
// https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview

231
pkg/proc/gdbserial/rr.go Normal file

@ -0,0 +1,231 @@
package gdbserial
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"unicode"
)
// Record uses rr to record the execution of the specified program and
// returns the trace directory's path.
func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
rfd, wfd, err := os.Pipe()
if err != nil {
return "", err
}
args := make([]string, 0, len(cmd)+2)
args = append(args, "record", "--print-trace-dir=3")
args = append(args, cmd...)
rrcmd := exec.Command("rr", args...)
rrcmd.Stdin = os.Stdin
if !quiet {
rrcmd.Stdout = os.Stdout
rrcmd.Stderr = os.Stderr
}
rrcmd.ExtraFiles = []*os.File{wfd}
rrcmd.Dir = wd
done := make(chan struct{})
go func() {
bs, _ := ioutil.ReadAll(rfd)
tracedir = strings.TrimSpace(string(bs))
close(done)
}()
err = rrcmd.Run()
// ignore run errors, it could be the program crashing
wfd.Close()
<-done
return
}
// Replay starts an instance of rr in replay mode, with the specified trace
// directory, and connects to it.
func Replay(tracedir string, quiet bool) (*Process, error) {
rrcmd := exec.Command("rr", "replay", "--dbgport=0", tracedir)
rrcmd.Stdout = os.Stdout
stderr, err := rrcmd.StderrPipe()
if err != nil {
return nil, err
}
rrcmd.SysProcAttr = backgroundSysProcAttr()
initch := make(chan rrInit)
go rrStderrParser(stderr, initch, quiet)
err = rrcmd.Start()
if err != nil {
return nil, err
}
init := <-initch
if init.err != nil {
rrcmd.Process.Kill()
return nil, err
}
p, err := Connect(init.port, init.exe, 0, 10)
if err != nil {
rrcmd.Process.Kill()
return nil, err
}
p.process = rrcmd
p.tracedir = tracedir
return p, nil
}
type rrInit struct {
port string
exe string
err error
}
const (
rrGdbCommandPrefix = " gdb "
rrGdbLaunchPrefix = "Launch gdb with"
targetCmd = "target extended-remote "
)
func rrStderrParser(stderr io.Reader, initch chan<- rrInit, quiet bool) {
rd := bufio.NewReader(stderr)
for {
line, err := rd.ReadString('\n')
if err != nil {
initch <- rrInit{"", "", err}
close(initch)
return
}
if strings.HasPrefix(line, rrGdbCommandPrefix) {
initch <- rrParseGdbCommand(line[len(rrGdbCommandPrefix):])
close(initch)
break
}
if strings.HasPrefix(line, rrGdbLaunchPrefix) {
continue
}
if !quiet {
os.Stderr.Write([]byte(line))
}
}
io.Copy(os.Stderr, rd)
}
type ErrMalformedRRGdbCommand struct {
line, reason string
}
func (err *ErrMalformedRRGdbCommand) Error() string {
return fmt.Sprintf("malformed gdb command %q: %s", err.line, err.reason)
}
func rrParseGdbCommand(line string) rrInit {
port := ""
fields := splitQuotedFields(line)
for i := 0; i < len(fields); i++ {
switch fields[i] {
case "-ex":
if i+1 >= len(fields) {
return rrInit{err: &ErrMalformedRRGdbCommand{line, "-ex not followed by an argument"}}
}
arg := fields[i+1]
if !strings.HasPrefix(arg, targetCmd) {
return rrInit{err: &ErrMalformedRRGdbCommand{line, "contents of -ex argument unexpected"}}
}
port = arg[len(targetCmd):]
i++
case "-l":
// skip argument
i++
}
}
if port == "" {
return rrInit{err: &ErrMalformedRRGdbCommand{line, "could not find -ex argument"}}
}
exe := fields[len(fields)-1]
return rrInit{port: port, exe: exe}
}
// Like strings.Fields but ignores spaces inside areas surrounded
// by single quotes.
// To specify a single quote use backslash to escape it: '\''
func splitQuotedFields(in string) []string {
type stateEnum int
const (
inSpace stateEnum = iota
inField
inQuote
inQuoteEscaped
)
state := inSpace
r := []string{}
var buf bytes.Buffer
for _, ch := range in {
switch state {
case inSpace:
if ch == '\'' {
state = inQuote
} else if !unicode.IsSpace(ch) {
buf.WriteRune(ch)
state = inField
}
case inField:
if ch == '\'' {
state = inQuote
} else if unicode.IsSpace(ch) {
r = append(r, buf.String())
buf.Reset()
} else {
buf.WriteRune(ch)
}
case inQuote:
if ch == '\'' {
state = inField
} else if ch == '\\' {
state = inQuoteEscaped
} else {
buf.WriteRune(ch)
}
case inQuoteEscaped:
buf.WriteRune(ch)
state = inQuote
}
}
if buf.Len() != 0 {
r = append(r, buf.String())
}
return r
}
// RecordAndReplay acts like calling Record and then Replay.
func RecordAndReplay(cmd []string, wd string, quiet bool) (p *Process, tracedir string, err error) {
tracedir, err = Record(cmd, wd, quiet)
if tracedir == "" {
return nil, "", err
}
p, err = Replay(tracedir, quiet)
return p, tracedir, err
}

@ -0,0 +1,249 @@
package gdbserial_test
import (
"fmt"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/gdbserial"
protest "github.com/derekparker/delve/pkg/proc/test"
)
func withTestRecording(name string, t testing.TB, fn func(p *gdbserial.Process, fixture protest.Fixture)) {
fixture := protest.BuildFixture(name)
protest.MustHaveRecordingAllowed(t)
if path, _ := exec.LookPath("rr"); path == "" {
t.Skip("test skipped, rr not found")
}
t.Log("recording")
p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true)
if err != nil {
t.Fatal("Launch():", err)
}
t.Logf("replaying %q", tracedir)
defer func() {
p.Halt()
p.Detach(true)
if tracedir != "" {
protest.SafeRemoveAll(tracedir)
}
}()
fn(p, fixture)
}
func assertNoError(err error, t testing.TB, s string) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file)
t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
}
}
func setFunctionBreakpoint(p proc.Process, t *testing.T, fname string) *proc.Breakpoint {
addr, err := proc.FindFunctionLocation(p, fname, true, 0)
assertNoError(err, t, fmt.Sprintf("FindFunctionLocation(%s)", fname))
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x) function %s", addr, fname))
return bp
}
func TestRestartAfterExit(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("testnextprog", t, func(p *gdbserial.Process, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue")
loc, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location()")
err = proc.Continue(p)
if _, isexited := err.(proc.ProcessExitedError); err == nil || !isexited {
t.Fatalf("program did not exit: %v", err)
}
assertNoError(p.Restart(""), t, "Restart")
assertNoError(proc.Continue(p), t, "Continue (after restart)")
loc2, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location() (after restart)")
if loc2.Line != loc.Line {
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
}
err = proc.Continue(p)
if _, isexited := err.(proc.ProcessExitedError); err == nil || !isexited {
t.Fatalf("program did not exit (after exit): %v", err)
}
})
}
func TestRestartDuringStop(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("testnextprog", t, func(p *gdbserial.Process, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue")
loc, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location()")
assertNoError(p.Restart(""), t, "Restart")
assertNoError(proc.Continue(p), t, "Continue (after restart)")
loc2, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location() (after restart)")
if loc2.Line != loc.Line {
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
}
err = proc.Continue(p)
if _, isexited := err.(proc.ProcessExitedError); err == nil || !isexited {
t.Fatalf("program did not exit (after exit): %v", err)
}
})
}
func setFileBreakpoint(p proc.Process, t *testing.T, file string, line int) *proc.Breakpoint {
addr, _, err := p.BinInfo().LineToPC(file, line)
assertNoError(err, t, "LineToPC")
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x) - %s:%d", addr, file, line))
return bp
}
func TestReverseBreakpointCounts(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("bpcountstest", t, func(p *gdbserial.Process, fixture protest.Fixture) {
endbp := setFileBreakpoint(p, t, fixture.Source, 28)
assertNoError(proc.Continue(p), t, "Continue()")
loc, _ := p.CurrentThread().Location()
if loc.PC != endbp.Addr {
t.Fatalf("did not reach end of main.main function: %s:%d (%#x)", loc.File, loc.Line, loc.PC)
}
p.ClearBreakpoint(endbp.Addr)
assertNoError(p.Direction(proc.Backward), t, "Switching to backward direction")
bp := setFileBreakpoint(p, t, fixture.Source, 12)
startbp := setFileBreakpoint(p, t, fixture.Source, 20)
countLoop:
for {
assertNoError(proc.Continue(p), t, "Continue()")
loc, _ := p.CurrentThread().Location()
switch loc.PC {
case startbp.Addr:
break countLoop
case bp.Addr:
// ok
default:
t.Fatalf("unexpected stop location %s:%d %#x", loc.File, loc.Line, loc.PC)
}
}
t.Logf("TotalHitCount: %d", bp.TotalHitCount)
if bp.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.TotalHitCount)
}
if len(bp.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.HitCount))
}
for _, v := range bp.HitCount {
if v != 100 {
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.HitCount)
}
}
})
}
func getPosition(p *gdbserial.Process, t *testing.T) (when string, loc *proc.Location) {
var err error
when, err = p.When()
assertNoError(err, t, "When")
loc, err = p.CurrentThread().Location()
assertNoError(err, t, "Location")
return
}
func TestCheckpoints(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("continuetestprog", t, func(p *gdbserial.Process, fixture protest.Fixture) {
// Continues until start of main.main, record output of 'when'
bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue")
when0, loc0 := getPosition(p, t)
t.Logf("when0: %q (%#x)", when0, loc0.PC)
// Create a checkpoint and check that the list of checkpoints reflects this
cpid, err := p.Checkpoint("checkpoint1")
if cpid != 1 {
t.Errorf("unexpected checkpoint id %d", cpid)
}
assertNoError(err, t, "Checkpoint")
checkpoints, err := p.Checkpoints()
assertNoError(err, t, "Checkpoints")
if len(checkpoints) != 1 {
t.Fatalf("wrong number of checkpoints %v (one expected)", checkpoints)
}
// Move forward with next, check that the output of 'when' changes
assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next")
when1, loc1 := getPosition(p, t)
t.Logf("when1: %q (%#x)", when1, loc1.PC)
if loc0.PC == loc1.PC {
t.Fatalf("next did not move process %#x", loc0.PC)
}
if when0 == when1 {
t.Fatalf("output of when did not change after next: %q", when0)
}
// Move back to checkpoint, check that the output of 'when' is the same as
// what it was when we set the breakpoint
p.Restart(fmt.Sprintf("c%d", cpid))
when2, loc2 := getPosition(p, t)
t.Logf("when2: %q (%#x)", when2, loc2.PC)
if loc2.PC != loc0.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC)
}
if when0 != when2 {
t.Fatalf("output of when mismatched %q != %q", when0, when2)
}
// Move forward with next again, check that the output of 'when' matches
assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next")
when3, loc3 := getPosition(p, t)
t.Logf("when3: %q (%#x)", when3, loc3.PC)
if loc3.PC != loc1.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc3.PC)
}
if when3 != when1 {
t.Fatalf("when output mismatch %q != %q", when1, when3)
}
// Delete breakpoint, move back to checkpoint then next twice and check
// output of 'when' again
_, err = p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint")
p.Restart(fmt.Sprintf("c%d", cpid))
assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next")
when4, loc4 := getPosition(p, t)
t.Logf("when4: %q (%#x)", when4, loc4.PC)
if loc4.PC != loc1.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc4.PC)
}
if when4 != when1 {
t.Fatalf("when output mismatch %q != %q", when1, when4)
}
// Delete checkpoint, check that the list of checkpoints is updated
assertNoError(p.ClearCheckpoint(cpid), t, "ClearCheckpoint")
checkpoints, err = p.Checkpoints()
assertNoError(err, t, "Checkpoints")
if len(checkpoints) != 0 {
t.Fatalf("wrong number of checkpoints %v (zero expected)", checkpoints)
}
})
}

@ -10,6 +10,43 @@ type Process interface {
Info
ProcessManipulation
BreakpointManipulation
RecordingManipulation
}
// RecordingManipulation is an interface for manipulating process recordings.
type RecordingManipulation interface {
// Recorded returns true if the current process is a recording and the path
// to the trace directory.
Recorded() (recorded bool, tracedir string)
// Restart restarts the recording from the specified position, or from the
// last checkpoint if pos == "".
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
// number.
Restart(pos string) error
// Direction changes execution direction.
Direction(Direction) error
// When returns current recording position.
When() (string, error)
// Checkpoint sets a checkpoint at the current position.
Checkpoint(where string) (id int, err error)
// Checkpoints returns the list of currently set checkpoint.
Checkpoints() ([]Checkpoint, error)
// ClearCheckpoint removes a checkpoint.
ClearCheckpoint(id int) error
}
type Direction int8
const (
Forward Direction = 0
Backward Direction = 1
)
// Checkpoint is a checkpoint
type Checkpoint struct {
ID int
When string
Where string
}
// Info is an interface that provides general information on the target.

@ -67,6 +67,14 @@ func (dbp *Process) BinInfo() *proc.BinaryInfo {
return &dbp.bi
}
func (dbp *Process) Recorded() (bool, string) { return false, "" }
func (dbp *Process) Restart(string) error { return proc.NotRecordedErr }
func (dbp *Process) Direction(proc.Direction) error { return proc.NotRecordedErr }
func (dbp *Process) When() (string, error) { return "", nil }
func (dbp *Process) Checkpoint(string) (int, error) { return -1, proc.NotRecordedErr }
func (dbp *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.NotRecordedErr }
func (dbp *Process) ClearCheckpoint(int) error { return proc.NotRecordedErr }
// Detach from the process being debugged, optionally killing it.
func (dbp *Process) Detach(kill bool) (err error) {
if dbp.exited {

@ -20,6 +20,7 @@ type functionDebugInfo struct {
}
var NotExecutableErr = errors.New("not an executable file")
var NotRecordedErr = errors.New("not a recording")
// ProcessExitedError indicates that the process has exited and contains both
// process id and exit status.
@ -114,7 +115,7 @@ func Continue(dbp Process) error {
switch {
case curbp == nil:
// runtime.Breakpoint or manual stop
if onRuntimeBreakpoint(curthread) {
if recorded, _ := dbp.Recorded(); onRuntimeBreakpoint(curthread) && !recorded {
// Single-step current thread until we exit runtime.breakpoint and
// runtime.Breakpoint.
// On go < 1.8 it was sufficient to single-step twice on go1.8 a change

@ -49,11 +49,17 @@ func withTestProcess(name string, t testing.TB, fn func(p proc.Process, fixture
fixture := protest.BuildFixture(name)
var p proc.Process
var err error
var tracedir string
switch testBackend {
case "native":
p, err = native.Launch([]string{fixture.Path}, ".")
case "lldb":
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".")
case "rr":
protest.MustHaveRecordingAllowed(t)
t.Log("recording")
p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true)
t.Logf("replaying %q", tracedir)
default:
t.Fatalf("unknown backend %q", testBackend)
}
@ -64,6 +70,9 @@ func withTestProcess(name string, t testing.TB, fn func(p proc.Process, fixture
defer func() {
p.Halt()
p.Detach(true)
if tracedir != "" {
protest.SafeRemoveAll(tracedir)
}
}()
fn(p, fixture)
@ -73,12 +82,18 @@ func withTestProcessArgs(name string, t testing.TB, wd string, fn func(p proc.Pr
fixture := protest.BuildFixture(name)
var p proc.Process
var err error
var tracedir string
switch testBackend {
case "native":
p, err = native.Launch(append([]string{fixture.Path}, args...), wd)
case "lldb":
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd)
case "rr":
protest.MustHaveRecordingAllowed(t)
t.Log("recording")
p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, wd, true)
t.Logf("replaying %q", tracedir)
default:
t.Fatal("unknown backend")
}
@ -89,6 +104,9 @@ func withTestProcessArgs(name string, t testing.TB, wd string, fn func(p proc.Pr
defer func() {
p.Halt()
p.Detach(true)
if tracedir != "" {
protest.SafeRemoveAll(tracedir)
}
}()
fn(p, fixture)
@ -133,6 +151,7 @@ func currentLineNumber(p proc.Process, t *testing.T) (string, int) {
}
func TestExit(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("continuetestprog", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
pe, ok := err.(proc.ProcessExitedError)
@ -149,6 +168,7 @@ func TestExit(t *testing.T) {
}
func TestExitAfterContinue(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("continuetestprog", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.sayhi")
assertNoError(err, t, "setFunctionBreakpoint()")
@ -234,6 +254,7 @@ func TestHalt(t *testing.T) {
}
func TestStep(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testprog", t, func(p proc.Process, fixture protest.Fixture) {
helloworldaddr, err := proc.FindFunctionLocation(p, "main.helloworld", false, 0)
assertNoError(err, t, "FindFunctionLocation")
@ -256,6 +277,7 @@ func TestStep(t *testing.T) {
}
func TestBreakpoint(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testprog", t, func(p proc.Process, fixture protest.Fixture) {
helloworldaddr, err := proc.FindFunctionLocation(p, "main.helloworld", false, 0)
assertNoError(err, t, "FindFunctionLocation")
@ -280,6 +302,7 @@ func TestBreakpoint(t *testing.T) {
}
func TestBreakpointInSeperateGoRoutine(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testthreads", t, func(p proc.Process, fixture protest.Fixture) {
fnentry, err := proc.FindFunctionLocation(p, "main.anotherthread", false, 0)
assertNoError(err, t, "FindFunctionLocation")
@ -355,6 +378,7 @@ const (
)
func testseq(program string, contFunc contFunc, testcases []nextTest, initialLocation string, t *testing.T) {
protest.AllowRecording(t)
withTestProcess(program, t, func(p proc.Process, fixture protest.Fixture) {
var bp *proc.Breakpoint
var err error
@ -371,7 +395,9 @@ func testseq(program string, contFunc contFunc, testcases []nextTest, initialLoc
p.ClearBreakpoint(bp.Addr)
regs, err := p.CurrentThread().Registers(false)
assertNoError(err, t, "Registers")
assertNoError(regs.SetPC(p.CurrentThread(), bp.Addr), t, "SetPC")
if testBackend != "rr" {
assertNoError(regs.SetPC(p.CurrentThread(), bp.Addr), t, "SetPC")
}
f, ln := currentLineNumber(p, t)
for _, tc := range testcases {
@ -455,6 +481,7 @@ func TestNextConcurrent(t *testing.T) {
{9, 10},
{10, 11},
}
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.sayhi")
assertNoError(err, t, "SetBreakpoint")
@ -496,6 +523,7 @@ func TestNextConcurrentVariant2(t *testing.T) {
{9, 10},
{10, 11},
}
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.sayhi")
assertNoError(err, t, "SetBreakpoint")
@ -545,6 +573,7 @@ func TestNextFunctionReturn(t *testing.T) {
{14, 15},
{15, 35},
}
protest.AllowRecording(t)
testseq("testnextprog", contNext, testcases, "main.helloworld", t)
}
@ -557,6 +586,7 @@ func TestNextFunctionReturnDefer(t *testing.T) {
{6, 7},
{7, 8},
}
protest.AllowRecording(t)
testseq("testnextdefer", contNext, testcases, "main.main", t)
}
@ -609,9 +639,9 @@ func TestRuntimeBreakpoint(t *testing.T) {
regs, err := p.CurrentThread().Registers(false)
assertNoError(err, t, "Registers")
pc := regs.PC()
_, l, _ := p.BinInfo().PCToLine(pc)
f, l, _ := p.BinInfo().PCToLine(pc)
if l != 10 {
t.Fatal("did not respect breakpoint")
t.Fatalf("did not respect breakpoint %s:%d", f, l)
}
})
}
@ -628,6 +658,7 @@ func returnAddress(thread proc.Thread) (uint64, error) {
}
func TestFindReturnAddress(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testnextprog", t, func(p proc.Process, fixture protest.Fixture) {
start, _, err := p.BinInfo().LineToPC(fixture.Source, 24)
if err != nil {
@ -653,6 +684,7 @@ func TestFindReturnAddress(t *testing.T) {
}
func TestFindReturnAddressTopOfStackFn(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testreturnaddress", t, func(p proc.Process, fixture protest.Fixture) {
fnName := "runtime.rt0_go"
fnentry, err := proc.FindFunctionLocation(p, fnName, false, 0)
@ -670,6 +702,7 @@ func TestFindReturnAddressTopOfStackFn(t *testing.T) {
}
func TestSwitchThread(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testnextprog", t, func(p proc.Process, fixture protest.Fixture) {
// With invalid thread id
err := p.SwitchThread(-1)
@ -720,6 +753,7 @@ func TestCGONext(t *testing.T) {
return
}
protest.AllowRecording(t)
withTestProcess("cgotest", t, func(p proc.Process, fixture protest.Fixture) {
pc, err := proc.FindFunctionLocation(p, "main.main", true, 0)
if err != nil {
@ -759,6 +793,7 @@ func TestStacktrace(t *testing.T) {
{{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}},
{{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
}
protest.AllowRecording(t)
withTestProcess("stacktraceprog", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "BreakByLocation()")
@ -841,6 +876,7 @@ func TestStacktraceGoroutine(t *testing.T) {
mainStack := []loc{{13, "main.stacktraceme"}, {26, "main.main"}}
agoroutineStacks := [][]loc{[]loc{{8, "main.agoroutine"}}, []loc{{9, "main.agoroutine"}}, []loc{{10, "main.agoroutine"}}}
protest.AllowRecording(t)
withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "BreakByLocation()")
@ -965,12 +1001,14 @@ func TestGetG(t *testing.T) {
return
}
protest.AllowRecording(t)
withTestProcess("cgotest", t, func(p proc.Process, fixture protest.Fixture) {
testGSupportFunc("cgo", t, p, fixture)
})
}
func TestContinueMulti(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("integrationprog", t, func(p proc.Process, fixture protest.Fixture) {
bp1, err := setFunctionBreakpoint(p, "main.main")
assertNoError(err, t, "BreakByLocation()")
@ -1035,6 +1073,7 @@ func TestParseVersionString(t *testing.T) {
}
func TestBreakpointOnFunctionEntry(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testprog", t, func(p proc.Process, fixture protest.Fixture) {
addr, err := proc.FindFunctionLocation(p, "main.main", false, 0)
assertNoError(err, t, "FindFunctionLocation()")
@ -1049,6 +1088,7 @@ func TestBreakpointOnFunctionEntry(t *testing.T) {
}
func TestProcessReceivesSIGCHLD(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("sigchldprog", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
_, ok := err.(proc.ProcessExitedError)
@ -1068,8 +1108,33 @@ func TestIssue239(t *testing.T) {
})
}
func findFirstNonRuntimeFrame(p proc.Process) (proc.Stackframe, error) {
frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10)
if err != nil {
return proc.Stackframe{}, err
}
for _, frame := range frames {
if frame.Current.Fn != nil && !strings.HasPrefix(frame.Current.Fn.Name, "runtime.") {
return frame, nil
}
}
return proc.Stackframe{}, fmt.Errorf("non-runtime frame not found")
}
func evalVariable(p proc.Process, symbol string) (*proc.Variable, error) {
scope, err := proc.GoroutineScope(p.CurrentThread())
var scope *proc.EvalScope
var err error
if testBackend == "rr" {
var frame proc.Stackframe
frame, err = findFirstNonRuntimeFrame(p)
if err == nil {
scope = proc.FrameToScope(p, frame)
}
} else {
scope, err = proc.GoroutineScope(p.CurrentThread())
}
if err != nil {
return nil, err
@ -1086,6 +1151,7 @@ func setVariable(p proc.Process, symbol, value string) error {
}
func TestVariableEvaluation(t *testing.T) {
protest.AllowRecording(t)
testcases := []struct {
name string
st reflect.Kind
@ -1171,6 +1237,7 @@ func TestVariableEvaluation(t *testing.T) {
}
func TestFrameEvaluation(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "setFunctionBreakpoint")
@ -1292,6 +1359,7 @@ func TestVariableFunctionScoping(t *testing.T) {
}
func TestRecursiveStructure(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
v, err := evalVariable(p, "aas")
@ -1302,6 +1370,7 @@ func TestRecursiveStructure(t *testing.T) {
func TestIssue316(t *testing.T) {
// A pointer loop that includes one interface should not send dlv into an infinite loop
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
_, err := evalVariable(p, "iface5")
@ -1311,6 +1380,7 @@ func TestIssue316(t *testing.T) {
func TestIssue325(t *testing.T) {
// nil pointer dereference when evaluating interfaces to function pointers
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
iface2fn1v, err := evalVariable(p, "iface2fn1")
@ -1324,6 +1394,7 @@ func TestIssue325(t *testing.T) {
}
func TestBreakpointCounts(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("bpcountstest", t, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 12)
assertNoError(err, t, "LineToPC")
@ -1358,6 +1429,7 @@ func TestBreakpointCounts(t *testing.T) {
func BenchmarkArray(b *testing.B) {
// each bencharr struct is 128 bytes, bencharr is 64 elements long
protest.AllowRecording(b)
b.SetBytes(int64(64 * 128))
withTestProcess("testvariables2", b, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), b, "Continue()")
@ -1375,6 +1447,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
return
}
m := map[int64]int64{}
protest.AllowRecording(t)
withTestProcess("bpcountstest", t, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 12)
assertNoError(err, t, "LineToPC")
@ -1433,6 +1506,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
func BenchmarkArrayPointer(b *testing.B) {
// each bencharr struct is 128 bytes, benchparr is an array of 64 pointers to bencharr
// each read will read 64 bencharr structs plus the 64 pointers of benchparr
protest.AllowRecording(b)
b.SetBytes(int64(64*128 + 64*8))
withTestProcess("testvariables2", b, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), b, "Continue()")
@ -1447,6 +1521,7 @@ func BenchmarkMap(b *testing.B) {
// m1 contains 41 entries, each one has a value that's 2 int values (2* 8 bytes) and a string key
// each string key has an average of 9 character
// reading strings and the map structure imposes a overhead that we ignore here
protest.AllowRecording(b)
b.SetBytes(int64(41 * (2*8 + 9)))
withTestProcess("testvariables2", b, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), b, "Continue()")
@ -1458,6 +1533,7 @@ func BenchmarkMap(b *testing.B) {
}
func BenchmarkGoroutinesInfo(b *testing.B) {
protest.AllowRecording(b)
withTestProcess("testvariables2", b, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), b, "Continue()")
for i := 0; i < b.N; i++ {
@ -1473,6 +1549,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) {
func TestIssue262(t *testing.T) {
// Continue does not work when the current breakpoint is set on a NOP instruction
protest.AllowRecording(t)
withTestProcess("issue262", t, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 11)
assertNoError(err, t, "LineToPC")
@ -1495,6 +1572,7 @@ func TestIssue305(t *testing.T) {
// If 'next' hits a breakpoint on the goroutine it's stepping through
// the internal breakpoints aren't cleared preventing further use of
// 'next' command
protest.AllowRecording(t)
withTestProcess("issue305", t, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 5)
assertNoError(err, t, "LineToPC()")
@ -1514,6 +1592,7 @@ func TestIssue305(t *testing.T) {
func TestPointerLoops(t *testing.T) {
// Pointer loops through map entries, pointers and slices
// Regression test for issue #341
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
for _, expr := range []string{"mapinf", "ptrinf", "sliceinf"} {
@ -1526,6 +1605,7 @@ func TestPointerLoops(t *testing.T) {
}
func BenchmarkLocalVariables(b *testing.B) {
protest.AllowRecording(b)
withTestProcess("testvariables", b, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), b, "Continue() returned an error")
scope, err := proc.GoroutineScope(p.CurrentThread())
@ -1538,6 +1618,7 @@ func BenchmarkLocalVariables(b *testing.B) {
}
func TestCondBreakpoint(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 9)
assertNoError(err, t, "LineToPC")
@ -1562,6 +1643,7 @@ func TestCondBreakpoint(t *testing.T) {
}
func TestCondBreakpointError(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 9)
assertNoError(err, t, "LineToPC")
@ -1607,6 +1689,7 @@ func TestCondBreakpointError(t *testing.T) {
func TestIssue356(t *testing.T) {
// slice with a typedef does not get printed correctly
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
mmvar, err := evalVariable(p, "mainMenu")
@ -1642,6 +1725,7 @@ func TestStepIntoFunction(t *testing.T) {
func TestIssue384(t *testing.T) {
// Crash related to reading uninitialized memory, introduced by the memory prefetching optimization
protest.AllowRecording(t)
withTestProcess("issue384", t, func(p proc.Process, fixture protest.Fixture) {
start, _, err := p.BinInfo().LineToPC(fixture.Source, 13)
assertNoError(err, t, "LineToPC()")
@ -1655,6 +1739,7 @@ func TestIssue384(t *testing.T) {
func TestIssue332_Part1(t *testing.T) {
// Next shouldn't step inside a function call
protest.AllowRecording(t)
withTestProcess("issue332", t, func(p proc.Process, fixture protest.Fixture) {
start, _, err := p.BinInfo().LineToPC(fixture.Source, 8)
assertNoError(err, t, "LineToPC()")
@ -1681,6 +1766,7 @@ func TestIssue332_Part2(t *testing.T) {
// In some parts of the prologue, for some functions, the FDE data is incorrect
// which leads to 'next' and 'stack' failing with error "could not find FDE for PC: <garbage>"
// because the incorrect FDE data leads to reading the wrong stack address as the return address
protest.AllowRecording(t)
withTestProcess("issue332", t, func(p proc.Process, fixture protest.Fixture) {
start, _, err := p.BinInfo().LineToPC(fixture.Source, 8)
assertNoError(err, t, "LineToPC()")
@ -1733,6 +1819,7 @@ func TestIssue396(t *testing.T) {
func TestIssue414(t *testing.T) {
// Stepping until the program exits
protest.AllowRecording(t)
withTestProcess("math", t, func(p proc.Process, fixture protest.Fixture) {
start, _, err := p.BinInfo().LineToPC(fixture.Source, 9)
assertNoError(err, t, "LineToPC()")
@ -1752,6 +1839,7 @@ func TestIssue414(t *testing.T) {
}
func TestPackageVariables(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
assertNoError(err, t, "Continue()")
@ -1785,6 +1873,7 @@ func TestIssue149(t *testing.T) {
}
func TestPanicBreakpoint(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("panic", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
bp, _, _ := p.CurrentThread().Breakpoint()
@ -1864,6 +1953,7 @@ func TestIssue462(t *testing.T) {
}
func TestNextParked(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.sayhi")
assertNoError(err, t, "SetBreakpoint()")
@ -1901,6 +1991,7 @@ func TestNextParked(t *testing.T) {
}
func TestStepParked(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.sayhi")
assertNoError(err, t, "SetBreakpoint()")
@ -2004,6 +2095,7 @@ func TestUnsupportedArch(t *testing.T) {
func TestIssue573(t *testing.T) {
// calls to runtime.duffzero and runtime.duffcopy jump directly into the middle
// of the function and the internal breakpoint set by StepInto may be missed.
protest.AllowRecording(t)
withTestProcess("issue573", t, func(p proc.Process, fixture protest.Fixture) {
fentry, _ := proc.FindFunctionLocation(p, "main.foo", false, 0)
_, err := p.SetBreakpoint(fentry, proc.UserBreakpoint, nil)
@ -2126,6 +2218,7 @@ func TestStepIgnorePrivateRuntime(t *testing.T) {
func TestIssue561(t *testing.T) {
// Step fails to make progress when PC is at a CALL instruction
// where a breakpoint is also set.
protest.AllowRecording(t)
withTestProcess("issue561", t, func(p proc.Process, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture, 10)
assertNoError(proc.Continue(p), t, "Continue()")
@ -2138,6 +2231,7 @@ func TestIssue561(t *testing.T) {
}
func TestStepOut(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testnextprog", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.helloworld")
assertNoError(err, t, "SetBreakpoint()")
@ -2159,6 +2253,7 @@ func TestStepOut(t *testing.T) {
}
func TestStepConcurrentDirect(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) {
pc, err := proc.FindFileLocation(p, fixture.Source, 37)
assertNoError(err, t, "FindFileLocation()")
@ -2233,6 +2328,7 @@ func nextInProgress(p proc.Process) bool {
}
func TestStepConcurrentPtr(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) {
pc, err := proc.FindFileLocation(p, fixture.Source, 24)
assertNoError(err, t, "FindFileLocation()")
@ -2312,6 +2408,7 @@ func TestStepConcurrentPtr(t *testing.T) {
}
func TestStepOutDefer(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testnextdefer", t, func(p proc.Process, fixture protest.Fixture) {
pc, err := proc.FindFileLocation(p, fixture.Source, 9)
assertNoError(err, t, "FindFileLocation()")
@ -2338,6 +2435,7 @@ func TestStepOutDeferReturnAndDirectCall(t *testing.T) {
// StepOut should not step into a deferred function if it is called
// directly, only if it is called through a panic.
// Here we test the case where the function is called by a deferreturn
protest.AllowRecording(t)
withTestProcess("defercall", t, func(p proc.Process, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture, 11)
assertNoError(proc.Continue(p), t, "Continue()")
@ -2355,6 +2453,7 @@ func TestStepOutDeferReturnAndDirectCall(t *testing.T) {
const maxInstructionLength uint64 = 15
func TestStepOnCallPtrInstr(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("teststepprog", t, func(p proc.Process, fixture protest.Fixture) {
pc, err := proc.FindFileLocation(p, fixture.Source, 10)
assertNoError(err, t, "FindFileLocation()")
@ -2408,9 +2507,18 @@ func TestIssue594(t *testing.T) {
// back to the target.
// In particular the target should be able to cause a nil pointer
// dereference panic and recover from it.
protest.AllowRecording(t)
withTestProcess("issue594", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
f, ln := currentLineNumber(p, t)
var f string
var ln int
if testBackend == "rr" {
frame, err := findFirstNonRuntimeFrame(p)
assertNoError(err, t, "findFirstNonRuntimeFrame")
f, ln = frame.Current.File, frame.Current.Line
} else {
f, ln = currentLineNumber(p, t)
}
if ln != 21 {
t.Fatalf("Program stopped at %s:%d, expected :21", f, ln)
}
@ -2421,6 +2529,7 @@ func TestStepOutPanicAndDirectCall(t *testing.T) {
// StepOut should not step into a deferred function if it is called
// directly, only if it is called through a panic.
// Here we test the case where the function is called by a panic
protest.AllowRecording(t)
withTestProcess("defercall", t, func(p proc.Process, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture, 17)
assertNoError(proc.Continue(p), t, "Continue()")
@ -2441,6 +2550,7 @@ func TestWorkDir(t *testing.T) {
if runtime.GOOS == "darwin" {
wd = "/private/tmp"
}
protest.AllowRecording(t)
withTestProcessArgs("workdir", t, wd, func(p proc.Process, fixture protest.Fixture) {
addr, _, err := p.BinInfo().LineToPC(fixture.Source, 14)
assertNoError(err, t, "LineToPC")
@ -2465,6 +2575,7 @@ func TestNegativeIntEvaluation(t *testing.T) {
{"ni16", "int16", int64(-5)},
{"ni32", "int32", int64(-5)},
}
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
for _, tc := range testcases {
@ -2482,6 +2593,7 @@ func TestNegativeIntEvaluation(t *testing.T) {
func TestIssue683(t *testing.T) {
// Step panics when source file can not be found
protest.AllowRecording(t)
withTestProcess("issue683", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.main")
assertNoError(err, t, "setFunctionBreakpoint()")
@ -2498,6 +2610,7 @@ func TestIssue683(t *testing.T) {
}
func TestIssue664(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("issue664", t, func(p proc.Process, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture, 4)
assertNoError(proc.Continue(p), t, "Continue()")
@ -2511,6 +2624,7 @@ func TestIssue664(t *testing.T) {
// Benchmarks (*Processs).Continue + (*Scope).FunctionArguments
func BenchmarkTrace(b *testing.B) {
protest.AllowRecording(b)
withTestProcess("traceperf", b, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.PerfCheck")
assertNoError(err, b, "setFunctionBreakpoint()")
@ -2531,6 +2645,7 @@ func TestNextInDeferReturn(t *testing.T) {
// instruction leaves the curg._defer field non-nil but with curg._defer.fn
// field being nil.
// We need to deal with this without panicing.
protest.AllowRecording(t)
withTestProcess("defercall", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "runtime.deferreturn")
assertNoError(err, t, "setFunctionBreakpoint()")
@ -2658,6 +2773,9 @@ func TestAttachDetach(t *testing.T) {
return
}
}
if testBackend == "rr" {
return
}
fixture := protest.BuildFixture("testnextnethttp")
cmd := exec.Command(fixture.Path)
cmd.Stdout = os.Stdout
@ -2721,6 +2839,7 @@ func TestAttachDetach(t *testing.T) {
}
func TestVarSum(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
sumvar, err := evalVariable(p, "s1[0] + s1[1]")
@ -2736,6 +2855,7 @@ func TestVarSum(t *testing.T) {
}
func TestPackageWithPathVar(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("pkgrenames", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
_, err := evalVariable(p, "pkg.SomeVar")
@ -2746,6 +2866,7 @@ func TestPackageWithPathVar(t *testing.T) {
}
func TestEnvironment(t *testing.T) {
protest.AllowRecording(t)
os.Setenv("SOMEVAR", "bah")
withTestProcess("testenv", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")

@ -17,6 +17,9 @@ func TestIssue419(t *testing.T) {
// debugserver bug?
return
}
if testBackend == "rr" {
return
}
// SIGINT directed at the inferior should be passed along not swallowed by delve
withTestProcess("issue419", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.main")

@ -8,6 +8,8 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
)
@ -92,3 +94,92 @@ func RunTestsWithFixtures(m *testing.M) int {
}
return status
}
var recordingAllowed = map[string]bool{}
var recordingAllowedMu sync.Mutex
// testName returns the name of the test being run using runtime.Caller.
// On go1.8 t.Name() could be called instead, this is a workaround to
// support <=go1.7
func testName(t testing.TB) string {
for i := 1; i < 10; i++ {
pc, _, _, ok := runtime.Caller(i)
if !ok {
break
}
fn := runtime.FuncForPC(pc)
if fn == nil {
continue
}
name := fn.Name()
v := strings.Split(name, ".")
if strings.HasPrefix(v[len(v)-1], "Test") {
return name
}
}
return "unknown"
}
// AllowRecording allows the calling test to be used with a recording of the
// fixture.
func AllowRecording(t testing.TB) {
recordingAllowedMu.Lock()
defer recordingAllowedMu.Unlock()
name := testName(t)
t.Logf("enabling recording for %s", name)
recordingAllowed[name] = true
}
// MustHaveRecordingAllowed skips this test if recording is not allowed
//
// Not all the tests can be run with a recording:
// - some fixtures never terminate independently (loopprog,
// testnextnethttp) and can not be recorded
// - some tests assume they can interact with the target process (for
// example TestIssue419, or anything changing the value of a variable),
// which we can't do on with a recording
// - some tests assume that the Pid returned by the process is valid, but
// it won't be at replay time
// - some tests will start the fixture but not never execute a single
// instruction, for some reason rr doesn't like this and will print an
// error if it happens
// - many tests will assume that we can return from a runtime.Breakpoint,
// with a recording this is not possible because when the fixture ran it
// wasn't attached to a debugger and in those circumstances a
// runtime.Breakpoint leads directly to a crash
//
// Some of the tests using runtime.Breakpoint (anything involving variable
// evaluation and TestWorkDir) have been adapted to work with a recording.
func MustHaveRecordingAllowed(t testing.TB) {
recordingAllowedMu.Lock()
defer recordingAllowedMu.Unlock()
name := testName(t)
if !recordingAllowed[name] {
t.Skipf("recording not allowed for %s", name)
}
}
// 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)
}

@ -218,6 +218,41 @@ Supported commands: print, stack and goroutine)`},
Specifies that the breakpoint or tracepoint should break only if the boolean expression is true.`},
}
if client == nil || client.Recorded() {
c.cmds = append(c.cmds, command{
aliases: []string{"rewind", "rw"},
cmdFn: rewind,
helpMsg: "Run backwards until breakpoint or program termination.",
})
c.cmds = append(c.cmds, command{
aliases: []string{"check", "checkpoint"},
cmdFn: checkpoint,
helpMsg: `Creates a checkpoint at the current position.
checkpoint [where]`,
})
c.cmds = append(c.cmds, command{
aliases: []string{"checkpoints"},
cmdFn: checkpoints,
helpMsg: "Print out info for existing checkpoints.",
})
c.cmds = append(c.cmds, command{
aliases: []string{"clear-checkpoint", "clearcheck"},
cmdFn: clearCheckpoint,
helpMsg: `Deletes checkpoint.
checkpoint <id>`,
})
for i := range c.cmds {
v := &c.cmds[i]
if v.match("restart") {
v.helpMsg = `Restart process from a checkpoint or event.
restart [event number or checkpoint id]`
}
}
}
sort.Sort(ByFirstAlias(c.cmds))
return c
}
@ -569,14 +604,24 @@ func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
}
func restart(t *Term, ctx callContext, args string) error {
discarded, err := t.client.Restart()
discarded, err := t.client.RestartFrom(args)
if err != nil {
return err
}
fmt.Println("Process restarted with PID", t.client.ProcessPid())
if !t.client.Recorded() {
fmt.Println("Process restarted with PID", t.client.ProcessPid())
}
for i := range discarded {
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
}
@ -1196,6 +1241,10 @@ func printcontext(t *Term, state *api.DebuggerState) error {
printcontextThread(t, state.CurrentThread)
if state.When != "" {
fmt.Println(state.When)
}
return nil
}
@ -1409,6 +1458,74 @@ func (c *Commands) executeFile(t *Term, name string) error {
return scanner.Err()
}
func rewind(t *Term, ctx callContext, args string) error {
stateChan := t.client.Rewind()
var state *api.DebuggerState
for state = range stateChan {
if state.Err != nil {
return state.Err
}
printcontext(t, state)
}
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
return nil
}
func checkpoint(t *Term, ctx callContext, args string) error {
if args == "" {
state, err := t.client.GetState()
if err != nil {
return err
}
var loc api.Location = api.Location{PC: state.CurrentThread.PC, File: state.CurrentThread.File, Line: state.CurrentThread.Line, Function: state.CurrentThread.Function}
if state.SelectedGoroutine != nil {
loc = state.SelectedGoroutine.CurrentLoc
}
fname := "???"
if loc.Function != nil {
fname = loc.Function.Name
}
args = fmt.Sprintf("%s() %s:%d (%#x)", fname, loc.File, loc.Line, loc.PC)
}
cpid, err := t.client.Checkpoint(args)
if err != nil {
return err
}
fmt.Printf("Checkpoint c%d created.\n", cpid)
return nil
}
func checkpoints(t *Term, ctx callContext, args string) error {
cps, err := t.client.ListCheckpoints()
if err != nil {
return err
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 4, 4, 2, ' ', 0)
fmt.Fprintln(w, "ID\tWhen\tWhere")
for _, cp := range cps {
fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where)
}
w.Flush()
return nil
}
func clearCheckpoint(t *Term, ctx callContext, args string) error {
if len(args) < 0 {
return errors.New("not enough arguments to clear-checkpoint")
}
if args[0] != 'c' {
return errors.New("clear-checkpoint argument must be a checkpoint ID")
}
id, err := strconv.Atoi(args[1:])
if err != nil {
return errors.New("clear-checkpoint argument must be a checkpoint ID")
}
return t.client.ClearCheckpoint(id)
}
func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
thing := "breakpoint"
if bp.Tracepoint {

@ -86,6 +86,9 @@ func (ft *FakeTerminal) AssertExecError(cmdstr, tgterr string) {
}
func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
if testBackend == "rr" {
test.MustHaveRecordingAllowed(t)
}
os.Setenv("TERM", "dumb")
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
@ -103,6 +106,9 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
client := rpc2.NewClient(listener.Addr().String())
defer func() {
client.Detach(true)
if dir, _ := client.TraceDirectory(); dir != "" {
test.SafeRemoveAll(dir)
}
}()
ft := &FakeTerminal{
@ -207,6 +213,7 @@ func TestIssue354(t *testing.T) {
}
func TestIssue411(t *testing.T) {
test.AllowRecording(t)
withTestTerminal("math", t, func(term *FakeTerminal) {
term.MustExec("break math.go:8")
term.MustExec("trace math.go:9")
@ -221,6 +228,7 @@ func TestIssue411(t *testing.T) {
func TestScopePrefix(t *testing.T) {
const goroutinesLinePrefix = " Goroutine "
const goroutinesCurLinePrefix = "* Goroutine "
test.AllowRecording(t)
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
term.MustExec("b stacktraceme")
term.MustExec("continue")
@ -344,6 +352,7 @@ func TestScopePrefix(t *testing.T) {
func TestOnPrefix(t *testing.T) {
const prefix = "\ti: "
test.AllowRecording(t)
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
term.MustExec("b agobp main.agoroutine")
term.MustExec("on agobp print i")
@ -384,6 +393,7 @@ func TestOnPrefix(t *testing.T) {
}
func TestNoVars(t *testing.T) {
test.AllowRecording(t)
withTestTerminal("locationsUpperCase", t, func(term *FakeTerminal) {
term.MustExec("b main.main")
term.MustExec("continue")
@ -395,6 +405,7 @@ func TestNoVars(t *testing.T) {
func TestOnPrefixLocals(t *testing.T) {
const prefix = "\ti: "
test.AllowRecording(t)
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
term.MustExec("b agobp main.agoroutine")
term.MustExec("on agobp args -v")
@ -449,6 +460,7 @@ func countOccourences(s string, needle string) int {
func TestIssue387(t *testing.T) {
// a breakpoint triggering during a 'next' operation will interrupt it
test.AllowRecording(t)
withTestTerminal("issue387", t, func(term *FakeTerminal) {
breakpointHitCount := 0
term.MustExec("break dostuff")
@ -498,13 +510,13 @@ func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end
outStart, outEnd := 0, 0
for i, line := range lines[1:] {
for _, line := range lines[1:] {
if line == "" {
continue
}
v := re.FindStringSubmatch(line)
if len(v) != 3 {
t.Fatalf("Could not parse line %d: %q\n", i+1, line)
continue
}
curline, _ := strconv.Atoi(v[2])
if v[1] == "=>" {
@ -518,8 +530,10 @@ func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end
outEnd = curline
}
if outStart != start || outEnd != end {
t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end)
if start != -1 || end != -1 {
if outStart != start || outEnd != end {
t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end)
}
}
}
@ -537,3 +551,33 @@ func TestListCmd(t *testing.T) {
}
})
}
func TestReverseContinue(t *testing.T) {
test.AllowRecording(t)
if testBackend != "rr" {
return
}
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
term.MustExec("break main.main")
term.MustExec("break main.sayhi")
listIsAt(t, term, "continue", 16, -1, -1)
listIsAt(t, term, "continue", 12, -1, -1)
listIsAt(t, term, "rewind", 16, -1, -1)
})
}
func TestCheckpoints(t *testing.T) {
test.AllowRecording(t)
if testBackend != "rr" {
return
}
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
term.MustExec("break main.main")
listIsAt(t, term, "continue", 16, -1, -1)
term.MustExec("checkpoint")
term.MustExec("checkpoints")
listIsAt(t, term, "next", 17, -1, -1)
listIsAt(t, term, "next", 18, -1, -1)
listIsAt(t, term, "restart c1", 16, -1, -1)
})
}

@ -274,3 +274,7 @@ func ConvertRegisters(in []proc.Register) (out []Register) {
}
return
}
func ConvertCheckpoint(in proc.Checkpoint) (out Checkpoint) {
return Checkpoint{ID: in.ID, When: in.When, Where: in.Where}
}

@ -29,6 +29,8 @@ type DebuggerState struct {
// Exited indicates whether the debugged process has exited.
Exited bool `json:"exited"`
ExitStatus int `json:"exitStatus"`
// When contains a description of the current position in a recording
When string
// Filled by RPCClient.Continue, indicates an error
Err error `json:"-"`
}
@ -236,6 +238,8 @@ type EvalScope struct {
const (
// Continue resumes process execution.
Continue = "continue"
// Rewind resumes process execution backwards (target must be a recording).
Rewind = "rewind"
// Step continues to next source line, entering function calls.
Step = "step"
// StepOut continues to the return address of the current function
@ -318,3 +322,9 @@ type DiscardedBreakpoint struct {
Breakpoint *Breakpoint
Reason string
}
type Checkpoint struct {
ID int
When string
Where string
}

@ -20,12 +20,16 @@ type Client interface {
// Restarts program.
Restart() ([]api.DiscardedBreakpoint, error)
// Restarts program from the specified position.
RestartFrom(pos string) ([]api.DiscardedBreakpoint, error)
// GetState returns the current debugger state.
GetState() (*api.DebuggerState, error)
// Continue resumes process execution.
Continue() <-chan *api.DebuggerState
// Rewind resumes process execution backwards.
Rewind() <-chan *api.DebuggerState
// Next continues to the next source line, not entering function calls.
Next() (*api.DebuggerState, error)
// Step continues to the next source line, entering function calls.
@ -112,4 +116,15 @@ type Client interface {
DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error)
// Disassemble code of the function containing PC
DisassemblePC(scope api.EvalScope, pc uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error)
// Recorded returns true if the target is a recording.
Recorded() bool
// TraceDirectory returns the path to the trace directory for a recording.
TraceDirectory() (string, error)
// Checkpoint sets a checkpoint at the current position.
Checkpoint(where string) (checkpointID int, err error)
// ListCheckpoints gets all checkpoints.
ListCheckpoints() ([]api.Checkpoint, error)
// ClearCheckpoint removes a checkpoint
ClearCheckpoint(id int) error
}

@ -78,8 +78,16 @@ func New(config *Config) (*Debugger, error) {
d.target = p
case d.config.CoreFile != "":
log.Printf("opening core file %s (executable %s)", d.config.CoreFile, d.config.ProcessArgs[0])
p, err := core.OpenCore(d.config.CoreFile, d.config.ProcessArgs[0])
var p proc.Process
var err error
switch d.config.Backend {
case "rr":
log.Printf("opening trace %s", d.config.CoreFile)
p, err = gdbserial.Replay(d.config.CoreFile, false)
default:
log.Printf("opening core file %s (executable %s)", d.config.CoreFile, d.config.ProcessArgs[0])
p, err = core.OpenCore(d.config.CoreFile, d.config.ProcessArgs[0])
}
if err != nil {
return nil, err
}
@ -105,6 +113,9 @@ func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error)
return native.Launch(processArgs, wd)
case "lldb":
return gdbserial.LLDBLaunch(processArgs, wd)
case "rr":
p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false)
return p, err
case "default":
if runtime.GOOS == "darwin" {
return gdbserial.LLDBLaunch(processArgs, wd)
@ -173,12 +184,19 @@ func (d *Debugger) detach(kill bool) error {
// Restart will restart the target process, first killing
// and then exec'ing it again.
func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
// 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
// event number.
func (d *Debugger) Restart(pos string) ([]api.DiscardedBreakpoint, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if d.config.CoreFile != "" {
return nil, errors.New("can not restart core dump")
if recorded, _ := d.target.Recorded(); recorded {
return nil, d.target.Restart(pos)
}
if pos != "" {
return nil, proc.NotRecordedErr
}
if !d.target.Exited() {
@ -203,6 +221,7 @@ func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
continue
}
if len(oldBp.File) > 0 {
var err error
oldBp.Addr, err = proc.FindFileLocation(p, oldBp.File, oldBp.Line)
if err != nil {
discarded = append(discarded, api.DiscardedBreakpoint{oldBp, err.Error()})
@ -262,6 +281,10 @@ func (d *Debugger) state() (*api.DebuggerState, error) {
}
}
if recorded, _ := d.target.Recorded(); recorded {
state.When, _ = d.target.When()
}
return state, nil
}
@ -498,6 +521,32 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
err = d.collectBreakpointInformation(state)
return state, err
case api.Rewind:
log.Print("rewinding")
if err := d.target.Direction(proc.Backward); err != nil {
return nil, err
}
defer func() {
d.target.Direction(proc.Forward)
}()
err = proc.Continue(d.target)
if err != nil {
if exitedErr, exited := err.(proc.ProcessExitedError); exited {
state := &api.DebuggerState{}
state.Exited = true
state.ExitStatus = exitedErr.Status
state.Err = errors.New(exitedErr.Error())
return state, nil
}
return nil, err
}
state, stateErr := d.state()
if stateErr != nil {
return state, stateErr
}
err = d.collectBreakpointInformation(state)
return state, err
case api.Next:
log.Print("nexting")
err = proc.Next(d.target)
@ -897,3 +946,36 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo
return disass, nil
}
// Recorded returns true if the target is a recording.
func (d *Debugger) Recorded() (recorded bool, tracedir string) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.target.Recorded()
}
func (d *Debugger) Checkpoint(where string) (int, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.target.Checkpoint(where)
}
func (d *Debugger) Checkpoints() ([]api.Checkpoint, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
cps, err := d.target.Checkpoints()
if err != nil {
return nil, err
}
r := make([]api.Checkpoint, len(cps))
for i := range cps {
r[i] = api.ConvertCheckpoint(cps[i])
}
return r, nil
}
func (d *Debugger) ClearCheckpoint(id int) error {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.target.ClearCheckpoint(id)
}

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

@ -51,7 +51,13 @@ func (c *RPCClient) Detach(kill bool) error {
func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut)
err := c.call("Restart", RestartIn{}, out)
err := c.call("Restart", RestartIn{""}, out)
return out.DiscardedBreakpoints, err
}
func (c *RPCClient) RestartFrom(pos string) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut)
err := c.call("Restart", RestartIn{pos}, out)
return out.DiscardedBreakpoints, err
}
@ -62,11 +68,19 @@ func (c *RPCClient) GetState() (*api.DebuggerState, error) {
}
func (c *RPCClient) Continue() <-chan *api.DebuggerState {
return c.continueDir(api.Continue)
}
func (c *RPCClient) Rewind() <-chan *api.DebuggerState {
return c.continueDir(api.Rewind)
}
func (c *RPCClient) continueDir(cmd string) <-chan *api.DebuggerState {
ch := make(chan *api.DebuggerState)
go func() {
for {
out := new(CommandOut)
err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, &out)
err := c.call("Command", &api.DebuggerCommand{Name: cmd}, &out)
state := out.State
if err != nil {
state.Err = err
@ -299,6 +313,41 @@ func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.As
return out.Disassemble, err
}
// Recorded returns true if the debugger target is a recording.
func (c *RPCClient) Recorded() bool {
out := new(RecordedOut)
c.call("Recorded", RecordedIn{}, out)
return out.Recorded
}
// TraceDirectory returns the path to the trace directory for a recording.
func (c *RPCClient) TraceDirectory() (string, error) {
var out RecordedOut
err := c.call("Recorded", RecordedIn{}, &out)
return out.TraceDirectory, err
}
// Checkpoint sets a checkpoint at the current position.
func (c *RPCClient) Checkpoint(where string) (checkpointID int, err error) {
var out CheckpointOut
err = c.call("Checkpoint", CheckpointIn{where}, &out)
return out.ID, err
}
// ListCheckpoints gets all checkpoints.
func (c *RPCClient) ListCheckpoints() ([]api.Checkpoint, error) {
var out ListCheckpointsOut
err := c.call("ListCheckpoints", ListCheckpointsIn{}, &out)
return out.Checkpoints, err
}
// ClearCheckpoint removes a checkpoint
func (c *RPCClient) ClearCheckpoint(id int) error {
var out ClearCheckpointOut
err := c.call("ClearCheckpoint", ClearCheckpointIn{id}, &out)
return err
}
func (c *RPCClient) url(path string) string {
return fmt.Sprintf("http://%s%s", c.addr, path)
}

@ -59,6 +59,9 @@ func (s *RPCServer) Detach(arg DetachIn, out *DetachOut) error {
}
type RestartIn struct {
// Position to restart from, if it starts with 'c' it's a checkpoint ID,
// otherwise it's an event number. Only valid for recorded targets.
Position string
}
type RestartOut struct {
@ -71,7 +74,7 @@ func (s *RPCServer) Restart(arg RestartIn, out *RestartOut) error {
return errors.New("cannot restart process Delve did not create")
}
var err error
out.DiscardedBreakpoints, err = s.debugger.Restart()
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Position)
return err
}
@ -573,3 +576,54 @@ func (c *RPCServer) Disassemble(arg DisassembleIn, out *DisassembleOut) error {
out.Disassemble, err = c.debugger.Disassemble(arg.Scope, arg.StartPC, arg.EndPC, arg.Flavour)
return err
}
type RecordedIn struct {
}
type RecordedOut struct {
Recorded bool
TraceDirectory string
}
func (s *RPCServer) Recorded(arg RecordedIn, out *RecordedOut) error {
out.Recorded, out.TraceDirectory = s.debugger.Recorded()
return nil
}
type CheckpointIn struct {
Where string
}
type CheckpointOut struct {
ID int
}
func (s *RPCServer) Checkpoint(arg CheckpointIn, out *CheckpointOut) error {
var err error
out.ID, err = s.debugger.Checkpoint(arg.Where)
return err
}
type ListCheckpointsIn struct {
}
type ListCheckpointsOut struct {
Checkpoints []api.Checkpoint
}
func (s *RPCServer) ListCheckpoints(arg ListCheckpointsIn, out *ListCheckpointsOut) error {
var err error
out.Checkpoints, err = s.debugger.Checkpoints()
return err
}
type ClearCheckpointIn struct {
ID int
}
type ClearCheckpointOut struct {
}
func (s *RPCServer) ClearCheckpoint(arg ClearCheckpointIn, out *ClearCheckpointOut) error {
return s.debugger.ClearCheckpoint(arg.ID)
}

@ -21,6 +21,9 @@ import (
)
func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
if testBackend == "rr" {
protest.MustHaveRecordingAllowed(t)
}
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("couldn't start listener: %s\n", err)
@ -43,6 +46,12 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
}
func Test1RunWithInvalidPath(t *testing.T) {
if testBackend == "rr" {
// This test won't work because rr returns an error, after recording, when
// the recording failed but also when the recording succeeded but the
// inferior returned an error. Therefore we have to ignore errors from rr.
return
}
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("couldn't start listener: %s\n", err)

@ -38,6 +38,9 @@ func TestMain(m *testing.M) {
}
func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
if testBackend == "rr" {
protest.MustHaveRecordingAllowed(t)
}
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("couldn't start listener: %s\n", err)
@ -54,12 +57,21 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
client := rpc2.NewClient(listener.Addr().String())
defer func() {
client.Detach(true)
if dir, _ := client.TraceDirectory(); dir != "" {
protest.SafeRemoveAll(dir)
}
}()
fn(client)
}
func TestRunWithInvalidPath(t *testing.T) {
if testBackend == "rr" {
// This test won't work because rr returns an error, after recording, when
// the recording failed but also when the recording succeeded but the
// inferior returned an error. Therefore we have to ignore errors from rr.
return
}
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("couldn't start listener: %s\n", err)
@ -97,6 +109,7 @@ func TestRestart_afterExit(t *testing.T) {
}
func TestRestart_breakpointPreservation(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("continuetestprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true})
assertNoError(err, t, "CreateBreakpoint()")
@ -167,6 +180,7 @@ func TestRestart_attachPid(t *testing.T) {
}
func TestClientServer_exit(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("continuetestprog", t, func(c service.Client) {
state, err := c.GetState()
if err != nil {
@ -190,6 +204,7 @@ func TestClientServer_exit(t *testing.T) {
}
func TestClientServer_step(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: -1})
if err != nil {
@ -213,6 +228,7 @@ func TestClientServer_step(t *testing.T) {
}
func TestClientServer_stepout(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testnextprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
@ -230,6 +246,7 @@ func TestClientServer_stepout(t *testing.T) {
}
func testnext2(testcases []nextTest, initialLocation string, t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testnextprog", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1})
if err != nil {
@ -321,6 +338,7 @@ func TestNextFunctionReturn(t *testing.T) {
}
func TestClientServer_breakpointInMainThread(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testprog", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1})
if err != nil {
@ -342,6 +360,7 @@ func TestClientServer_breakpointInMainThread(t *testing.T) {
}
func TestClientServer_breakpointInSeparateGoroutine(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testthreads", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1})
if err != nil {
@ -396,6 +415,7 @@ func TestClientServer_clearBreakpoint(t *testing.T) {
}
func TestClientServer_switchThread(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testnextprog", t, func(c service.Client) {
// With invalid thread id
_, err := c.SwitchThread(-1)
@ -439,6 +459,7 @@ func TestClientServer_switchThread(t *testing.T) {
}
func TestClientServer_infoLocals(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testnextprog", t, func(c service.Client) {
fp := testProgPath(t, "testnextprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
@ -460,6 +481,7 @@ func TestClientServer_infoLocals(t *testing.T) {
}
func TestClientServer_infoArgs(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testnextprog", t, func(c service.Client) {
fp := testProgPath(t, "testnextprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47})
@ -488,6 +510,7 @@ func TestClientServer_infoArgs(t *testing.T) {
}
func TestClientServer_traceContinue(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("integrationprog", t, func(c service.Client) {
fp := testProgPath(t, "integrationprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Variables: []string{"i"}})
@ -545,6 +568,7 @@ func TestClientServer_traceContinue(t *testing.T) {
}
func TestClientServer_traceContinue2(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("integrationprog", t, func(c service.Client) {
bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true})
if err != nil {
@ -750,6 +774,7 @@ func TestClientServer_SetVariable(t *testing.T) {
}
func TestClientServer_FullStacktrace(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("goroutinestackprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
@ -823,6 +848,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
func TestIssue355(t *testing.T) {
// After the target process has terminated should return an error but not crash
protest.AllowRecording(t)
withTestClient2("continuetestprog", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
@ -854,9 +880,13 @@ func TestIssue355(t *testing.T) {
_, err = c.Halt()
assertError(err, t, "Halt()")
_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1})
assertError(err, t, "CreateBreakpoint()")
if testBackend != "rr" {
assertError(err, t, "CreateBreakpoint()")
}
_, err = c.ClearBreakpoint(bp.ID)
assertError(err, t, "ClearBreakpoint()")
if testBackend != "rr" {
assertError(err, t, "ClearBreakpoint()")
}
_, err = c.ListThreads()
assertError(err, t, "ListThreads()")
_, err = c.GetThread(tid)
@ -986,6 +1016,7 @@ func TestDisasm(t *testing.T) {
func TestNegativeStackDepthBug(t *testing.T) {
// After the target process has terminated should return an error but not crash
protest.AllowRecording(t)
withTestClient2("continuetestprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
@ -998,6 +1029,7 @@ func TestNegativeStackDepthBug(t *testing.T) {
}
func TestClientServer_CondBreakpoint(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("parallel_next", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1})
assertNoError(err, t, "CreateBreakpoint()")
@ -1095,6 +1127,7 @@ func TestIssue419(t *testing.T) {
}
func TestTypesCommand(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testvariables2", t, func(c service.Client) {
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
@ -1121,6 +1154,7 @@ func TestTypesCommand(t *testing.T) {
}
func TestIssue406(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("issue406", t, func(c service.Client) {
locs, err := c.FindLocation(api.EvalScope{-1, 0}, "issue406.go:146")
assertNoError(err, t, "FindLocation()")
@ -1191,6 +1225,7 @@ func TestClientServer_FpRegisters(t *testing.T) {
{"XMM7", "0x40026666666666664002666666666666"},
{"XMM8", "0x4059999a404ccccd4059999a404ccccd"},
}
protest.AllowRecording(t)
withTestClient2("fputest/", t, func(c service.Client) {
<-c.Continue()
regs, err := c.ListRegisters(0, true)
@ -1216,11 +1251,15 @@ func TestClientServer_FpRegisters(t *testing.T) {
}
func TestClientServer_RestartBreakpointPosition(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("locationsprog2", t, func(c service.Client) {
bpBefore, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.afunction", Line: -1, Tracepoint: true, Name: "this"})
addrBefore := bpBefore.Addr
t.Logf("%x\n", bpBefore.Addr)
assertNoError(err, t, "CreateBreakpoint")
stateCh := c.Continue()
for range stateCh {
}
_, err = c.Halt()
assertNoError(err, t, "Halt")
_, err = c.Restart()
@ -1242,6 +1281,7 @@ func TestClientServer_SelectedGoroutineLoc(t *testing.T) {
// CurrentLocation of SelectedGoroutine should reflect what's happening on
// the thread running the goroutine, not the position the goroutine was in
// the last time it was parked.
protest.AllowRecording(t)
withTestClient2("testprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -11})
assertNoError(err, t, "CreateBreakpoint")
@ -1260,3 +1300,38 @@ func TestClientServer_SelectedGoroutineLoc(t *testing.T) {
}
})
}
func TestClientServer_ReverseContinue(t *testing.T) {
protest.AllowRecording(t)
if testBackend != "rr" {
t.Skip("backend is not rr")
}
withTestClient2("continuetestprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1})
assertNoError(err, t, "CreateBreakpoint(main.main)")
_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
assertNoError(err, t, "CreateBreakpoint(main.sayhi)")
state := <-c.Continue()
assertNoError(state.Err, t, "first continue")
mainPC := state.CurrentThread.PC
t.Logf("after first continue %#x", mainPC)
state = <-c.Continue()
assertNoError(state.Err, t, "second continue")
sayhiPC := state.CurrentThread.PC
t.Logf("after second continue %#x", sayhiPC)
if mainPC == sayhiPC {
t.Fatalf("expected different PC after second PC (%#x)", mainPC)
}
state = <-c.Rewind()
assertNoError(state.Err, t, "rewind")
if mainPC != state.CurrentThread.PC {
t.Fatalf("Expected rewind to go back to the first breakpoint: %#x", state.CurrentThread.PC)
}
})
}

@ -55,11 +55,34 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) {
}
}
func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
scope, err := proc.GoroutineScope(p.CurrentThread())
func findFirstNonRuntimeFrame(p proc.Process) (proc.Stackframe, error) {
frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10)
if err != nil {
return nil, err
return proc.Stackframe{}, err
}
for _, frame := range frames {
if frame.Current.Fn != nil && !strings.HasPrefix(frame.Current.Fn.Name, "runtime.") {
return frame, nil
}
}
return proc.Stackframe{}, fmt.Errorf("non-runtime frame not found")
}
func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
var scope *proc.EvalScope
var err error
if testBackend == "rr" {
var frame proc.Stackframe
frame, err = findFirstNonRuntimeFrame(p)
if err == nil {
scope = proc.FrameToScope(p, frame)
}
} else {
scope, err = proc.GoroutineScope(p.CurrentThread())
}
return scope.EvalVariable(symbol, cfg)
}
@ -83,11 +106,17 @@ func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture
fixture := protest.BuildFixture(name)
var p proc.Process
var err error
var tracedir string
switch testBackend {
case "native":
p, err = native.Launch([]string{fixture.Path}, ".")
case "lldb":
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".")
case "rr":
protest.MustHaveRecordingAllowed(t)
t.Log("recording")
p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true)
t.Logf("replaying %q", tracedir)
default:
t.Fatalf("unknown backend %q", testBackend)
}
@ -98,6 +127,9 @@ func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture
defer func() {
p.Halt()
p.Detach(true)
if tracedir != "" {
protest.SafeRemoveAll(tracedir)
}
}()
fn(p, fixture)
@ -148,6 +180,7 @@ func TestVariableEvaluation(t *testing.T) {
{"NonExistent", true, "", "", "", fmt.Errorf("could not find symbol value for NonExistent")},
}
protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error")
@ -166,7 +199,7 @@ func TestVariableEvaluation(t *testing.T) {
}
}
if tc.alternate != "" {
if tc.alternate != "" && testBackend != "rr" {
assertNoError(setVariable(p, tc.name, tc.alternate), t, "SetVariable()")
variable, err = evalVariable(p, tc.name, pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
@ -226,6 +259,7 @@ func TestVariableEvaluationShort(t *testing.T) {
{"NonExistent", true, "", "", "", fmt.Errorf("could not find symbol value for NonExistent")},
}
protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error")
@ -281,6 +315,7 @@ func TestMultilineVariableEvaluation(t *testing.T) {
Nest: *(*main.Nest)(`, "", "main.Nest", nil},
}
protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error")
@ -354,13 +389,26 @@ func TestLocalVariables(t *testing.T) {
{"baz", true, "\"bazburzum\"", "", "string", nil}}},
}
protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) {
err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases {
scope, err := proc.GoroutineScope(p.CurrentThread())
assertNoError(err, t, "AsScope()")
var scope *proc.EvalScope
var err error
if testBackend == "rr" {
var frame proc.Stackframe
frame, err = findFirstNonRuntimeFrame(p)
if err == nil {
scope = proc.FrameToScope(p, frame)
}
} else {
scope, err = proc.GoroutineScope(p.CurrentThread())
}
assertNoError(err, t, "scope")
vars, err := tc.fn(scope, pnormalLoadConfig)
assertNoError(err, t, "LocalVariables() returned an error")
@ -378,6 +426,7 @@ func TestLocalVariables(t *testing.T) {
}
func TestEmbeddedStruct(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
testcases := []varTest{
{"b.val", true, "-314", "-314", "int", nil},
@ -654,6 +703,7 @@ func TestEvalExpression(t *testing.T) {
}
}
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
for _, tc := range testcases {
@ -678,6 +728,7 @@ func TestEvalExpression(t *testing.T) {
}
func TestEvalAddrAndCast(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig)
@ -704,6 +755,7 @@ func TestEvalAddrAndCast(t *testing.T) {
}
func TestMapEvaluation(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
m1v, err := evalVariable(p, "m1", pnormalLoadConfig)
@ -738,6 +790,7 @@ func TestMapEvaluation(t *testing.T) {
}
func TestUnsafePointer(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
up1v, err := evalVariable(p, "up1", pnormalLoadConfig)
@ -775,6 +828,7 @@ func TestIssue426(t *testing.T) {
// Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces
// differs from the serialization used by the linker to produce DWARF type information
protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
for _, testcase := range testcases {
@ -826,6 +880,7 @@ func TestPackageRenames(t *testing.T) {
return
}
protest.AllowRecording(t)
withTestProcess("pkgrenames", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error")
for _, tc := range testcases {