mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 06:32:16 +08:00 
			
		
		
		
	Allows setting suspended breakpoints and try to enable them automatically after every time a plugin is loaded. Fixes #1653 Updates #2551
		
			
				
	
	
		
			182 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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) bool {
 | 
						|
		watchpointOutOfScope(t, watchpoint)
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	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) bool {
 | 
						|
		adjustStackWatchpoint(t, th, watchpoint)
 | 
						|
		return false // 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
 | 
						|
}
 |