github.com/undoio/delve@v1.9.0/pkg/proc/stackwatch.go (about)

     1  package proc
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/undoio/delve/pkg/astutil"
     7  	"github.com/undoio/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) bool {
    32  		watchpointOutOfScope(t, watchpoint)
    33  		return true
    34  	}
    35  
    36  	topframe, retframe, err := topframe(scope.g, nil)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	sameGCond := sameGoroutineCondition(scope.g)
    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.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  	fn := t.BinInfo().LookupFunc["runtime.copystack"]
   100  	if fn == nil {
   101  		return errors.New("could not find runtime.copystack")
   102  	}
   103  	text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	var retpc uint64
   108  	for _, instr := range text {
   109  		if instr.IsRet() {
   110  			if retpc != 0 {
   111  				return errors.New("runtime.copystack has too many return instructions")
   112  			}
   113  			retpc = instr.Loc.PC
   114  		}
   115  	}
   116  	if retpc == 0 {
   117  		return errors.New("could not find return instruction in runtime.copystack")
   118  	}
   119  	rszbp, err := t.SetBreakpoint(0, retpc, StackResizeBreakpoint, sameGCond)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	rszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1]
   125  	rszbreaklet.watchpoint = watchpoint
   126  	rszbreaklet.callback = func(th Thread) bool {
   127  		adjustStackWatchpoint(t, th, watchpoint)
   128  		return false // we never want this breakpoint to be shown to the user
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // clearStackWatchBreakpoints clears all accessory breakpoints for
   135  // watchpoint.
   136  func (t *Target) clearStackWatchBreakpoints(watchpoint *Breakpoint) error {
   137  	bpmap := t.Breakpoints()
   138  	for _, bp := range bpmap.M {
   139  		changed := false
   140  		for i, breaklet := range bp.Breaklets {
   141  			if breaklet.watchpoint == watchpoint {
   142  				bp.Breaklets[i] = nil
   143  				changed = true
   144  			}
   145  		}
   146  		if changed {
   147  			_, err := t.finishClearBreakpoint(bp)
   148  			if err != nil {
   149  				return err
   150  			}
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  // watchpointOutOfScope is called when the watchpoint goes out of scope. It
   157  // is used as a breaklet callback function.
   158  // Its responsibility is to delete the watchpoint and make sure that the
   159  // user is notified of the watchpoint going out of scope.
   160  func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) {
   161  	t.Breakpoints().WatchOutOfScope = append(t.Breakpoints().WatchOutOfScope, watchpoint)
   162  	err := t.ClearBreakpoint(watchpoint.Addr)
   163  	if err != nil {
   164  		log := logflags.DebuggerLogger()
   165  		log.Errorf("could not clear out-of-scope watchpoint: %v", err)
   166  	}
   167  	delete(t.Breakpoints().Logical, watchpoint.LogicalID())
   168  }
   169  
   170  // adjustStackWatchpoint is called when the goroutine of watchpoint resizes
   171  // its stack. It is used as a breaklet callback function.
   172  // Its responsibility is to move the watchpoint to a its new address.
   173  func adjustStackWatchpoint(t *Target, th Thread, watchpoint *Breakpoint) {
   174  	g, _ := GetG(th)
   175  	if g == nil {
   176  		return
   177  	}
   178  	err := t.proc.EraseBreakpoint(watchpoint)
   179  	if err != nil {
   180  		log := logflags.DebuggerLogger()
   181  		log.Errorf("could not adjust watchpoint at %#x: %v", watchpoint.Addr, err)
   182  		return
   183  	}
   184  	delete(t.Breakpoints().M, watchpoint.Addr)
   185  	watchpoint.Addr = uint64(int64(g.stack.hi) + watchpoint.watchStackOff)
   186  	err = t.proc.WriteBreakpoint(watchpoint)
   187  	if err != nil {
   188  		log := logflags.DebuggerLogger()
   189  		log.Errorf("could not adjust watchpoint at %#x: %v", watchpoint.Addr, err)
   190  		return
   191  	}
   192  	t.Breakpoints().M[watchpoint.Addr] = watchpoint
   193  }