mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	 6f34add5db
			
		
	
	6f34add5db
	
	
	
		
			
			Introduces a new TargetGroup abstraction that can be used to manage multiple related targets. No actual management of child processes is implemented here, this is just a refactoring to make it possible to do that in the future. Updates #2551
		
			
				
	
	
		
			194 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			6.0 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
 | |
| 
 | |
| 	fn := t.BinInfo().LookupFunc["runtime.copystack"]
 | |
| 	if fn == nil {
 | |
| 		return errors.New("could not find runtime.copystack")
 | |
| 	}
 | |
| 	text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	var retpc uint64
 | |
| 	for _, instr := range text {
 | |
| 		if instr.IsRet() {
 | |
| 			if retpc != 0 {
 | |
| 				return errors.New("runtime.copystack has too many return instructions")
 | |
| 			}
 | |
| 			retpc = instr.Loc.PC
 | |
| 		}
 | |
| 	}
 | |
| 	if retpc == 0 {
 | |
| 		return errors.New("could not find return instruction in runtime.copystack")
 | |
| 	}
 | |
| 	rszbp, err := t.SetBreakpoint(0, retpc, 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
 | |
| }
 |