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
- Verify the debuggee is alive; restart the session if it died
- Retry the call injection after re-attaching
- Use a native backend session if the failure is backend-specific
- 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
- Verify the target stays alive during injected calls
- Re-attach after any backend communication error
- Prefer native backend for reliable register writes
- Restart the session on PC-restore failure instead of continuing
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
- could not restore registers: %v
- could not restore SP: %v
- could not restore RFLAGS register: %v
- can not change register values of core process
- read out of bounds
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/de19e0a907b40a35.
Report an issue: GitHub.