go-delve/delve · error

could not get registers

Error message

could not get registers

What it means

registers() on darwin/amd64 reads GPRs with the Mach call get_registers(thread_act, x86_thread_state64_t). Any kern_return other than KERN_SUCCESS produces the opaque 'could not get registers' error. Typically the thread act is no longer valid or the task port lost rights.

Source

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

	case x86asm.R12:
		return r.r12, nil
	case x86asm.R13:
		return r.r13, nil
	case x86asm.R14:
		return r.r14, nil
	case x86asm.R15:
		return r.r15, nil
	}

	return 0, proc.ErrUnknownRegister
}

func registers(thread *nativeThread) (proc.Registers, error) {
	var state C.x86_thread_state64_t
	var identity C.thread_identifier_info_data_t
	kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state)
	if kret != C.KERN_SUCCESS {
		return nil, fmt.Errorf("could not get registers")
	}
	kret = C.get_identity(C.mach_port_name_t(thread.os.threadAct), &identity)
	if kret != C.KERN_SUCCESS {
		return nil, fmt.Errorf("could not get thread identity information")
	}
	/*
		thread_identifier_info::thread_handle contains the base of the
		thread-specific data area, which on x86 and x86_64 is the thread’s base
		address of the %gs segment. 10.9.2 xnu-2422.90.20/osfmk/kern/thread.c
		thread_info_internal() gets the value from
		machine_thread::cthread_self, which is the same value used to set the
		%gs base in xnu-2422.90.20/osfmk/i386/pcb_native.c
		act_machine_switch_pcb().
		--
		comment copied from chromium's crashpad
		https://chromium.googlesource.com/crashpad/crashpad/+/master/snapshot/mac/process_reader.cc
	*/
	regs := &Regs{

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Refresh the thread list and skip/evict threads whose act is invalid (KERN_TERMINATED)
  2. Ensure delve can access the task port (same user/root, and not blocked by SIP/hardened runtime restrictions)
  3. Re-attach to the process if its task port rights were revoked (e.g. after exec)
  4. Surface the kern_return value in the error to distinguish thread-dead from permission problems

Example fix

// before
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state)
if kret != C.KERN_SUCCESS {
	return nil, fmt.Errorf("could not get registers")
}
// after
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state)
if kret != C.KERN_SUCCESS {
	return nil, fmt.Errorf("could not get registers for thread %d: kern_return=%d", thread.ID, kret)
}
Defensive patterns

Strategy: retry

Validate before calling

// before reading registers on darwin
if !isThreadAlive(dbp, thread.ID) {
	return nil, fmt.Errorf("thread %d no longer valid", thread.ID)
}

Type guard

func isGetRegsErr(err error) bool {
	return err != nil && strings.Contains(err.Error(), "could not get registers")
}

Try / catch

regs, err := registers(thread)
if isGetRegsErr(err) {
	// refresh thread list once, then retry
	dbp.refreshThreads()
	regs, err = registers(thread)
}
return regs, err

Prevention

When it happens

Trigger: Listing/reading registers for a thread that terminated between thread enumeration and the Mach call, after the target execed (stale acts), or when task-for-pid/thread privileges were denied by macOS hardening.

Common situations: Debuggee exit racing a 'goroutines'/'registers' command; attaching to protected processes under SIP/hardened runtime; stale thread cache after fork/exec.

Related errors


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