{"record":{"id":"22c9c12223f45ae9","repo":"go-delve/delve","slug":"could-not-restore-sp-v","errorCode":null,"errorMessage":"could not restore SP: %v","messagePattern":"could not restore SP: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/proc/fncall.go","lineNumber":869,"sourceCode":"\t\t}\n\t\terrvar.Name = \"err\"\n\t\tfncall.err = fmt.Errorf(\"%v\", constant.StringVal(errvar.Value))\n\n\tcase debugCallRegCompleteCall: // 0\n\t\tp.fncallForG[callScope.g.ID].startThreadID = 0\n\n\tcase debugCallRegRestoreRegisters: // 16\n\t\t// runtime requests that we restore the registers (all except pc and sp),\n\t\t// this is also the last step of the function call protocol.\n\t\tpc, sp := regs.PC(), regs.SP()\n\t\tif err := thread.RestoreRegisters(fncall.savedRegs); err != nil {\n\t\t\tfncall.err = fmt.Errorf(\"could not restore registers: %v\", err)\n\t\t}\n\t\tif err := setPC(thread, pc); err != nil {\n\t\t\tfncall.err = fmt.Errorf(\"could not restore PC: %v\", err)\n\t\t}\n\t\tif err := setSP(thread, sp); err != nil {\n\t\t\tfncall.err = fmt.Errorf(\"could not restore SP: %v\", err)\n\t\t}\n\t\tfncallLog(\"stepping thread %d\", thread.ThreadID())\n\t\tif err := stepInstructionOut(callScope.callCtx.grp, p, thread, fncall.debugCallName, fncall.debugCallName); err != nil {\n\t\t\tfncall.err = fmt.Errorf(\"could not step out of %s: %v\", fncall.debugCallName, err)\n\t\t}\n\t\tif bi.Arch.Name == \"amd64\" {\n\t\t\t// The tail of debugCallV2 corrupts the state of RFLAGS, we must restore\n\t\t\t// it one extra time after stepping out of it.\n\t\t\t// See https://github.com/go-delve/delve/issues/2985 and\n\t\t\t// TestCallInjectionFlagCorruption\n\t\t\trflags := bi.Arch.RegistersToDwarfRegisters(0, fncall.savedRegs).Uint64Val(regnum.AMD64_Rflags)\n\t\t\terr := thread.SetReg(regnum.AMD64_Rflags, op.DwarfRegisterFromUint64(rflags))\n\t\t\tif err != nil {\n\t\t\t\tfncall.err = fmt.Errorf(\"could not restore RFLAGS register: %v\", err)\n\t\t\t}\n\t\t}\n\t\treturn true\n","sourceCodeStart":851,"sourceCodeEnd":887,"githubUrl":"https://github.com/go-delve/delve/blob/a23773e6c31361e43246bc43a424ee009679b174/pkg/proc/fncall.go#L851-L887","documentation":"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.","triggerScenarios":"funcCallStep in the restore-registers phase calls setSP(thread, sp) and the backend write fails.","commonSituations":"Process crash during the injected call; ptrace failures (permissions, seccomp); remote session dropped; stack pointer computed against a stale register snapshot.","solutions":["Confirm the process is alive and re-attach if needed","Retry the 'call' command in a fresh stop","Check ptrace permissions (e.g. /proc/sys/kernel/yama/ptrace_scope) for native debugging","Restart the debug session — corrupted SP is unsafe to continue from"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Check ptrace works on the host before debugging:\n// cat /proc/sys/kernel/yama/ptrace_scope  (0 or with CAP_SYS_PTRACE is safest)","typeGuard":null,"tryCatchPattern":"err := dbg.CallFunction(scope, fn, args)\nif err != nil && strings.Contains(err.Error(), \"could not restore SP\") {\n\t// SP corrupted: detach and restart the session\n}\n","preventionTips":["Ensure ptrace is permitted (yama scope, container privileges)","Do not let the target crash during call injection","Re-attach on remote-connection drops","Restart the session after any SP-restore failure"],"tags":["go","fncall","registers","sp"],"backgroundTag":"register-restore-failed","analyzedSha":"a23773e6c31361e43246bc43a424ee009679b174","analyzedAt":"2026-08-31T15:12:45.221Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}