proc,terminal: Implement reverse step, next and stepout (#1785)
* proc: move defer breakpoint code into a function Moves the code that sets a breakpoint on the first deferred function, used by both next and StepOut, to its function. * proc: implement reverse step/next/stepout When the direction of execution is reversed (on a recording) Step, Next and StepOut will behave similarly to their forward version. However there are some subtle interactions between their behavior, prologue skipping, deferred calls and normal calls. Specifically: - when stepping backwards we need to set a breakpoint on the first instruction after each CALL instruction, once this breakpoint is reached we need to execute a single StepInstruction operation to reverse step into the CALL. - to insure that the prologue is skipped reverse next needs to check if it is on the first instruction after the prologue, and if it is behave like reverse stepout. - there is no reason to set breakpoints on deferred calls when reverse nexting or reverse stepping out, they will never be hit. - reverse step out should generally place its breakpoint on the CALL instruction that created the current stack frame (which will be the CALL instruction immediately preceding the instruction at the return address). - reverse step out needs to treat panic calls and deferreturn calls specially. * service,terminal: implement reverse step, next, stepout
This commit is contained in:
parent
e90a5b48ca
commit
1a9e38aa0c
@ -3,3 +3,4 @@
|
||||
- Delve does not currently support 32bit systems. This will usually manifest as a compiler error in `proc/disasm.go`. See [Issue #20](https://github.com/go-delve/delve/issues/20).
|
||||
- When Delve is compiled with versions of go prior to 1.7.0 it is not possible to set a breakpoint on a function in a remote package using the `Receiver.MethodName` syntax. See [Issue #528](https://github.com/go-delve/delve/issues/528).
|
||||
- When running Delve on binaries compiled with a version of go prior to 1.9.0 `locals` will print all local variables, including ones that are out of scope, the shadowed flag will be applied arbitrarily. If there are multiple variables defined with the same name in the current function `print` will not be able to select the correct one for the current line.
|
||||
- `reverse step` will not reverse step into functions called by deferred calls.
|
||||
|
@ -14,6 +14,7 @@ Command | Description
|
||||
[continue](#continue) | Run until breakpoint or program termination.
|
||||
[next](#next) | Step over to next source line.
|
||||
[restart](#restart) | Restart process from a checkpoint or event.
|
||||
[rev](#rev) | Reverses the execution of the target program for the command specified.
|
||||
[rewind](#rewind) | Run backwards until breakpoint or program termination.
|
||||
[step](#step) | Single step through program.
|
||||
[step-instruction](#step-instruction) | Single step a single cpu instruction.
|
||||
@ -83,7 +84,6 @@ Command | Description
|
||||
[help](#help) | Prints the help message.
|
||||
[libraries](#libraries) | List loaded dynamic libraries
|
||||
[list](#list) | Show source code.
|
||||
[rev](#rev) | Reverses the execution of the target program for the command specified.
|
||||
[source](#source) | Executes a file containing a list of delve commands
|
||||
[sources](#sources) | Print list of source files.
|
||||
[types](#types) | Print list of types
|
||||
|
@ -445,7 +445,7 @@ func TestTypecheckRPC(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fndecl.Name.Name == "Continue" || fndecl.Name.Name == "Rewind" {
|
||||
if fndecl.Name.Name == "Continue" || fndecl.Name.Name == "Rewind" || fndecl.Name.Name == "DirectionCongruentContinue" {
|
||||
// using continueDir
|
||||
continue
|
||||
}
|
||||
|
@ -124,19 +124,13 @@ func (bp *Breakpoint) CheckCondition(thread Thread) BreakpointState {
|
||||
}
|
||||
nextDeferOk := true
|
||||
if bp.Kind&NextDeferBreakpoint != 0 {
|
||||
var err error
|
||||
frames, err := ThreadStacktrace(thread, 2)
|
||||
if err == nil {
|
||||
ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
|
||||
isdeferreturn := false
|
||||
if len(frames) >= 1 {
|
||||
for _, pc := range bp.DeferReturns {
|
||||
if frames[0].Ret == pc {
|
||||
isdeferreturn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
nextDeferOk = isPanicCall(frames)
|
||||
if !nextDeferOk {
|
||||
nextDeferOk, _ = isDeferReturnCall(frames, bp.DeferReturns)
|
||||
}
|
||||
nextDeferOk = ispanic || isdeferreturn
|
||||
}
|
||||
}
|
||||
if bp.IsInternal() {
|
||||
@ -155,6 +149,21 @@ func (bp *Breakpoint) CheckCondition(thread Thread) BreakpointState {
|
||||
return bpstate
|
||||
}
|
||||
|
||||
func isPanicCall(frames []Stackframe) bool {
|
||||
return len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
|
||||
}
|
||||
|
||||
func isDeferReturnCall(frames []Stackframe, deferReturns []uint64) (bool, uint64) {
|
||||
if len(frames) >= 1 {
|
||||
for _, pc := range deferReturns {
|
||||
if frames[0].Ret == pc {
|
||||
return true, pc
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// IsInternal returns true if bp is an internal breakpoint.
|
||||
// User-set breakpoints can overlap with internal breakpoints, in that case
|
||||
// both IsUser and IsInternal will be true.
|
||||
|
@ -243,8 +243,11 @@ func (p *Process) Recorded() (bool, string) { return true, "" }
|
||||
// Restart will only return an error for core files, as they are not executing.
|
||||
func (p *Process) Restart(string) error { return ErrContinueCore }
|
||||
|
||||
// Direction will only return an error as you cannot continue a core process.
|
||||
func (p *Process) Direction(proc.Direction) error { return ErrContinueCore }
|
||||
// ChangeDirection will only return an error as you cannot continue a core process.
|
||||
func (p *Process) ChangeDirection(proc.Direction) error { return ErrContinueCore }
|
||||
|
||||
// GetDirection will always return forward.
|
||||
func (p *Process) GetDirection() proc.Direction { return proc.Forward }
|
||||
|
||||
// When does not apply to core files, it is to support the Mozilla 'rr' backend.
|
||||
func (p *Process) When() (string, error) { return "", nil }
|
||||
|
@ -658,6 +658,7 @@ func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
|
||||
var threadID string
|
||||
var trapthread *Thread
|
||||
var tu = threadUpdater{p: p}
|
||||
var atstart bool
|
||||
continueLoop:
|
||||
for {
|
||||
var err error
|
||||
@ -683,7 +684,7 @@ continueLoop:
|
||||
}
|
||||
|
||||
var shouldStop bool
|
||||
trapthread, shouldStop = p.handleThreadSignals(trapthread)
|
||||
trapthread, atstart, shouldStop = p.handleThreadSignals(trapthread)
|
||||
if shouldStop {
|
||||
break continueLoop
|
||||
}
|
||||
@ -691,18 +692,23 @@ continueLoop:
|
||||
|
||||
p.clearThreadRegisters()
|
||||
|
||||
stopReason := proc.StopUnknown
|
||||
if atstart {
|
||||
stopReason = proc.StopLaunched
|
||||
}
|
||||
|
||||
if p.BinInfo().GOOS == "linux" {
|
||||
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
|
||||
return nil, proc.StopUnknown, err
|
||||
return nil, stopReason, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.setCurrentBreakpoints(); err != nil {
|
||||
return nil, proc.StopUnknown, err
|
||||
return nil, stopReason, err
|
||||
}
|
||||
|
||||
if trapthread == nil {
|
||||
return nil, proc.StopUnknown, fmt.Errorf("could not find thread %s", threadID)
|
||||
return nil, stopReason, fmt.Errorf("could not find thread %s", threadID)
|
||||
}
|
||||
|
||||
var err error
|
||||
@ -724,7 +730,7 @@ continueLoop:
|
||||
// the signals that are reported here can not be propagated back to the target process.
|
||||
trapthread.sig = 0
|
||||
}
|
||||
return trapthread, proc.StopUnknown, err
|
||||
return trapthread, stopReason, err
|
||||
}
|
||||
|
||||
func (p *Process) findThreadByStrID(threadID string) *Thread {
|
||||
@ -741,7 +747,7 @@ func (p *Process) findThreadByStrID(threadID string) *Thread {
|
||||
// and returns true if we should stop execution in response to one of the
|
||||
// signals and return control to the user.
|
||||
// Adjusts trapthread to a thread that we actually want to stop at.
|
||||
func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread, shouldStop bool) {
|
||||
func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread, atstart bool, shouldStop bool) {
|
||||
var trapthreadCandidate *Thread
|
||||
|
||||
for _, th := range p.threads {
|
||||
@ -777,8 +783,9 @@ func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread
|
||||
// Signal 0 is returned by rr when it reaches the start of the process
|
||||
// in backward continue mode.
|
||||
case 0:
|
||||
if p.conn.direction == proc.Backward {
|
||||
if p.conn.direction == proc.Backward && th == trapthread {
|
||||
isStopSignal = true
|
||||
atstart = true
|
||||
}
|
||||
|
||||
default:
|
||||
@ -809,7 +816,7 @@ func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread
|
||||
shouldStop = true
|
||||
}
|
||||
|
||||
return trapthread, shouldStop
|
||||
return trapthread, atstart, shouldStop
|
||||
}
|
||||
|
||||
// RequestManualStop will attempt to stop the process
|
||||
@ -1014,10 +1021,13 @@ func (p *Process) ClearCheckpoint(id int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Direction sets whether to run the program forwards or in reverse execution.
|
||||
func (p *Process) Direction(dir proc.Direction) error {
|
||||
// ChangeDirection sets whether to run the program forwards or in reverse execution.
|
||||
func (p *Process) ChangeDirection(dir proc.Direction) error {
|
||||
if p.tracedir == "" {
|
||||
return proc.ErrNotRecorded
|
||||
if dir != proc.Forward {
|
||||
return proc.ErrNotRecorded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if p.conn.conn == nil {
|
||||
return proc.ErrProcessExited{Pid: p.conn.pid}
|
||||
@ -1032,6 +1042,11 @@ func (p *Process) Direction(dir proc.Direction) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDirection returns the current direction of execution.
|
||||
func (p *Process) GetDirection() proc.Direction {
|
||||
return p.conn.direction
|
||||
}
|
||||
|
||||
// Breakpoints returns the list of breakpoints currently set.
|
||||
func (p *Process) Breakpoints() *proc.BreakpointMap {
|
||||
return &p.breakpoints
|
||||
|
@ -146,7 +146,7 @@ func TestReverseBreakpointCounts(t *testing.T) {
|
||||
}
|
||||
|
||||
p.ClearBreakpoint(endbp.Addr)
|
||||
assertNoError(p.Direction(proc.Backward), t, "Switching to backward direction")
|
||||
assertNoError(p.ChangeDirection(proc.Backward), t, "Switching to backward direction")
|
||||
bp := setFileBreakpoint(p, t, fixture, 12)
|
||||
startbp := setFileBreakpoint(p, t, fixture, 20)
|
||||
|
||||
@ -285,7 +285,7 @@ func TestIssue1376(t *testing.T) {
|
||||
assertNoError(proc.Continue(p), t, "Continue (forward)")
|
||||
_, err := p.ClearBreakpoint(bp.Addr)
|
||||
assertNoError(err, t, "ClearBreakpoint")
|
||||
assertNoError(p.Direction(proc.Backward), t, "Switching to backward direction")
|
||||
assertNoError(p.ChangeDirection(proc.Backward), t, "Switching to backward direction")
|
||||
assertNoError(proc.Continue(p), t, "Continue (backward)")
|
||||
})
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ type RecordingManipulation interface {
|
||||
// to the trace directory.
|
||||
Recorded() (recorded bool, tracedir string)
|
||||
// Direction changes execution direction.
|
||||
Direction(Direction) error
|
||||
ChangeDirection(Direction) error
|
||||
// GetDirection returns the current direction of execution.
|
||||
GetDirection() Direction
|
||||
// When returns current recording position.
|
||||
When() (string, error)
|
||||
// Checkpoint sets a checkpoint at the current position.
|
||||
|
@ -70,9 +70,17 @@ func (dbp *Process) Recorded() (bool, string) { return false, "" }
|
||||
// recorded traces.
|
||||
func (dbp *Process) Restart(string) error { return proc.ErrNotRecorded }
|
||||
|
||||
// Direction will always return an error in the native proc backend, only for
|
||||
// ChangeDirection will always return an error in the native proc backend, only for
|
||||
// recorded traces.
|
||||
func (dbp *Process) Direction(proc.Direction) error { return proc.ErrNotRecorded }
|
||||
func (dbp *Process) ChangeDirection(dir proc.Direction) error {
|
||||
if dir != proc.Forward {
|
||||
return proc.ErrNotRecorded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDirection will always return Forward.
|
||||
func (p *Process) GetDirection() proc.Direction { return proc.Forward }
|
||||
|
||||
// When will always return an empty string and nil, not supported on native proc backend.
|
||||
func (dbp *Process) When() (string, error) { return "", nil }
|
||||
|
194
pkg/proc/proc.go
194
pkg/proc/proc.go
@ -8,7 +8,6 @@ import (
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -164,6 +163,9 @@ func Continue(dbp *Target) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dbp.StopReason == StopLaunched {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
}
|
||||
|
||||
threads := dbp.ThreadList()
|
||||
|
||||
@ -229,20 +231,19 @@ func Continue(dbp *Target) error {
|
||||
if err := conditionErrors(threads); err != nil {
|
||||
return err
|
||||
}
|
||||
regs, err := curthread.Registers(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pc := regs.PC()
|
||||
text, err := disassemble(curthread, regs, dbp.Breakpoints(), dbp.BinInfo(), pc, pc+uint64(dbp.BinInfo().Arch.MaxInstructionLength()), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// here we either set a breakpoint into the destination of the CALL
|
||||
// instruction or we determined that the called function is hidden,
|
||||
// either way we need to resume execution
|
||||
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
||||
return err
|
||||
if dbp.GetDirection() == Forward {
|
||||
text, err := disassembleCurrentInstruction(dbp, curthread)
|
||||
// here we either set a breakpoint into the destination of the CALL
|
||||
// instruction or we determined that the called function is hidden,
|
||||
// either way we need to resume execution
|
||||
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := dbp.ClearInternalBreakpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
return StepInstruction(dbp)
|
||||
}
|
||||
default:
|
||||
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
|
||||
@ -315,6 +316,15 @@ func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
|
||||
return dbp.SwitchThread(trapthread.ThreadID())
|
||||
}
|
||||
|
||||
func disassembleCurrentInstruction(p Process, thread Thread) ([]AsmInstruction, error) {
|
||||
regs, err := thread.Registers(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc := regs.PC()
|
||||
return disassemble(thread, regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()), true)
|
||||
}
|
||||
|
||||
// stepInstructionOut repeatedly calls StepInstruction until the current
|
||||
// function is neither fnname1 or fnname2.
|
||||
// This function is used to step out of runtime.Breakpoint as well as
|
||||
@ -355,6 +365,11 @@ func Step(dbp *Target) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if bp := dbp.CurrentThread().Breakpoint().Breakpoint; bp != nil && bp.Kind == StepBreakpoint && dbp.GetDirection() == Backward {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return StepInstruction(dbp)
|
||||
}
|
||||
|
||||
return Continue(dbp)
|
||||
}
|
||||
|
||||
@ -402,6 +417,7 @@ func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
|
||||
// StepOut will continue until the current goroutine exits the
|
||||
// function currently being executed or a deferred function is executed
|
||||
func StepOut(dbp *Target) error {
|
||||
backward := dbp.GetDirection() == Backward
|
||||
if _, err := dbp.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -436,29 +452,20 @@ func StepOut(dbp *Target) error {
|
||||
sameGCond := SameGoroutineCondition(selg)
|
||||
retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
|
||||
|
||||
var deferpc uint64
|
||||
if filepath.Ext(topframe.Current.File) == ".go" {
|
||||
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
|
||||
deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
|
||||
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if backward {
|
||||
if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
success = true
|
||||
return Continue(dbp)
|
||||
}
|
||||
|
||||
if deferpc != 0 && deferpc != topframe.Current.PC {
|
||||
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
|
||||
var deferpc uint64
|
||||
if !backward {
|
||||
deferpc, err = setDeferBreakpoint(dbp, nil, topframe, sameGCond, false)
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if bp != nil {
|
||||
// For StepOut we do not want to step into the deferred function
|
||||
// when it's called by runtime.deferreturn so we do not populate
|
||||
// DeferReturns.
|
||||
bp.DeferReturns = []uint64{}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,11 +474,9 @@ func StepOut(dbp *Target) error {
|
||||
}
|
||||
|
||||
if topframe.Ret != 0 {
|
||||
bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
|
||||
bp, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond))
|
||||
if err != nil {
|
||||
if _, isexists := err.(BreakpointExistsError); !isexists {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
if bp != nil {
|
||||
configureReturnBreakpoint(dbp.BinInfo(), bp, &topframe, retFrameCond)
|
||||
@ -523,6 +528,119 @@ func StepInstruction(dbp *Target) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func allowDuplicateBreakpoint(bp *Breakpoint, err error) (*Breakpoint, error) {
|
||||
if err != nil {
|
||||
if _, isexists := err.(BreakpointExistsError); isexists {
|
||||
return bp, nil
|
||||
}
|
||||
}
|
||||
return bp, err
|
||||
}
|
||||
|
||||
// setDeferBreakpoint is a helper function used by next and StepOut to set a
|
||||
// breakpoint on the first deferred function.
|
||||
func setDeferBreakpoint(p Process, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr, stepInto bool) (uint64, error) {
|
||||
// Set breakpoint on the most recently deferred function (if any)
|
||||
var deferpc uint64
|
||||
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
|
||||
deferfn := p.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
|
||||
var err error
|
||||
deferpc, err = FirstPCAfterPrologue(p, deferfn, false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if deferpc != 0 && deferpc != topframe.Current.PC {
|
||||
bp, err := allowDuplicateBreakpoint(p.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if bp != nil && stepInto {
|
||||
// If DeferReturns is set then the breakpoint will also be triggered when
|
||||
// called from runtime.deferreturn. We only do this for the step command,
|
||||
// not for next or stepout.
|
||||
bp.DeferReturns = FindDeferReturnCalls(text)
|
||||
}
|
||||
}
|
||||
|
||||
return deferpc, nil
|
||||
}
|
||||
|
||||
// findCallInstrForRet returns the PC address of the CALL instruction
|
||||
// immediately preceding the instruction at ret.
|
||||
func findCallInstrForRet(p Process, mem MemoryReadWriter, ret uint64, fn *Function) (uint64, error) {
|
||||
text, err := disassemble(mem, nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End, false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var prevInstr AsmInstruction
|
||||
for _, instr := range text {
|
||||
if instr.Loc.PC == ret {
|
||||
return prevInstr.Loc.PC, nil
|
||||
}
|
||||
prevInstr = instr
|
||||
}
|
||||
return 0, fmt.Errorf("could not find CALL instruction for address %#x in %s", ret, fn.Name)
|
||||
}
|
||||
|
||||
// stepOutReverse sets a breakpoint on the CALL instruction that created the current frame, this is either:
|
||||
// - the CALL instruction immediately preceding the return address of the
|
||||
// current frame
|
||||
// - the return address of the current frame if the current frame was
|
||||
// created by a runtime.deferreturn run
|
||||
// - the return address of the runtime.gopanic frame if the current frame
|
||||
// was created by a panic
|
||||
// This function is used to implement reversed StepOut
|
||||
func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr) error {
|
||||
curthread := p.CurrentThread()
|
||||
selg := p.SelectedGoroutine()
|
||||
|
||||
if selg != nil && selg.Thread != nil {
|
||||
curthread = selg.Thread
|
||||
}
|
||||
|
||||
callerText, err := disassemble(curthread, nil, p.Breakpoints(), p.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deferReturns := FindDeferReturnCalls(callerText)
|
||||
|
||||
var frames []Stackframe
|
||||
if selg == nil {
|
||||
if !curthread.Blocked() {
|
||||
frames, err = ThreadStacktrace(curthread, 3)
|
||||
}
|
||||
} else {
|
||||
frames, err = selg.Stacktrace(3, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var callpc uint64
|
||||
|
||||
if isPanicCall(frames) {
|
||||
if len(frames) < 4 || frames[3].Current.Fn == nil {
|
||||
return &ErrNoSourceForPC{frames[2].Current.PC}
|
||||
}
|
||||
callpc, err = findCallInstrForRet(p, curthread, frames[2].Ret, frames[3].Current.Fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if ok, pc := isDeferReturnCall(frames, deferReturns); ok {
|
||||
callpc = pc
|
||||
} else {
|
||||
callpc, err = findCallInstrForRet(p, curthread, topframe.Ret, retframe.Current.Fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = allowDuplicateBreakpoint(p.SetBreakpoint(callpc, NextBreakpoint, sameGCond))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GoroutinesInfo searches for goroutines starting at index 'start', and
|
||||
// returns an array of up to 'count' (or all found elements, if 'count' is 0)
|
||||
// G structures representing the information Delve care about from the internal
|
||||
|
@ -385,6 +385,10 @@ const (
|
||||
contNext
|
||||
contStep
|
||||
contStepout
|
||||
contReverseNext
|
||||
contReverseStep
|
||||
contReverseStepout
|
||||
contContinueToBreakpoint
|
||||
)
|
||||
|
||||
type seqTest struct {
|
||||
@ -459,6 +463,35 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te
|
||||
_, err := p.ClearBreakpoint(bp.Addr)
|
||||
assertNoError(err, t, "ClearBreakpoint() returned an error")
|
||||
}
|
||||
case contReverseNext:
|
||||
if traceTestseq2 {
|
||||
t.Log("reverse-next")
|
||||
}
|
||||
assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch")
|
||||
assertNoError(proc.Next(p), t, "reverse Next() returned an error")
|
||||
assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch")
|
||||
case contReverseStep:
|
||||
if traceTestseq2 {
|
||||
t.Log("reverse-step")
|
||||
}
|
||||
assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch")
|
||||
assertNoError(proc.Step(p), t, "reverse Step() returned an error")
|
||||
assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch")
|
||||
case contReverseStepout:
|
||||
if traceTestseq2 {
|
||||
t.Log("reverse-stepout")
|
||||
}
|
||||
assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch")
|
||||
assertNoError(proc.StepOut(p), t, "reverse StepOut() returned an error")
|
||||
assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch")
|
||||
case contContinueToBreakpoint:
|
||||
bp := setFileBreakpoint(p, t, fixture.Source, tc.pos.(int))
|
||||
if traceTestseq2 {
|
||||
t.Log("continue")
|
||||
}
|
||||
assertNoError(proc.Continue(p), t, "Continue() returned an error")
|
||||
_, err := p.ClearBreakpoint(bp.Addr)
|
||||
assertNoError(err, t, "ClearBreakpoint() returned an error")
|
||||
}
|
||||
|
||||
f, ln = currentLineNumber(p, t)
|
||||
@ -467,7 +500,7 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te
|
||||
|
||||
if traceTestseq2 {
|
||||
t.Logf("at %#x %s:%d", pc, f, ln)
|
||||
fmt.Printf("at %#x %s:%d", pc, f, ln)
|
||||
fmt.Printf("at %#x %s:%d\n", pc, f, ln)
|
||||
}
|
||||
switch pos := tc.pos.(type) {
|
||||
case int:
|
||||
@ -4600,3 +4633,118 @@ func BenchmarkConditionalBreakpoints(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackwardNextGeneral(t *testing.T) {
|
||||
if testBackend != "rr" {
|
||||
t.Skip("Reverse stepping test needs rr")
|
||||
}
|
||||
testseq2(t, "testnextprog", "main.helloworld", []seqTest{
|
||||
{contContinue, 13},
|
||||
{contNext, 14},
|
||||
{contReverseNext, 13},
|
||||
{contReverseNext, 34},
|
||||
{contReverseNext, 28},
|
||||
{contReverseNext, 27},
|
||||
{contReverseNext, 26},
|
||||
{contReverseNext, 24},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 31},
|
||||
{contReverseNext, 26},
|
||||
{contReverseNext, 24},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 31},
|
||||
{contReverseNext, 26},
|
||||
{contReverseNext, 24},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 20},
|
||||
{contReverseNext, 19},
|
||||
{contReverseNext, 17},
|
||||
{contReverseNext, 39},
|
||||
{contReverseNext, 38},
|
||||
{contReverseNext, 37},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackwardStepOutGeneral(t *testing.T) {
|
||||
if testBackend != "rr" {
|
||||
t.Skip("Reverse stepping test needs rr")
|
||||
}
|
||||
testseq2(t, "testnextprog", "main.helloworld", []seqTest{
|
||||
{contContinue, 13},
|
||||
{contNext, 14},
|
||||
{contReverseStepout, 34},
|
||||
{contReverseStepout, 39},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackwardStepGeneral(t *testing.T) {
|
||||
if testBackend != "rr" {
|
||||
t.Skip("Reverse stepping test needs rr")
|
||||
}
|
||||
testseq2(t, "testnextprog", "main.helloworld", []seqTest{
|
||||
{contContinue, 13},
|
||||
{contNext, 14},
|
||||
{contReverseStep, 13},
|
||||
{contReverseStep, 34},
|
||||
{contReverseStep, 28},
|
||||
{contReverseNext, 27}, // skip fmt.Printf
|
||||
{contReverseStep, 26},
|
||||
{contReverseStep, 24},
|
||||
{contReverseStep, 23},
|
||||
{contReverseStep, 11},
|
||||
{contReverseNext, 10}, // skip time.Sleep
|
||||
{contReverseStep, 9},
|
||||
|
||||
{contReverseStep, 31},
|
||||
{contReverseStep, 26},
|
||||
{contReverseStep, 24},
|
||||
{contReverseStep, 23},
|
||||
{contReverseStep, 11},
|
||||
{contReverseNext, 10}, // skip time.Sleep
|
||||
{contReverseStep, 9},
|
||||
|
||||
{contReverseStep, 31},
|
||||
{contReverseStep, 26},
|
||||
{contReverseStep, 24},
|
||||
{contReverseStep, 23},
|
||||
{contReverseStep, 20},
|
||||
{contReverseStep, 19},
|
||||
{contReverseStep, 17},
|
||||
{contReverseStep, 39},
|
||||
{contReverseStep, 38},
|
||||
{contReverseStep, 37},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackwardNextDeferPanic(t *testing.T) {
|
||||
if testBackend != "rr" {
|
||||
t.Skip("Reverse stepping test needs rr")
|
||||
}
|
||||
testseq2(t, "defercall", "", []seqTest{
|
||||
{contContinue, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
|
||||
{contContinueToBreakpoint, 12}, // skip first call to sampleFunction
|
||||
{contContinueToBreakpoint, 6}, // go to call to sampleFunction through deferreturn
|
||||
{contReverseNext, 13},
|
||||
{contReverseNext, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
|
||||
{contContinueToBreakpoint, 18}, // go to panic call
|
||||
{contNext, 6}, // panic so the deferred call happens
|
||||
{contReverseNext, 18},
|
||||
{contReverseNext, 17},
|
||||
{contReverseNext, 16},
|
||||
{contReverseNext, 15},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 22},
|
||||
{contReverseNext, 21},
|
||||
{contReverseNext, 28},
|
||||
})
|
||||
}
|
||||
|
@ -143,6 +143,7 @@ func (err *ErrNoSourceForPC) Error() string {
|
||||
// when removing instructions belonging to inlined calls we also remove all
|
||||
// instructions belonging to the current inlined call.
|
||||
func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
backward := dbp.GetDirection() == Backward
|
||||
selg := dbp.SelectedGoroutine()
|
||||
curthread := dbp.CurrentThread()
|
||||
topframe, retframe, err := topframe(selg, curthread)
|
||||
@ -154,6 +155,10 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
return &ErrNoSourceForPC{topframe.Current.PC}
|
||||
}
|
||||
|
||||
if backward && retframe.Current.Fn == nil {
|
||||
return &ErrNoSourceForPC{retframe.Current.PC}
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if inlinedStepOut && !topframe.Inlined {
|
||||
panic("next called with inlinedStepOut but topframe was not inlined")
|
||||
@ -178,12 +183,36 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
sameGCond := SameGoroutineCondition(selg)
|
||||
|
||||
var firstPCAfterPrologue uint64
|
||||
|
||||
if backward {
|
||||
firstPCAfterPrologue, err = FirstPCAfterPrologue(dbp, topframe.Current.Fn, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if firstPCAfterPrologue == topframe.Current.PC {
|
||||
// We don't want to step into the prologue so we just execute a reverse step out instead
|
||||
if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
success = true
|
||||
return nil
|
||||
}
|
||||
|
||||
topframe.Ret, err = findCallInstrForRet(dbp, thread, topframe.Ret, retframe.Current.Fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false)
|
||||
if err != nil && stepInto {
|
||||
return err
|
||||
}
|
||||
|
||||
sameGCond := SameGoroutineCondition(selg)
|
||||
retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
|
||||
sameFrameCond := andFrameoffCondition(sameGCond, topframe.FrameOffset())
|
||||
var sameOrRetFrameCond ast.Expr
|
||||
@ -203,50 +232,17 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
if stepInto {
|
||||
for _, instr := range text {
|
||||
if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
|
||||
continue
|
||||
}
|
||||
|
||||
if instr.DestLoc != nil {
|
||||
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Non-absolute call instruction, set a StepBreakpoint here
|
||||
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond); err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if stepInto && !backward {
|
||||
err := setStepIntoBreakpoints(dbp, text, topframe, sameGCond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !csource {
|
||||
deferreturns := FindDeferReturnCalls(text)
|
||||
|
||||
// Set breakpoint on the most recently deferred function (if any)
|
||||
var deferpc uint64
|
||||
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
|
||||
deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
|
||||
var err error
|
||||
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if deferpc != 0 && deferpc != topframe.Current.PC {
|
||||
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if bp != nil && stepInto {
|
||||
bp.DeferReturns = deferreturns
|
||||
}
|
||||
if !backward {
|
||||
_, err = setDeferBreakpoint(dbp, text, topframe, sameGCond, stepInto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,6 +252,20 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if backward {
|
||||
// Ensure that pcs contains firstPCAfterPrologue when reverse stepping.
|
||||
found := false
|
||||
for _, pc := range pcs {
|
||||
if pc == firstPCAfterPrologue {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
pcs = append(pcs, firstPCAfterPrologue)
|
||||
}
|
||||
}
|
||||
|
||||
if !stepInto {
|
||||
// Removing any PC range belonging to an inlined call
|
||||
frame := topframe
|
||||
@ -286,14 +296,19 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
}
|
||||
|
||||
for _, pc := range pcs {
|
||||
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond); err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return err
|
||||
}
|
||||
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond)); err != nil {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if stepInto && backward {
|
||||
err := setStepIntoBreakpointsReverse(dbp, text, topframe, sameGCond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !topframe.Inlined {
|
||||
// Add a breakpoint on the return address for the current frame.
|
||||
// For inlined functions there is no need to do this, the set of PCs
|
||||
@ -325,6 +340,46 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setStepIntoBreakpoints(dbp Process, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr) error {
|
||||
for _, instr := range text {
|
||||
if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
|
||||
continue
|
||||
}
|
||||
|
||||
if instr.DestLoc != nil {
|
||||
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Non-absolute call instruction, set a StepBreakpoint here
|
||||
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setStepIntoBreakpointsReverse(dbp Process, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr) error {
|
||||
// Set a breakpoint after every CALL instruction
|
||||
for i, instr := range text {
|
||||
if instr.Loc.File != topframe.Current.File || !instr.IsCall() || instr.DestLoc == nil || instr.DestLoc.Fn == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if fn := instr.DestLoc.Fn; strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if nextIdx := i + 1; nextIdx < len(text) {
|
||||
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(text[nextIdx].Loc.PC, StepBreakpoint, sameGCond)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindDeferReturnCalls(text []AsmInstruction) []uint64 {
|
||||
const deferreturn = "runtime.deferreturn"
|
||||
deferreturns := []uint64{}
|
||||
@ -415,10 +470,8 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
|
||||
}
|
||||
|
||||
// Set a breakpoint after the function's prologue
|
||||
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(pc, NextBreakpoint, cond)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -142,16 +142,16 @@ For live targets the command takes the following forms:
|
||||
If newargv is omitted the process is restarted (or re-recorded) with the same argument vector.
|
||||
If -noargs is specified instead, the argument vector is cleared.
|
||||
`},
|
||||
{aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
|
||||
{aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, helpMsg: "Single step through program."},
|
||||
{aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: "Run until breakpoint or program termination."},
|
||||
{aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, allowedPrefixes: revPrefix, helpMsg: "Single step through program."},
|
||||
{aliases: []string{"step-instruction", "si"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."},
|
||||
{aliases: []string{"next", "n"}, group: runCmds, cmdFn: c.next, helpMsg: `Step over to next source line.
|
||||
{aliases: []string{"next", "n"}, group: runCmds, cmdFn: c.next, allowedPrefixes: revPrefix, helpMsg: `Step over to next source line.
|
||||
|
||||
next [count]
|
||||
|
||||
Optional [count] argument allows you to skip multiple lines.
|
||||
`},
|
||||
{aliases: []string{"stepout", "so"}, group: runCmds, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
|
||||
{aliases: []string{"stepout", "so"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
|
||||
{aliases: []string{"call"}, group: runCmds, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||
|
||||
call [-unsafe] <function call expression>
|
||||
@ -390,7 +390,7 @@ For example:
|
||||
c.cmds = append(c.cmds, command{
|
||||
aliases: []string{"rewind", "rw"},
|
||||
group: runCmds,
|
||||
cmdFn: rewind,
|
||||
cmdFn: c.rewind,
|
||||
helpMsg: "Run backwards until breakpoint or program termination.",
|
||||
})
|
||||
c.cmds = append(c.cmds, command{
|
||||
@ -416,6 +416,7 @@ The "note" is arbitrary text that can be used to identify the checkpoint, if it
|
||||
})
|
||||
c.cmds = append(c.cmds, command{
|
||||
aliases: []string{"rev"},
|
||||
group: runCmds,
|
||||
cmdFn: c.revCmd,
|
||||
helpMsg: `Reverses the execution of the target program for the command specified.
|
||||
Currently, only the rev step-instruction command is supported.`,
|
||||
@ -1056,6 +1057,9 @@ func printcontextNoState(t *Term) {
|
||||
}
|
||||
|
||||
func (c *Commands) cont(t *Term, ctx callContext, args string) error {
|
||||
if ctx.Prefix == revPrefix {
|
||||
return c.rewind(t, ctx, args)
|
||||
}
|
||||
c.frame = 0
|
||||
stateChan := t.client.Continue()
|
||||
var state *api.DebuggerState
|
||||
@ -1079,7 +1083,7 @@ func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, sho
|
||||
}
|
||||
for {
|
||||
fmt.Printf("\tbreakpoint hit during %s, continuing...\n", op)
|
||||
stateChan := t.client.Continue()
|
||||
stateChan := t.client.DirectionCongruentContinue()
|
||||
var state *api.DebuggerState
|
||||
for state = range stateChan {
|
||||
if state.Err != nil {
|
||||
@ -1117,7 +1121,11 @@ func (c *Commands) step(t *Term, ctx callContext, args string) error {
|
||||
return err
|
||||
}
|
||||
c.frame = 0
|
||||
state, err := exitedToError(t.client.Step())
|
||||
stepfn := t.client.Step
|
||||
if ctx.Prefix == revPrefix {
|
||||
stepfn = t.client.ReverseStep
|
||||
}
|
||||
state, err := exitedToError(stepfn())
|
||||
if err != nil {
|
||||
printcontextNoState(t)
|
||||
return err
|
||||
@ -1172,6 +1180,12 @@ func (c *Commands) next(t *Term, ctx callContext, args string) error {
|
||||
if c.frame != 0 {
|
||||
return notOnFrameZeroErr
|
||||
}
|
||||
|
||||
nextfn := t.client.Next
|
||||
if ctx.Prefix == revPrefix {
|
||||
nextfn = t.client.ReverseNext
|
||||
}
|
||||
|
||||
var count int64
|
||||
var err error
|
||||
if count, err = parseOptionalCount(args); err != nil {
|
||||
@ -1180,7 +1194,7 @@ func (c *Commands) next(t *Term, ctx callContext, args string) error {
|
||||
return errors.New("Invalid next count")
|
||||
}
|
||||
for ; count > 0; count-- {
|
||||
state, err := exitedToError(t.client.Next())
|
||||
state, err := exitedToError(nextfn())
|
||||
if err != nil {
|
||||
printcontextNoState(t)
|
||||
return err
|
||||
@ -1204,7 +1218,13 @@ func (c *Commands) stepout(t *Term, ctx callContext, args string) error {
|
||||
if c.frame != 0 {
|
||||
return notOnFrameZeroErr
|
||||
}
|
||||
state, err := exitedToError(t.client.StepOut())
|
||||
|
||||
stepoutfn := t.client.StepOut
|
||||
if ctx.Prefix == revPrefix {
|
||||
stepoutfn = t.client.ReverseStepOut
|
||||
}
|
||||
|
||||
state, err := exitedToError(stepoutfn())
|
||||
if err != nil {
|
||||
printcontextNoState(t)
|
||||
return err
|
||||
@ -2304,7 +2324,8 @@ func (c *Commands) executeFile(t *Term, name string) error {
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func rewind(t *Term, ctx callContext, args string) error {
|
||||
func (c *Commands) rewind(t *Term, ctx callContext, args string) error {
|
||||
c.frame = 0
|
||||
stateChan := t.client.Rewind()
|
||||
var state *api.DebuggerState
|
||||
for state = range stateChan {
|
||||
|
@ -366,16 +366,24 @@ const (
|
||||
Continue = "continue"
|
||||
// Rewind resumes process execution backwards (target must be a recording).
|
||||
Rewind = "rewind"
|
||||
// DirecitonCongruentContinue resumes process execution, if a reverse next, step or stepout operation is in progress it will resume execution backward.
|
||||
DirectionCongruentContinue = "directionCongruentContinue"
|
||||
// Step continues to next source line, entering function calls.
|
||||
Step = "step"
|
||||
// ReverseStep continues backward to the previous line of source code, entering function calls.
|
||||
ReverseStep = "reverseStep"
|
||||
// StepOut continues to the return address of the current function
|
||||
StepOut = "stepOut"
|
||||
// ReverseStepOut continues backward to the calle rof the current function.
|
||||
ReverseStepOut = "reverseStepOut"
|
||||
// StepInstruction continues for exactly 1 cpu instruction.
|
||||
StepInstruction = "stepInstruction"
|
||||
// ReverseStepInstruction reverses execution for exactly 1 cpu instruction.
|
||||
ReverseStepInstruction = "reverseStepInstruction"
|
||||
// Next continues to the next source line, not entering function calls.
|
||||
Next = "next"
|
||||
// ReverseNext continues backward to the previous line of source code, not entering function calls.
|
||||
ReverseNext = "reverseNext"
|
||||
// SwitchThread switches the debugger's current thread context.
|
||||
SwitchThread = "switchThread"
|
||||
// SwitchGoroutine switches the debugger's current thread context to the thread running the specified goroutine
|
||||
|
@ -32,12 +32,20 @@ type Client interface {
|
||||
Continue() <-chan *api.DebuggerState
|
||||
// Rewind resumes process execution backwards.
|
||||
Rewind() <-chan *api.DebuggerState
|
||||
// DirecitonCongruentContinue resumes process execution, if a reverse next, step or stepout operation is in progress it will resume execution backward.
|
||||
DirectionCongruentContinue() <-chan *api.DebuggerState
|
||||
// Next continues to the next source line, not entering function calls.
|
||||
Next() (*api.DebuggerState, error)
|
||||
// ReverseNext continues backward to the previous line of source code, not entering function calls.
|
||||
ReverseNext() (*api.DebuggerState, error)
|
||||
// Step continues to the next source line, entering function calls.
|
||||
Step() (*api.DebuggerState, error)
|
||||
// StepOut continues to the return address of the current function
|
||||
// ReverseStep continues backward to the previous line of source code, entering function calls.
|
||||
ReverseStep() (*api.DebuggerState, error)
|
||||
// StepOut continues to the return address of the current function.
|
||||
StepOut() (*api.DebuggerState, error)
|
||||
// ReverseStepOut continues backward to the calle rof the current function.
|
||||
ReverseStepOut() (*api.DebuggerState, error)
|
||||
// Call resumes process execution while making a function call.
|
||||
Call(goroutineID int, expr string, unsafe bool) (*api.DebuggerState, error)
|
||||
|
||||
|
@ -695,9 +695,18 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
||||
switch command.Name {
|
||||
case api.Continue:
|
||||
d.log.Debug("continuing")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.Continue(d.target)
|
||||
case api.DirectionCongruentContinue:
|
||||
d.log.Debug("continuing (direction congruent)")
|
||||
err = proc.Continue(d.target)
|
||||
case api.Call:
|
||||
d.log.Debugf("function call %s", command.Expr)
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if command.ReturnInfoLoadConfig == nil {
|
||||
return nil, errors.New("can not call function with nil ReturnInfoLoadConfig")
|
||||
}
|
||||
@ -711,33 +720,57 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
||||
err = proc.EvalExpressionWithCalls(d.target, g, command.Expr, *api.LoadConfigToProc(command.ReturnInfoLoadConfig), !command.UnsafeCall)
|
||||
case api.Rewind:
|
||||
d.log.Debug("rewinding")
|
||||
if err := d.target.Direction(proc.Backward); err != nil {
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
d.target.Direction(proc.Forward)
|
||||
}()
|
||||
err = proc.Continue(d.target)
|
||||
case api.Next:
|
||||
d.log.Debug("nexting")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.Next(d.target)
|
||||
case api.ReverseNext:
|
||||
d.log.Debug("reverse nexting")
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.Next(d.target)
|
||||
case api.Step:
|
||||
d.log.Debug("stepping")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.Step(d.target)
|
||||
case api.ReverseStep:
|
||||
d.log.Debug("reverse stepping")
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.Step(d.target)
|
||||
case api.StepInstruction:
|
||||
d.log.Debug("single stepping")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.StepInstruction(d.target)
|
||||
case api.ReverseStepInstruction:
|
||||
d.log.Debug("reverse single stepping")
|
||||
if err := d.target.Direction(proc.Backward); err != nil {
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
d.target.Direction(proc.Forward)
|
||||
}()
|
||||
err = proc.StepInstruction(d.target)
|
||||
case api.StepOut:
|
||||
d.log.Debug("step out")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.StepOut(d.target)
|
||||
case api.ReverseStepOut:
|
||||
d.log.Debug("reverse step out")
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proc.StepOut(d.target)
|
||||
case api.SwitchThread:
|
||||
d.log.Debugf("switching to thread %d", command.ThreadID)
|
||||
|
@ -92,6 +92,10 @@ func (c *RPCClient) Rewind() <-chan *api.DebuggerState {
|
||||
return c.continueDir(api.Rewind)
|
||||
}
|
||||
|
||||
func (c *RPCClient) DirectionCongruentContinue() <-chan *api.DebuggerState {
|
||||
return c.continueDir(api.DirectionCongruentContinue)
|
||||
}
|
||||
|
||||
func (c *RPCClient) continueDir(cmd string) <-chan *api.DebuggerState {
|
||||
ch := make(chan *api.DebuggerState)
|
||||
go func() {
|
||||
@ -136,18 +140,36 @@ func (c *RPCClient) Next() (*api.DebuggerState, error) {
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ReverseNext() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseNext, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) Step() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.Step, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ReverseStep() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStep, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) StepOut() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.StepOut, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ReverseStepOut() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStepOut, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) Call(goroutineID int, expr string, unsafe bool) (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr, UnsafeCall: unsafe, GoroutineID: goroutineID}, &out)
|
||||
|
Loading…
Reference in New Issue
Block a user