go-delve/delve · error

could not set pc

Error message

could not set pc

What it means

On darwin/amd64, nativeThread.setPC calls the Mach wrapper set_pc on the thread act; any kern_return other than KERN_SUCCESS is collapsed into the opaque message 'could not set pc'. The kernel refused to change the instruction pointer of the thread.

Solutions

  1. Check the process is still alive before attempting setPC; treat thread-death races as expected failures
  2. Re-attach if task/port rights were invalidated (e.g. after the target execed)
  3. Run delve with proper entitlements/permissions on macOS (SIP constraints, developer mode)
  4. Log the kern_return code from set_pc to distinguish KERN_INVALID_ARGUMENT vs KERN_TERMINATED for accurate handling

Example fix

// before
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
if kret != C.KERN_SUCCESS {
	return fmt.Errorf("could not set pc")
}
// after
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
if kret != C.KERN_SUCCESS {
	return fmt.Errorf("could not set pc to 0x%x: kern_return=%d", pc, kret)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before setPC on darwin
if !procAlive(dbp) || !isThreadAlive(dbp, thread.ID) {
	return fmt.Errorf("thread gone; cannot set pc")
}

Type guard

func isSetPCErr(err error) bool {
	return err != nil && strings.Contains(err.Error(), "could not set pc")
}

Try / catch

err := thread.setPC(pc)
if isSetPCErr(err) {
	// likely thread/process died mid-step; re-sync debugger state
	dbp.reattachOrDrop()
	return err
}

Prevention

When it happens

Trigger: Setting a new PC during step/restart (e.g. after a breakpoint hit, 'continue' from a breakpoint skipping, or reverse-step machinery) when the thread act is invalid — the thread/process terminated — or Mach privileges were lost.

Common situations: Race where the debuggee exits while delve is relocating the PC off a breakpoint; macOS hardening (SIP/hardened runtime) interfering with task-for-pid; stale thread act after exec.

Related errors


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

Appendix: source

Thrown at pkg/proc/native/registers_darwin_amd64.go:122

	return 0
}

// TLS returns the value of the register
// that contains the location of the thread
// local storage segment.
func (r *Regs) TLS() uint64 {
	return r.gsBase
}

func (r *Regs) GAddr() (uint64, bool) {
	return 0, false
}

// SetPC sets the RIP register to the value specified by `pc`.
func (thread *nativeThread) setPC(pc uint64) error {
	kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
	if kret != C.KERN_SUCCESS {
		return fmt.Errorf("could not set pc")
	}
	return nil
}

// SetReg changes the value of the specified register.
func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
	if regNum != regnum.AMD64_Rip {
		return fmt.Errorf("changing register %d not implemented", regNum)
	}
	return thread.setPC(reg.Uint64Val)
}

func (r *Regs) Get(n int) (uint64, error) {
	reg := x86asm.Reg(n)
	const (
		mask8  = 0x000f
		mask16 = 0x00ff
		mask32 = 0xffff

View on GitHub (pinned to a23773e6c3)