go-delve/delve · error

could not restore PC: %v

Error message

could not restore PC: %v

What it means

After restoring registers, Delve must also restore the saved program counter so execution continues where the injected call left off. This error occurs when setPC fails on the thread, indicating the instruction pointer could not be written back.

Source

Thrown at pkg/proc/fncall.go:866

		if err != nil {
			fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
			break
		}
		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)
			}

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Verify the debuggee is alive; restart the session if it died
  2. Retry the call injection after re-attaching
  3. Use a native backend session if the failure is backend-specific
  4. Report a bug with backend, OS and architecture if it reproduces consistently
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the thread is live before call injection:
// state, err := p.ProcessState(); ensure thread ID still exists in p.ThreadList()

Try / catch

err := dbg.CallFunction(scope, fn, args)
if err != nil && strings.Contains(err.Error(), "could not restore PC") {
	// restart session; PC could not be written back
}

Prevention

When it happens

Trigger: funcCallStep in debugCallRegRestoreRegisters calls setPC(thread, pc) and the underlying SetPC/SetReg write fails (dead thread, ptrace error, invalid PC value).

Common situations: Target process terminated during the call; hardware/architecture restrictions on writing PC; gdbserial target that disconnected.

Related errors


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