delve/pkg/proc/stackwatch.go
Alessandro Arzilli 36980dcbf9
proc: step breakpoints shouldn't hide normal breakpoints (#3287)
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.
2023-04-24 12:12:54 -07:00

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
}