github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/target.go (about)

     1  package proc
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"go/constant"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/go-delve/delve/pkg/dwarf/op"
    12  	"github.com/go-delve/delve/pkg/goversion"
    13  	"github.com/go-delve/delve/pkg/logflags"
    14  	"github.com/go-delve/delve/pkg/proc/internal/ebpf"
    15  )
    16  
    17  var (
    18  	// ErrNotRecorded is returned when an action is requested that is
    19  	// only possible on recorded (traced) programs.
    20  	ErrNotRecorded = errors.New("not a recording")
    21  
    22  	// ErrNoRuntimeAllG is returned when the runtime.allg list could
    23  	// not be found.
    24  	ErrNoRuntimeAllG = errors.New("could not find goroutine array")
    25  
    26  	// ErrProcessDetached indicates that we detached from the target process.
    27  	ErrProcessDetached = errors.New("detached from the process")
    28  )
    29  
    30  type LaunchFlags uint8
    31  
    32  const (
    33  	LaunchForeground LaunchFlags = 1 << iota
    34  	LaunchDisableASLR
    35  )
    36  
    37  // Target represents the process being debugged.
    38  type Target struct {
    39  	Process
    40  
    41  	proc   ProcessInternal
    42  	recman RecordingManipulationInternal
    43  
    44  	pid     int
    45  	CmdLine string
    46  
    47  	// StopReason describes the reason why the target process is stopped.
    48  	// A process could be stopped for multiple simultaneous reasons, in which
    49  	// case only one will be reported.
    50  	StopReason StopReason
    51  
    52  	// currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected.
    53  	currentThread Thread
    54  
    55  	// Goroutine that will be used by default to set breakpoint, eval variables, etc...
    56  	// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
    57  	selectedGoroutine *G
    58  
    59  	// fncallForG stores a mapping of current active function calls.
    60  	fncallForG map[int64]*callInjection
    61  
    62  	asyncPreemptChanged bool  // runtime/debug.asyncpreemptoff was changed
    63  	asyncPreemptOff     int64 // cached value of runtime/debug.asyncpreemptoff
    64  
    65  	// gcache is a cache for Goroutines that we
    66  	// have read and parsed from the targets memory.
    67  	// This must be cleared whenever the target is resumed.
    68  	gcache goroutineCache
    69  	iscgo  *bool
    70  
    71  	// exitStatus is the exit status of the process we are debugging.
    72  	// Saved here to relay to any future commands.
    73  	exitStatus int
    74  
    75  	// fakeMemoryRegistry contains the list of all compositeMemory objects
    76  	// created since the last restart, it exists so that registerized variables
    77  	// can be given a unique address.
    78  	fakeMemoryRegistry    []*compositeMemory
    79  	fakeMemoryRegistryMap map[string]*compositeMemory
    80  
    81  	partOfGroup bool
    82  }
    83  
    84  type KeepSteppingBreakpoints uint8
    85  
    86  const (
    87  	HaltKeepsSteppingBreakpoints KeepSteppingBreakpoints = 1 << iota
    88  	TracepointKeepsSteppingBreakpoints
    89  )
    90  
    91  // ErrProcessExited indicates that the process has exited and contains both
    92  // process id and exit status.
    93  type ErrProcessExited struct {
    94  	Pid    int
    95  	Status int
    96  }
    97  
    98  func (pe ErrProcessExited) Error() string {
    99  	return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status)
   100  }
   101  
   102  // StopReason describes the reason why the target process is stopped.
   103  // A process could be stopped for multiple simultaneous reasons, in which
   104  // case only one will be reported.
   105  type StopReason uint8
   106  
   107  // String maps StopReason to string representation.
   108  func (sr StopReason) String() string {
   109  	switch sr {
   110  	case StopUnknown:
   111  		return "unknown"
   112  	case StopLaunched:
   113  		return "launched"
   114  	case StopAttached:
   115  		return "attached"
   116  	case StopExited:
   117  		return "exited"
   118  	case StopBreakpoint:
   119  		return "breakpoint"
   120  	case StopHardcodedBreakpoint:
   121  		return "hardcoded breakpoint"
   122  	case StopManual:
   123  		return "manual"
   124  	case StopNextFinished:
   125  		return "next finished"
   126  	case StopCallReturned:
   127  		return "call returned"
   128  	case StopWatchpoint:
   129  		return "watchpoint"
   130  	default:
   131  		return ""
   132  	}
   133  }
   134  
   135  const (
   136  	StopUnknown             StopReason = iota
   137  	StopLaunched                       // The process was just launched
   138  	StopAttached                       // The debugger stopped the process after attaching
   139  	StopExited                         // The target process terminated
   140  	StopBreakpoint                     // The target process hit one or more software breakpoints
   141  	StopHardcodedBreakpoint            // The target process hit a hardcoded breakpoint (for example runtime.Breakpoint())
   142  	StopManual                         // A manual stop was requested
   143  	StopNextFinished                   // The next/step/stepout/stepInstruction command terminated
   144  	StopCallReturned                   // An injected call completed
   145  	StopWatchpoint                     // The target process hit one or more watchpoints
   146  )
   147  
   148  // DisableAsyncPreemptEnv returns a process environment (like os.Environ)
   149  // where asyncpreemptoff is set to 1.
   150  func DisableAsyncPreemptEnv() []string {
   151  	env := os.Environ()
   152  	for i := range env {
   153  		if strings.HasPrefix(env[i], "GODEBUG=") {
   154  			// Go 1.14 asynchronous preemption mechanism is incompatible with
   155  			// debuggers, see: https://github.com/golang/go/issues/36494
   156  			env[i] += ",asyncpreemptoff=1"
   157  		}
   158  	}
   159  	return env
   160  }
   161  
   162  // newTarget returns an initialized Target object.
   163  // The p argument can optionally implement the RecordingManipulation interface.
   164  func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thread, path, cmdline string) (*Target, error) {
   165  	entryPoint, err := p.EntryPoint()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	err = p.BinInfo().LoadBinaryInfo(path, entryPoint, grp.cfg.DebugInfoDirs)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	for _, image := range p.BinInfo().Images {
   175  		if image.loadErr != nil {
   176  			return nil, image.loadErr
   177  		}
   178  	}
   179  
   180  	t := &Target{
   181  		Process:       p,
   182  		proc:          p,
   183  		fncallForG:    make(map[int64]*callInjection),
   184  		currentThread: currentThread,
   185  		pid:           pid,
   186  		CmdLine:       cmdline,
   187  	}
   188  
   189  	if recman, ok := p.(RecordingManipulationInternal); ok {
   190  		t.recman = recman
   191  	} else {
   192  		t.recman = &dummyRecordingManipulation{}
   193  	}
   194  
   195  	g, _ := GetG(currentThread)
   196  	t.selectedGoroutine = g
   197  
   198  	t.Breakpoints().Logical = grp.LogicalBreakpoints
   199  	t.createUnrecoveredPanicBreakpoint()
   200  	t.createFatalThrowBreakpoint()
   201  	t.createPluginOpenBreakpoint()
   202  
   203  	t.gcache.init(p.BinInfo())
   204  	t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
   205  
   206  	if grp.cfg.DisableAsyncPreempt {
   207  		setAsyncPreemptOff(t, 1)
   208  	}
   209  
   210  	return t, nil
   211  }
   212  
   213  // Pid returns the pid of the target process.
   214  func (t *Target) Pid() int {
   215  	return t.pid
   216  }
   217  
   218  // IsCgo returns the value of runtime.iscgo
   219  func (t *Target) IsCgo() bool {
   220  	if t.iscgo != nil {
   221  		return *t.iscgo
   222  	}
   223  	scope := globalScope(t, t.BinInfo(), t.BinInfo().Images[0], t.Memory())
   224  	iscgov, err := scope.findGlobal("runtime", "iscgo")
   225  	if err == nil {
   226  		iscgov.loadValue(loadFullValue)
   227  		if iscgov.Unreadable == nil {
   228  			t.iscgo = new(bool)
   229  			*t.iscgo = constant.BoolVal(iscgov.Value)
   230  			return constant.BoolVal(iscgov.Value)
   231  		}
   232  	}
   233  	return false
   234  }
   235  
   236  // Valid returns true if this Process can be used. When it returns false it
   237  // also returns an error describing why the Process is invalid (either
   238  // ErrProcessExited or ErrProcessDetached).
   239  func (t *Target) Valid() (bool, error) {
   240  	ok, err := t.proc.Valid()
   241  	if !ok && err != nil {
   242  		if pe, ok := err.(ErrProcessExited); ok {
   243  			pe.Status = t.exitStatus
   244  			err = pe
   245  		}
   246  	}
   247  	return ok, err
   248  }
   249  
   250  // SupportsFunctionCalls returns whether or not the backend supports
   251  // calling functions during a debug session.
   252  // Currently only non-recorded processes running on AMD64 support
   253  // function calls.
   254  func (t *Target) SupportsFunctionCalls() bool {
   255  	return t.Process.BinInfo().Arch.Name == "amd64" || (t.Process.BinInfo().Arch.Name == "arm64" && t.Process.BinInfo().GOOS != "windows")
   256  }
   257  
   258  // ClearCaches clears internal caches that should not survive a restart.
   259  // This should be called anytime the target process executes instructions.
   260  func (t *Target) ClearCaches() {
   261  	t.clearFakeMemory()
   262  	t.gcache.Clear()
   263  	for _, thread := range t.ThreadList() {
   264  		thread.Common().g = nil
   265  	}
   266  }
   267  
   268  // Restart will start the process group over from the location specified by the "from" locspec.
   269  // This is only useful for recorded targets.
   270  // Restarting of a normal process happens at a higher level (debugger.Restart).
   271  func (grp *TargetGroup) Restart(from string) error {
   272  	if len(grp.targets) != 1 {
   273  		panic("multiple targets not implemented")
   274  	}
   275  	for _, t := range grp.targets {
   276  		t.ClearCaches()
   277  	}
   278  	t := grp.Selected
   279  	currentThread, err := t.recman.Restart(grp.cctx, from)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	t.currentThread = currentThread
   284  	t.selectedGoroutine, _ = GetG(t.CurrentThread())
   285  	if from != "" {
   286  		t.StopReason = StopManual
   287  	} else {
   288  		t.StopReason = StopLaunched
   289  	}
   290  	return nil
   291  }
   292  
   293  // SelectedGoroutine returns the currently selected goroutine.
   294  func (t *Target) SelectedGoroutine() *G {
   295  	return t.selectedGoroutine
   296  }
   297  
   298  // SwitchGoroutine will change the selected and active goroutine.
   299  func (t *Target) SwitchGoroutine(g *G) error {
   300  	if ok, err := t.Valid(); !ok {
   301  		return err
   302  	}
   303  	if g == nil {
   304  		return nil
   305  	}
   306  	if g.Thread != nil {
   307  		return t.SwitchThread(g.Thread.ThreadID())
   308  	}
   309  	t.selectedGoroutine = g
   310  	return nil
   311  }
   312  
   313  // SwitchThread will change the selected and active thread.
   314  func (t *Target) SwitchThread(tid int) error {
   315  	if ok, err := t.Valid(); !ok {
   316  		return err
   317  	}
   318  	if th, ok := t.FindThread(tid); ok {
   319  		t.currentThread = th
   320  		t.selectedGoroutine, _ = GetG(t.CurrentThread())
   321  		return nil
   322  	}
   323  	return fmt.Errorf("thread %d does not exist", tid)
   324  }
   325  
   326  // setAsyncPreemptOff enables or disables async goroutine preemption by
   327  // writing the value 'v' to runtime.debug.asyncpreemptoff.
   328  // A value of '1' means off, a value of '0' means on.
   329  func setAsyncPreemptOff(p *Target, v int64) {
   330  	if producer := p.BinInfo().Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 14) {
   331  		return
   332  	}
   333  	logger := p.BinInfo().logger
   334  	scope := globalScope(p, p.BinInfo(), p.BinInfo().Images[0], p.Memory())
   335  	// +rtype -var debug anytype
   336  	debugv, err := scope.findGlobal("runtime", "debug")
   337  	if err != nil {
   338  		logger.Warnf("could not find runtime/debug variable (or unreadable): %v", err)
   339  		return
   340  	}
   341  	if debugv.Unreadable != nil {
   342  		logger.Warnf("runtime/debug variable unreadable: %v", err, debugv.Unreadable)
   343  		return
   344  	}
   345  	asyncpreemptoffv, err := debugv.structMember("asyncpreemptoff") // +rtype int32
   346  	if err != nil {
   347  		logger.Warnf("could not find asyncpreemptoff field: %v", err)
   348  		return
   349  	}
   350  	asyncpreemptoffv.loadValue(loadFullValue)
   351  	if asyncpreemptoffv.Unreadable != nil {
   352  		logger.Warnf("asyncpreemptoff field unreadable: %v", asyncpreemptoffv.Unreadable)
   353  		return
   354  	}
   355  	p.asyncPreemptChanged = true
   356  	p.asyncPreemptOff, _ = constant.Int64Val(asyncpreemptoffv.Value)
   357  
   358  	err = scope.setValue(asyncpreemptoffv, newConstant(constant.MakeInt64(v), scope.Mem), "")
   359  	if err != nil {
   360  		logger.Warnf("could not set asyncpreemptoff %v", err)
   361  	}
   362  }
   363  
   364  // createUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
   365  func (t *Target) createUnrecoveredPanicBreakpoint() {
   366  	panicpcs, err := FindFunctionLocation(t.Process, "runtime.startpanic", 0)
   367  	if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
   368  		panicpcs, err = FindFunctionLocation(t.Process, "runtime.fatalpanic", 0)
   369  	}
   370  	if err == nil {
   371  		bp, err := t.SetBreakpoint(unrecoveredPanicID, panicpcs[0], UserBreakpoint, nil)
   372  		if err == nil {
   373  			bp.Logical.Name = UnrecoveredPanic
   374  			bp.Logical.Variables = []string{"runtime.curg._panic.arg"}
   375  		}
   376  	}
   377  }
   378  
   379  // createFatalThrowBreakpoint creates the a breakpoint as runtime.fatalthrow.
   380  func (t *Target) createFatalThrowBreakpoint() {
   381  	setFatalThrow := func(pcs []uint64, err error) {
   382  		if err == nil {
   383  			bp, err := t.SetBreakpoint(fatalThrowID, pcs[0], UserBreakpoint, nil)
   384  			if err == nil {
   385  				bp.Logical.Name = FatalThrow
   386  			}
   387  		}
   388  	}
   389  	setFatalThrow(FindFunctionLocation(t.Process, "runtime.throw", 0))
   390  	setFatalThrow(FindFunctionLocation(t.Process, "runtime.fatal", 0))
   391  	setFatalThrow(FindFunctionLocation(t.Process, "runtime.winthrow", 0))
   392  	setFatalThrow(FindFunctionLocation(t.Process, "runtime.fatalsignal", 0))
   393  }
   394  
   395  // createPluginOpenBreakpoint creates a breakpoint at the return instruction
   396  // of plugin.Open (if it exists) that will try to enable suspended
   397  // breakpoints.
   398  func (t *Target) createPluginOpenBreakpoint() {
   399  	retpcs, _ := findRetPC(t, "plugin.Open")
   400  	for _, retpc := range retpcs {
   401  		bp, err := t.SetBreakpoint(0, retpc, PluginOpenBreakpoint, nil)
   402  		if err != nil {
   403  			t.BinInfo().logger.Errorf("could not set plugin.Open breakpoint: %v", err)
   404  		} else {
   405  			bp.Breaklets[len(bp.Breaklets)-1].callback = t.pluginOpenCallback
   406  		}
   407  	}
   408  }
   409  
   410  // CurrentThread returns the currently selected thread which will be used
   411  // for next/step/stepout and for reading variables, unless a goroutine is
   412  // selected.
   413  func (t *Target) CurrentThread() Thread {
   414  	return t.currentThread
   415  }
   416  
   417  type UProbeTraceResult struct {
   418  	FnAddr       int
   419  	GoroutineID  int
   420  	IsRet        bool
   421  	InputParams  []*Variable
   422  	ReturnParams []*Variable
   423  }
   424  
   425  func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult {
   426  	var results []*UProbeTraceResult
   427  	tracepoints := t.proc.GetBufferedTracepoints()
   428  	convertInputParamToVariable := func(ip *ebpf.RawUProbeParam) *Variable {
   429  		v := &Variable{}
   430  		v.RealType = ip.RealType
   431  		v.Len = ip.Len
   432  		v.Base = ip.Base
   433  		v.Addr = ip.Addr
   434  		v.Kind = ip.Kind
   435  
   436  		if v.RealType == nil {
   437  			v.Unreadable = errors.New("type not supported by ebpf")
   438  			return v
   439  		}
   440  
   441  		cachedMem := CreateLoadedCachedMemory(ip.Data)
   442  		compMem, _ := CreateCompositeMemory(cachedMem, t.BinInfo().Arch, op.DwarfRegisters{}, ip.Pieces, ip.RealType.Common().ByteSize)
   443  		v.mem = compMem
   444  
   445  		// Load the value here so that we don't have to export
   446  		// loadValue outside of proc.
   447  		v.loadValue(loadFullValue)
   448  
   449  		return v
   450  	}
   451  	for _, tp := range tracepoints {
   452  		r := &UProbeTraceResult{}
   453  		r.FnAddr = tp.FnAddr
   454  		r.GoroutineID = tp.GoroutineID
   455  		r.IsRet = tp.IsRet
   456  		for _, ip := range tp.InputParams {
   457  			v := convertInputParamToVariable(ip)
   458  			r.InputParams = append(r.InputParams, v)
   459  		}
   460  		for _, ip := range tp.ReturnParams {
   461  			v := convertInputParamToVariable(ip)
   462  			r.ReturnParams = append(r.ReturnParams, v)
   463  		}
   464  		results = append(results, r)
   465  	}
   466  	return results
   467  }
   468  
   469  // ResumeNotify specifies a channel that will be closed the next time
   470  // Continue finishes resuming the targets.
   471  func (grp *TargetGroup) ResumeNotify(ch chan<- struct{}) {
   472  	grp.cctx.ResumeChan = ch
   473  }
   474  
   475  // RequestManualStop attempts to stop all the processes' threads.
   476  func (grp *TargetGroup) RequestManualStop() error {
   477  	grp.cctx.StopMu.Lock()
   478  	defer grp.cctx.StopMu.Unlock()
   479  	grp.cctx.manualStopRequested = true
   480  	return grp.Selected.proc.RequestManualStop(grp.cctx)
   481  }
   482  
   483  const (
   484  	FakeAddressBase     = 0xbeef000000000000
   485  	fakeAddressUnresolv = 0xbeed000000000000 // this address never resolves to memory
   486  )
   487  
   488  // newCompositeMemory creates a new compositeMemory object and registers it.
   489  // If the same composite memory has been created before it will return a
   490  // cached object.
   491  // This caching is primarily done so that registerized variables don't get a
   492  // different address every time they are evaluated, which would be confusing
   493  // and leak memory.
   494  func (t *Target) newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece, descr *locationExpr, size int64) (int64, *compositeMemory, error) {
   495  	var key string
   496  	if regs.CFA != 0 && len(pieces) > 0 {
   497  		// key is created by concatenating the location expression with the CFA,
   498  		// this combination is guaranteed to be unique between resumes.
   499  		buf := new(strings.Builder)
   500  		fmt.Fprintf(buf, "%#x ", regs.CFA)
   501  		op.PrettyPrint(buf, descr.instr, t.BinInfo().Arch.RegnumToString)
   502  		key = buf.String()
   503  
   504  		if cmem := t.fakeMemoryRegistryMap[key]; cmem != nil {
   505  			return int64(cmem.base), cmem, nil
   506  		}
   507  	}
   508  
   509  	cmem, err := newCompositeMemory(mem, t.BinInfo().Arch, regs, pieces, size)
   510  	if err != nil {
   511  		return 0, cmem, err
   512  	}
   513  	t.registerFakeMemory(cmem)
   514  	if key != "" {
   515  		t.fakeMemoryRegistryMap[key] = cmem
   516  	}
   517  	return int64(cmem.base), cmem, nil
   518  }
   519  
   520  func (t *Target) registerFakeMemory(mem *compositeMemory) (addr uint64) {
   521  	t.fakeMemoryRegistry = append(t.fakeMemoryRegistry, mem)
   522  	addr = FakeAddressBase
   523  	if len(t.fakeMemoryRegistry) > 1 {
   524  		prevMem := t.fakeMemoryRegistry[len(t.fakeMemoryRegistry)-2]
   525  		addr = uint64(alignAddr(int64(prevMem.base+uint64(len(prevMem.data))), 0x100)) // the call to alignAddr just makes the address look nicer, it is not necessary
   526  	}
   527  	mem.base = addr
   528  	return addr
   529  }
   530  
   531  func (t *Target) findFakeMemory(addr uint64) *compositeMemory {
   532  	i := sort.Search(len(t.fakeMemoryRegistry), func(i int) bool {
   533  		mem := t.fakeMemoryRegistry[i]
   534  		return addr <= mem.base || (mem.base <= addr && addr < (mem.base+uint64(len(mem.data))))
   535  	})
   536  	if i != len(t.fakeMemoryRegistry) {
   537  		mem := t.fakeMemoryRegistry[i]
   538  		if mem.base <= addr && addr < (mem.base+uint64(len(mem.data))) {
   539  			return mem
   540  		}
   541  	}
   542  	return nil
   543  }
   544  
   545  func (t *Target) clearFakeMemory() {
   546  	for i := range t.fakeMemoryRegistry {
   547  		t.fakeMemoryRegistry[i] = nil
   548  	}
   549  	t.fakeMemoryRegistry = t.fakeMemoryRegistry[:0]
   550  	t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
   551  }
   552  
   553  // dwrapUnwrap checks if fn is a dwrap wrapper function and unwraps it if it is.
   554  func (t *Target) dwrapUnwrap(fn *Function) *Function {
   555  	if fn == nil {
   556  		return nil
   557  	}
   558  	if !strings.Contains(fn.Name, "·dwrap·") && !fn.trampoline {
   559  		return fn
   560  	}
   561  	if unwrap := t.BinInfo().dwrapUnwrapCache[fn.Entry]; unwrap != nil {
   562  		return unwrap
   563  	}
   564  	text, err := disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End, false)
   565  	if err != nil {
   566  		return fn
   567  	}
   568  	for _, instr := range text {
   569  		if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && !instr.DestLoc.Fn.privateRuntime() {
   570  			t.BinInfo().dwrapUnwrapCache[fn.Entry] = instr.DestLoc.Fn
   571  			return instr.DestLoc.Fn
   572  		}
   573  	}
   574  	return fn
   575  }
   576  
   577  func (t *Target) pluginOpenCallback(Thread, *Target) (bool, error) {
   578  	logger := logflags.DebuggerLogger()
   579  	for _, lbp := range t.Breakpoints().Logical {
   580  		if isSuspended(t, lbp) {
   581  			err := enableBreakpointOnTarget(t, lbp)
   582  			if err != nil {
   583  				logger.Debugf("could not enable breakpoint %d: %v", lbp.LogicalID, err)
   584  			} else {
   585  				logger.Debugf("suspended breakpoint %d enabled", lbp.LogicalID)
   586  			}
   587  		}
   588  	}
   589  	return false, nil
   590  }
   591  
   592  func isSuspended(t *Target, lbp *LogicalBreakpoint) bool {
   593  	for _, bp := range t.Breakpoints().M {
   594  		if bp.LogicalID() == lbp.LogicalID {
   595  			return false
   596  		}
   597  	}
   598  	return true
   599  }
   600  
   601  type dummyRecordingManipulation struct {
   602  }
   603  
   604  // Recorded always returns false for the native proc backend.
   605  func (*dummyRecordingManipulation) Recorded() (bool, string) { return false, "" }
   606  
   607  // ChangeDirection will always return an error in the native proc backend, only for
   608  // recorded traces.
   609  func (*dummyRecordingManipulation) ChangeDirection(dir Direction) error {
   610  	if dir != Forward {
   611  		return ErrNotRecorded
   612  	}
   613  	return nil
   614  }
   615  
   616  // GetDirection will always return Forward.
   617  func (*dummyRecordingManipulation) GetDirection() Direction { return Forward }
   618  
   619  // When will always return an empty string and nil, not supported on native proc backend.
   620  func (*dummyRecordingManipulation) When() (string, error) { return "", nil }
   621  
   622  // Checkpoint will always return an error on the native proc backend,
   623  // only supported for recorded traces.
   624  func (*dummyRecordingManipulation) Checkpoint(string) (int, error) { return -1, ErrNotRecorded }
   625  
   626  // Checkpoints will always return an error on the native proc backend,
   627  // only supported for recorded traces.
   628  func (*dummyRecordingManipulation) Checkpoints() ([]Checkpoint, error) { return nil, ErrNotRecorded }
   629  
   630  // ClearCheckpoint will always return an error on the native proc backend,
   631  // only supported in recorded traces.
   632  func (*dummyRecordingManipulation) ClearCheckpoint(int) error { return ErrNotRecorded }
   633  
   634  // Restart will always return an error in the native proc backend, only for
   635  // recorded traces.
   636  func (*dummyRecordingManipulation) Restart(*ContinueOnceContext, string) (Thread, error) {
   637  	return nil, ErrNotRecorded
   638  }
   639  
   640  var ErrWaitForNotImplemented = errors.New("waitfor not implemented")
   641  
   642  func (waitFor *WaitFor) Valid() bool {
   643  	return waitFor != nil && waitFor.Name != ""
   644  }