go-delve/delve · error

could not restore SP: %v

Error message

could not restore SP: %v

What it means

Similarly to PC restoration, Delve restores the saved stack pointer after the injected call completes. When setSP fails the stack pointer cannot be written back, which usually means the thread is no longer writable or alive.

Source

Thrown at pkg/proc/fncall.go:869

		}
		errvar.Name = "err"
		fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))

	case debugCallRegCompleteCall: // 0
		p.fncallForG[callScope.g.ID].startThreadID = 0

	case debugCallRegRestoreRegisters: // 16
		// runtime requests that we restore the registers (all except pc and sp),
		// this is also the last step of the function call protocol.
		pc, sp := regs.PC(), regs.SP()
		if err := thread.RestoreRegisters(fncall.savedRegs); err != nil {
			fncall.err = fmt.Errorf("could not restore registers: %v", err)
		}
		if err := setPC(thread, pc); err != nil {
			fncall.err = fmt.Errorf("could not restore PC: %v", err)
		}
		if err := setSP(thread, sp); err != nil {
			fncall.err = fmt.Errorf("could not restore SP: %v", err)
		}
		fncallLog("stepping thread %d", thread.ThreadID())
		if err := stepInstructionOut(callScope.callCtx.grp, p, thread, fncall.debugCallName, fncall.debugCallName); err != nil {
			fncall.err = fmt.Errorf("could not step out of %s: %v", fncall.debugCallName, err)
		}
		if bi.Arch.Name == "amd64" {
			// The tail of debugCallV2 corrupts the state of RFLAGS, we must restore
			// it one extra time after stepping out of it.
			// See https://github.com/go-delve/delve/issues/2985 and
			// TestCallInjectionFlagCorruption
			rflags := bi.Arch.RegistersToDwarfRegisters(0, fncall.savedRegs).Uint64Val(regnum.AMD64_Rflags)
			err := thread.SetReg(regnum.AMD64_Rflags, op.DwarfRegisterFromUint64(rflags))
			if err != nil {
				fncall.err = fmt.Errorf("could not restore RFLAGS register: %v", err)
			}
		}
		return true

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Confirm the process is alive and re-attach if needed
  2. Retry the 'call' command in a fresh stop
  3. Check ptrace permissions (e.g. /proc/sys/kernel/yama/ptrace_scope) for native debugging
  4. Restart the debug session — corrupted SP is unsafe to continue from
Defensive patterns

Strategy: try-catch

Validate before calling

// Check ptrace works on the host before debugging:
// cat /proc/sys/kernel/yama/ptrace_scope  (0 or with CAP_SYS_PTRACE is safest)

Try / catch

err := dbg.CallFunction(scope, fn, args)
if err != nil && strings.Contains(err.Error(), "could not restore SP") {
	// SP corrupted: detach and restart the session
}

Prevention

When it happens

Trigger: funcCallStep in the restore-registers phase calls setSP(thread, sp) and the backend write fails.

Common situations: Process crash during the injected call; ptrace failures (permissions, seccomp); remote session dropped; stack pointer computed against a stale register snapshot.

Related errors


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/22c9c12223f45ae9. Report an issue: GitHub.