github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/stackwatch.go (about)

     1  package proc
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/go-delve/delve/pkg/astutil"
     7  	"github.com/go-delve/delve/pkg/logflags"
     8  )
     9  
    10  // This file implements most of the details needed to support stack
    11  // watchpoints. Some of the remaining details are in breakpoints, along with
    12  // the code to support non-stack allocated watchpoints.
    13  //
    14  // In Go goroutine stacks start small and are frequently resized by the
    15  // runtime according to the needs of the goroutine.
    16  // To support this behavior we create a StackResizeBreakpoint, deep inside
    17  // the Go runtime, when this breakpoint is hit all the stack watchpoints on
    18  // the goroutine being resized are adjusted for the new stack.
    19  // Furthermore, we need to detect when a goroutine leaves the stack frame
    20  // where the variable we are watching was declared, so that we can notify
    21  // the user that the variable went out of scope and clear the watchpoint.
    22  //
    23  // These breakpoints are created by setStackWatchBreakpoints and cleared by
    24  // clearStackWatchBreakpoints.
    25  
    26  // setStackWatchBreakpoints sets the out of scope sentinel breakpoints for
    27  // watchpoint and a stack resize breakpoint.
    28  func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoint) error {
    29  	// Watchpoint Out-of-scope Sentinel
    30  
    31  	woos := func(_ Thread, _ *Target) (bool, error) {
    32  		watchpointOutOfScope(t, watchpoint)
    33  		return true, nil
    34  	}
    35  
    36  	topframe, retframe, err := topframe(t, scope.g, nil)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	sameGCond := sameGoroutineCondition(scope.BinInfo, scope.g, 0)
    42  	retFrameCond := astutil.And(sameGCond, frameoffCondition(&retframe))
    43  
    44  	var deferpc uint64
    45  	if topframe.TopmostDefer != nil {
    46  		_, _, deferfn := topframe.TopmostDefer.DeferredFunc(t)
    47  		if deferfn != nil {
    48  			var err error
    49  			deferpc, err = FirstPCAfterPrologue(t, deferfn, false)
    50  			if err != nil {
    51  				return err
    52  			}
    53  		}
    54  	}
    55  	if deferpc != 0 && deferpc != topframe.Current.PC {
    56  		deferbp, err := t.SetBreakpoint(0, deferpc, WatchOutOfScopeBreakpoint, sameGCond)
    57  		if err != nil {
    58  			return err
    59  		}
    60  		deferbreaklet := deferbp.Breaklets[len(deferbp.Breaklets)-1]
    61  		deferbreaklet.checkPanicCall = true
    62  		deferbreaklet.watchpoint = watchpoint
    63  		deferbreaklet.callback = woos
    64  	}
    65  
    66  	retbp, err := t.SetBreakpoint(0, retframe.Current.PC, WatchOutOfScopeBreakpoint, retFrameCond)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	retbreaklet := retbp.Breaklets[len(retbp.Breaklets)-1]
    72  	retbreaklet.watchpoint = watchpoint
    73  	retbreaklet.callback = woos
    74  
    75  	if recorded, _ := t.recman.Recorded(); recorded && retframe.Current.Fn != nil {
    76  		// Must also set a breakpoint on the call instruction immediately
    77  		// preceding retframe.Current.PC, because the watchpoint could also go out
    78  		// of scope while we are running backwards.
    79  		callerText, err := disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		for i, instr := range callerText {
    84  			if instr.Loc.PC == retframe.Current.PC && i > 0 {
    85  				retbp2, err := t.SetBreakpoint(0, callerText[i-1].Loc.PC, WatchOutOfScopeBreakpoint, retFrameCond)
    86  				if err != nil {
    87  					return err
    88  				}
    89  				retbreaklet2 := retbp2.Breaklets[len(retbp.Breaklets)-1]
    90  				retbreaklet2.watchpoint = watchpoint
    91  				retbreaklet2.callback = woos
    92  				break
    93  			}
    94  		}
    95  	}
    96  
    97  	// Stack Resize Sentinel
    98  
    99  	retpcs, err := findRetPC(t, "runtime.copystack")
   100  	if err != nil {
   101  		return err
   102  	}
   103  	if len(retpcs) > 1 {
   104  		return errors.New("runtime.copystack has too many return instructions")
   105  	}
   106  
   107  	rszbp, err := t.SetBreakpoint(0, retpcs[0], StackResizeBreakpoint, sameGCond)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	rszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1]
   113  	rszbreaklet.watchpoint = watchpoint
   114  	rszbreaklet.callback = func(th Thread, _ *Target) (bool, error) {
   115  		adjustStackWatchpoint(t, th, watchpoint)
   116  		return false, nil // we never want this breakpoint to be shown to the user
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  // clearStackWatchBreakpoints clears all accessory breakpoints for
   123  // watchpoint.
   124  func (t *Target) clearStackWatchBreakpoints(watchpoint *Breakpoint) error {
   125  	bpmap := t.Breakpoints()
   126  	for _, bp := range bpmap.M {
   127  		changed := false
   128  		for i, breaklet := range bp.Breaklets {
   129  			if breaklet.watchpoint == watchpoint {
   130  				bp.Breaklets[i] = nil
   131  				changed = true
   132  			}
   133  		}
   134  		if changed {
   135  			_, err := t.finishClearBreakpoint(bp)
   136  			if err != nil {
   137  				return err
   138  			}
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  // watchpointOutOfScope is called when the watchpoint goes out of scope. It
   145  // is used as a breaklet callback function.
   146  // Its responsibility is to delete the watchpoint and make sure that the
   147  // user is notified of the watchpoint going out of scope.
   148  func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) {
   149  	t.Breakpoints().WatchOutOfScope = append(t.Breakpoints().WatchOutOfScope, watchpoint)
   150  	err := t.ClearBreakpoint(watchpoint.Addr)
   151  	if err != nil {
   152  		log := logflags.DebuggerLogger()
   153  		log.Errorf("could not clear out-of-scope watchpoint: %v", err)
   154  	}
   155  	delete(t.Breakpoints().Logical, watchpoint.LogicalID())
   156  }
   157  
   158  // adjustStackWatchpoint is called when the goroutine of watchpoint resizes
   159  // its stack. It is used as a breaklet callback function.
   160  // Its responsibility is to move the watchpoint to a its new address.
   161  func adjustStackWatchpoint(t *Target, th Thread, watchpoint *Breakpoint) {
   162  	g, _ := GetG(th)
   163  	if g == nil {
   164  		return
   165  	}
   166  	err := t.proc.EraseBreakpoint(watchpoint)
   167  	if err != nil {
   168  		log := logflags.DebuggerLogger()
   169  		log.Errorf("could not adjust watchpoint at %#x: %v", watchpoint.Addr, err)
   170  		return
   171  	}
   172  	delete(t.Breakpoints().M, watchpoint.Addr)
   173  	watchpoint.Addr = uint64(int64(g.stack.hi) + watchpoint.watchStackOff)
   174  	err = t.proc.WriteBreakpoint(watchpoint)
   175  	if err != nil {
   176  		log := logflags.DebuggerLogger()
   177  		log.Errorf("could not adjust watchpoint at %#x: %v", watchpoint.Addr, err)
   178  		return
   179  	}
   180  	t.Breakpoints().M[watchpoint.Addr] = watchpoint
   181  }