go-delve/delve · error

could not get precheck error reason: %v

Error message

could not get precheck error reason: %v

What it means

During the debugCallV2 protocol, the Go runtime writes a human-readable reason string on the stack when it must abort an injected call. Delve failed to read that stack variable (of type 'string'), so it wraps the read failure with this message instead of the underlying runtime reason. The actual function-call error is then hidden behind this diagnostic.

Source

Thrown at pkg/proc/fncall.go:849

				fnname = loc.Fn.Name
			}
		}
		fncallLog("function call interrupt gid=%d (original) thread=%d regval=%#x (PC=%#x in %s %s:%d)", callScope.g.ID, thread.ThreadID(), regval, pc, fnname, loc.File, loc.Line)
	}

	switch regval {
	case debugCallRegPrecheckFailed: // 8
		stack.callInjectionContinue = true
		archoff := uint64(0)
		if bi.Arch.Name == "arm64" || bi.Arch.Name == "loong64" {
			archoff = 8
		} else if bi.Arch.Name == "ppc64le" {
			archoff = 40
		}
		// get error from top of the stack and return it to user
		errvar, err := readStackVariable(p, thread, regs, archoff, "string", LoadFullValue())
		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)
		}

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Retry the 'call' command — transient memory-read failures often succeed on a second attempt
  2. Check the target process is alive and not already crashed
  3. Ensure the binary has full debug info (not stripped)
  4. Upgrade Delve and/or Go: protocol offsets changed between versions
Defensive patterns

Strategy: retry

Validate before calling

// Before 'call', confirm the target is stoppable and readable:
// dlv: state, err := dbg.ProcessState(); check state.Exited == false
// and that the binary is not stripped: file <binary> | grep 'not stripped'

Try / catch

err := dbg.CallFunction(scope, fn, args)
if err != nil && strings.Contains(err.Error(), "could not get precheck error reason") {
	// retry once after confirming process alive
}

Prevention

When it happens

Trigger: funcCallStep receives debugCallRegCheckPrecondition/other failure status from the runtime, and readStackVariable fails while reading the 'string' reason variable from the top of the stack (bad stack pointer, unreadable memory, missing registers).

Common situations: Attaching to a stripped or foreign binary whose DWARF for the debug-call stubs is missing; process memory unreadable because the target crashed mid-protocol; architecture offset mismatches (archoff) on ppc64le/arm64/amd64.

Related errors


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