go-delve/delve · error
ptraceGetLwpInfo err %s %d
Error message
ptraceGetLwpInfo err %s %d
What it means
After a successful wait on FreeBSD, trapWaitInternal asks the kernel for ptrace(PT_LWPINFO) details of the stopped light-weight process (thread). When PT_LWPINFO fails, delve wraps the error with the pid being debugged. This means the stop event was received but the thread metadata could not be fetched, so the stop cannot be fully processed.
Source
Thrown at pkg/proc/native/proc_freebsd.go:370
if status.Killed() {
// "Killed" status may arrive as a result of a Process.Kill() of some other process in
// the system performed by the same tracer (e.g. in the previous test)
continue
}
if status.Exited() {
dbp.postExit()
return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
}
if status.Signaled() {
// Killed by a signal
dbp.postExit()
return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
}
var info sys.PtraceLwpInfoStruct
dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) })
if err != nil {
return nil, fmt.Errorf("ptraceGetLwpInfo err %s %d", err, pid)
}
tid := int(info.Lwpid)
pl_flags := int(info.Flags)
th, ok := dbp.threads[tid]
if ok {
th.Status = (*waitStatus)(status)
}
if status.StopSignal() == sys.SIGTRAP {
if pl_flags&_PL_FLAG_EXITED != 0 {
delete(dbp.threads, tid)
dbp.execPtraceFunc(func() { err = ptraceCont(tid, 0) })
if err != nil {
return nil, err
}
continue
} else if pl_flags&_PL_FLAG_BORN != 0 {
th, err = dbp.addThread(int(tid), false)View on GitHub (pinned to a23773e6c3)
Solutions
- Retry the Continue/step; the error is often a benign race with a dying thread.
- Ensure the debugger runs as the same user as the target (or root) and that ptrace is permitted (kern.securelevel, MAC policy).
- Reduce thread churn in the debugged program if reproducible, and capture the underlying errno to confirm ESRCH.
- Update FreeBSD/delve if PT_LWPINFO persistently fails for valid stops.
Example fix
// before
err := dbp.Continue() // fails: ptraceGetLwpInfo err ESRCH 1234
// after
err := dbp.Continue()
if err != nil && strings.Contains(err.Error(), "ptraceGetLwpInfo err") {
// a thread died during the stop; re-list threads and retry once
err = dbp.Continue()
} Defensive patterns
Strategy: retry
Validate before calling
// confirm ptrace permission and target liveness before debugging on FreeBSD
func canTrace(pid int) error {
if _, err := os.Stat(fmt.Sprintf("/proc/%d/status", pid)); err == nil {
return nil
}
return exec.Command("ps", "-p", strconv.Itoa(pid)).Run()
} Try / catch
err := dbp.Continue()
if err != nil && strings.Contains(err.Error(), "ptraceGetLwpInfo err") {
// thread died between wait and PT_LWPINFO: re-list threads and retry
err = dbp.Continue()
} Prevention
- Run as same user or root so PT_LWPINFO is permitted.
- Treat ESRCH-ish failures as benign thread-death races and retry.
- Avoid attaching while the target is in heavy thread create/destroy loops.
- Log the underlying errno to distinguish permission vs race causes.
When it happens
Trigger: ptraceGetLwpInfo(wpid) returns an error right after wait reports a stop — typically because the LWP exited between the wait and the PT_LWPINFO call (ESRCH), or because wpid is no longer traceable by this process.
Common situations: Racy thread death during heavy multithreaded debugging; attaching while threads are being created/destroyed; running as a different user than the target (ptrace permission checks); hardened kernels/JS Chebyshev with restricted ptrace scope.
Related errors
- error while waiting after adding process: %d %s
- could not continue new thread %d %s
- procstat_getprocs returned no processes
- waiting for target execve failed: %s
- wait err %s %d
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/a6d4e483dd73f967.
Report an issue: GitHub.