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
- Refresh the thread list and skip/evict threads whose act is invalid (KERN_TERMINATED)
- Ensure delve can access the task port (same user/root, and not blocked by SIP/hardened runtime restrictions)
- Re-attach to the process if its task port rights were revoked (e.g. after exec)
- 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
- Evict threads whose Mach act returns KERN_TERMINATED and refresh the thread cache
- Handle exec/fork by re-attaching instead of reusing old thread acts
- Verify macOS permissions (SIP, task-for-pid) when failures are consistent rather than transient
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
- could not set pc
- could not attach to %d
- changing register %d not implemented
- could not resume task
- error while waiting for task
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/a9cda590082ed43f.
Report an issue: GitHub.