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 }