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