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 }