go-delve/delve · error

could not restore LR: %v

Error message

could not restore LR: %v

What it means

On arm64/ppc64le/loong64, Delve's call injection protocol stashes the original Link Register value on the stack and must restore it once the injected call completes. callInjectionComplete2 reads the saved LR from the thread's stack via readUintRaw at regs.SP(). If that memory read fails, fncall.err is set to "could not restore LR: %v" and the call injection aborts, leaving the target's register/stack state at risk of corruption.

Source

Thrown at pkg/proc/fncall.go:979

		// possible is to ignore it and hope it didn't matter.
		stack.callInjectionContinue = true
		fncallLog("unknown value of protocol register %#x", regval)
	}

	return false
}

func callInjectionComplete2(callScope *EvalScope, bi *BinaryInfo, fncall *functionCallState, regs Registers, thread Thread) {
	// Store the stack span of the currently running goroutine (which in Go >=
	// 1.15 might be different from the original injection goroutine) so that
	// later on we can use it to perform the escapeCheck
	if threadg, _ := GetG(thread); threadg != nil {
		callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
	}
	if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" || bi.Arch.Name == "loong64" {
		oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
		if err != nil {
			fncall.err = fmt.Errorf("could not restore LR: %v", err)
			return
		}
		if err = setLR(thread, oldlr); err != nil {
			fncall.err = fmt.Errorf("could not restore LR: %v", err)
			return
		}
	}
}

func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTarget, stack *evalStack, thread Thread) {
	fncall := stack.fncallPeek()
	if !fncall.hasDebugPinner && (fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0) {
		stack.err = funcCallEvalFuncExpr(scope, stack, fncall)
		if stack.err != nil {
			return
		}
	}
	stack.pop() // target function, consumed by funcCallEvalFuncExpr either above or in evalop.CallInjectionStart

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Check the wrapped cause: if it is a memory-read error, verify the target process is still alive (threads command).
  2. Restart or re-attach to the target process; this error usually indicates lost protocol state that cannot be repaired in-session.
  3. Avoid injecting calls into functions that panic or exit the process; guard expressions before calling.
  4. Update Delve to the latest version to get protocol fixes for your architecture and Go version.
  5. Retry on a stable breakpoint where SP matches the goroutine's current stack.
Defensive patterns

Strategy: retry

Validate before calling

// before injecting, confirm thread is alive and stack is mapped
thr, err := proc.FindThread(dbg, tid)
if err != nil { return err } // thread gone -> do not inject
_, err = thr.Registers()
if err != nil { return err } // cannot even read regs -> skip injection

Try / catch

res, err := dbg.StepInstruction / CallFunction(...)
if err != nil && strings.Contains(err.Error(), "could not restore LR:") {
    // saved LR unreadable or register write failed: session state is lost
    // only recovery is re-attach/restart; surface to user, do not resume
}

Prevention

When it happens

Trigger: Completing a function call injection on arm64, ppc64le, or loong64 when readUintRaw(thread.ProcessMemory(), regs.SP(), ptrSize) fails: the thread exited, SP points to unmapped/corrupt memory, or the process died between the call finishing and the completion step.

Common situations: Target process crashes or exits during the injected call; debugging a core dump or remote session where memory at SP is not readable; Go runtime moved the goroutine to a different stack mid-call (stack growth) so the saved LR is no longer at the recorded SP.

Related errors


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