
When using Step on a function that has a dynamic CALL instruction we set a Step breakpoint on the call. When it is hit we determine the destination of the CALL by looking at registers, set a breakpoint there and continue. If the Step breakpoint is hit simultaneously with a normal breakpoint our Step logic will take precedence and the normal breakpoint hit will be hidden from the user. Move the Step logic to a breaklet callback so that it does not interfere with the decision to stop.
182 lines
5.7 KiB
Go
182 lines
5.7 KiB
Go
package proc
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/go-delve/delve/pkg/astutil"
|
|
"github.com/go-delve/delve/pkg/logflags"
|
|
)
|
|
|
|
// This file implements most of the details needed to support stack
|
|
// watchpoints. Some of the remaining details are in breakpoints, along with
|
|
// the code to support non-stack allocated watchpoints.
|
|
//
|
|
// In Go goroutine stacks start small and are frequently resized by the
|
|
// runtime according to the needs of the goroutine.
|
|
// To support this behavior we create a StackResizeBreakpoint, deep inside
|
|
// the Go runtime, when this breakpoint is hit all the stack watchpoints on
|
|
// the goroutine being resized are adjusted for the new stack.
|
|
// Furthermore, we need to detect when a goroutine leaves the stack frame
|
|
// where the variable we are watching was declared, so that we can notify
|
|
// the user that the variable went out of scope and clear the watchpoint.
|
|
//
|
|
// These breakpoints are created by setStackWatchBreakpoints and cleared by
|
|
// clearStackWatchBreakpoints.
|
|
|
|
// setStackWatchBreakpoints sets the out of scope sentinel breakpoints for
|
|
// watchpoint and a stack resize breakpoint.
|
|
func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoint) error {
|
|
// Watchpoint Out-of-scope Sentinel
|
|
|
|
woos := func(_ Thread, _ *Target) (bool, error) {
|
|
watchpointOutOfScope(t, watchpoint)
|
|
return true, nil
|
|
}
|
|
|
|
topframe, retframe, err := topframe(scope.g, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sameGCond := sameGoroutineCondition(scope.g)
|
|
retFrameCond := astutil.And(sameGCond, frameoffCondition(&retframe))
|
|
|
|
var deferpc uint64
|
|
if topframe.TopmostDefer != nil {
|
|
_, _, deferfn := topframe.TopmostDefer.DeferredFunc(t)
|
|
if deferfn != nil {
|
|
var err error
|
|
deferpc, err = FirstPCAfterPrologue(t, deferfn, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if deferpc != 0 && deferpc != topframe.Current.PC {
|
|
deferbp, err := t.SetBreakpoint(0, deferpc, WatchOutOfScopeBreakpoint, sameGCond)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deferbreaklet := deferbp.Breaklets[len(deferbp.Breaklets)-1]
|
|
deferbreaklet.checkPanicCall = true
|
|
deferbreaklet.watchpoint = watchpoint
|
|
deferbreaklet.callback = woos
|
|
}
|
|
|
|
retbp, err := t.SetBreakpoint(0, retframe.Current.PC, WatchOutOfScopeBreakpoint, retFrameCond)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
retbreaklet := retbp.Breaklets[len(retbp.Breaklets)-1]
|
|
retbreaklet.watchpoint = watchpoint
|
|
retbreaklet.callback = woos
|
|
|
|
if recorded, _ := t.recman.Recorded(); recorded && retframe.Current.Fn != nil {
|
|
// Must also set a breakpoint on the call instruction immediately
|
|
// preceding retframe.Current.PC, because the watchpoint could also go out
|
|
// of scope while we are running backwards.
|
|
callerText, err := disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i, instr := range callerText {
|
|
if instr.Loc.PC == retframe.Current.PC && i > 0 {
|
|
retbp2, err := t.SetBreakpoint(0, callerText[i-1].Loc.PC, WatchOutOfScopeBreakpoint, retFrameCond)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
retbreaklet2 := retbp2.Breaklets[len(retbp.Breaklets)-1]
|
|
retbreaklet2.watchpoint = watchpoint
|
|
retbreaklet2.callback = woos
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stack Resize Sentinel
|
|
|
|
retpcs, err := findRetPC(t, "runtime.copystack")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(retpcs) > 1 {
|
|
return errors.New("runtime.copystack has too many return instructions")
|
|
}
|
|
|
|
rszbp, err := t.SetBreakpoint(0, retpcs[0], StackResizeBreakpoint, sameGCond)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1]
|
|
rszbreaklet.watchpoint = watchpoint
|
|
rszbreaklet.callback = func(th Thread, _ *Target) (bool, error) {
|
|
adjustStackWatchpoint(t, th, watchpoint)
|
|
return false, nil // we never want this breakpoint to be shown to the user
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// clearStackWatchBreakpoints clears all accessory breakpoints for
|
|
// watchpoint.
|
|
func (t *Target) clearStackWatchBreakpoints(watchpoint *Breakpoint) error {
|
|
bpmap := t.Breakpoints()
|
|
for _, bp := range bpmap.M {
|
|
changed := false
|
|
for i, breaklet := range bp.Breaklets {
|
|
if breaklet.watchpoint == watchpoint {
|
|
bp.Breaklets[i] = nil
|
|
changed = true
|
|
}
|
|
}
|
|
if changed {
|
|
_, err := t.finishClearBreakpoint(bp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// watchpointOutOfScope is called when the watchpoint goes out of scope. It
|
|
// is used as a breaklet callback function.
|
|
// Its responsibility is to delete the watchpoint and make sure that the
|
|
// user is notified of the watchpoint going out of scope.
|
|
func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) {
|
|
t.Breakpoints().WatchOutOfScope = append(t.Breakpoints().WatchOutOfScope, watchpoint)
|
|
err := t.ClearBreakpoint(watchpoint.Addr)
|
|
if err != nil {
|
|
log := logflags.DebuggerLogger()
|
|
log.Errorf("could not clear out-of-scope watchpoint: %v", err)
|
|
}
|
|
delete(t.Breakpoints().Logical, watchpoint.LogicalID())
|
|
}
|
|
|
|
// adjustStackWatchpoint is called when the goroutine of watchpoint resizes
|
|
// its stack. It is used as a breaklet callback function.
|
|
// Its responsibility is to move the watchpoint to a its new address.
|
|
func adjustStackWatchpoint(t *Target, th Thread, watchpoint *Breakpoint) {
|
|
g, _ := GetG(th)
|
|
if g == nil {
|
|
return
|
|
}
|
|
err := t.proc.EraseBreakpoint(watchpoint)
|
|
if err != nil {
|
|
log := logflags.DebuggerLogger()
|
|
log.Errorf("could not adjust watchpoint at %#x: %v", watchpoint.Addr, err)
|
|
return
|
|
}
|
|
delete(t.Breakpoints().M, watchpoint.Addr)
|
|
watchpoint.Addr = uint64(int64(g.stack.hi) + watchpoint.watchStackOff)
|
|
err = t.proc.WriteBreakpoint(watchpoint)
|
|
if err != nil {
|
|
log := logflags.DebuggerLogger()
|
|
log.Errorf("could not adjust watchpoint at %#x: %v", watchpoint.Addr, err)
|
|
return
|
|
}
|
|
t.Breakpoints().M[watchpoint.Addr] = watchpoint
|
|
}
|