Index | Thread | Search

From:
Kurt Miller <kurt@intricatesoftware.com>
Subject:
devel/gdb: implement thread_alive
To:
jca@wxcvbn.org, pascal@stumpf.co, ports@openbsd.org, tech@openbsd.org
Date:
Mon, 8 Dec 2025 00:43:56 +0000

Download raw body.

Thread
I've been trying to debug an issue with the jvm but whenever a
thread exits that has previously been seen by gdb, I can't get
a full list of threads with thread info. I get this error instead:
"Couldn't get registers: No such process." For example:

(gdb) info threads
  Id   Target Id                                                Frame 
  1    thread 388840 of process 15667 ""                        futex () at /tmp/-:3
  2    thread 563387 of process 15667 ""                        futex () at /tmp/-:3
  3    thread 536589 of process 15667                           Couldn't get registers: No such process.

This is because we don't provide a function override for
thread_alive. This diff adds it (again copying NetBSD's
implementation) and is enough to fix the above problem.

However, this diff goes a step further and moves aways from using 
ptrace PT_GET_THREAD_FIRST/NEXT for finding threads and solely
utilizes sysctl KERN_PROC_PID | KERN_PROC_SHOW_THREADS for finding
threads. The advantage of this is that we can filter out threads
with SIDL or SDEAD states from gdb's view of the process's threads.

This should prevent a thread from being added in one of those
states and then removed when thread_alive returns false for it.

I've tested this on aarch64 with the jvm with threads being created
and exiting while stopping periodically to check on state. I've
checked both starting the process in gdb or attaching to the process.
I also double checked a single threaded program still works in gdb.

There are other ways to approach fixing this, like considering
threads in SIDL or SDEAD states alive - I'm not sure if that would
fix "Couldn't get registers: No such process." though. Another
aproach would be to have the kernel skip threads with
SIDL or SDEAD with ptrace PT_GET_THREAD_FIRST/NEXT.

Thoughts on my current approach or okays?

Index: Makefile
===================================================================
RCS file: /cvs/ports/devel/gdb/Makefile,v
diff -u -p -u -r1.98 Makefile
--- Makefile	4 Dec 2025 18:28:32 -0000	1.98
+++ Makefile	8 Dec 2025 00:11:51 -0000
@@ -2,7 +2,7 @@ COMMENT=	GNU debugger
 CATEGORIES=	devel
 
 DISTNAME=	gdb-16.3
-REVISION=	0
+REVISION=	1
 
 HOMEPAGE=	https://www.gnu.org/software/gdb/
 
Index: patches/patch-gdb_obsd-nat_c
===================================================================
RCS file: /cvs/ports/devel/gdb/patches/patch-gdb_obsd-nat_c,v
diff -u -p -u -r1.1 patch-gdb_obsd-nat_c
--- patches/patch-gdb_obsd-nat_c	4 Dec 2025 18:28:32 -0000	1.1
+++ patches/patch-gdb_obsd-nat_c	8 Dec 2025 00:11:51 -0000
@@ -1,11 +1,16 @@
-Add support for thread_name.
+Add support for thread_name and thread_alive.
+Use sysctl KERN_PROC_PID | KERN_PROC_SHOW_THREADS instead of 
+ptrace(PT_GET_THREAD_FIRST/NEXT) for adding threads so that we 
+can filter out threads with SIDL or SDEAD states.
 
 Index: gdb/obsd-nat.c
 --- gdb/obsd-nat.c.orig
 +++ gdb/obsd-nat.c
-@@ -23,11 +23,13 @@
+@@ -22,12 +22,15 @@
+ #include "target.h"
  
  #include <sys/types.h>
++#include <sys/proc.h>
  #include <sys/ptrace.h>
 +#include <sys/sysctl.h>
  #include "gdbsupport/gdb_wait.h"
@@ -17,12 +22,12 @@ Index: gdb/obsd-nat.c
  
  /* OpenBSD 5.2 and later include rthreads which uses a thread model
     that maps userland threads directly onto kernel threads in a 1:1
-@@ -183,4 +185,69 @@ int
- obsd_nat_target::remove_fork_catchpoint (int pid)
- {
-   return 0;
-+}
-+
+@@ -42,34 +45,111 @@ obsd_nat_target::pid_to_str (ptid_t ptid)
+   return normal_pid_to_str (ptid);
+ }
+ 
+-void
+-obsd_nat_target::update_thread_list ()
 +/* Generic thread lister within a specified PID.  The CALLBACK
 +   parameters is a C++ function that is called for each detected thread.
 +   When the CALLBACK function returns true, the iteration is interrupted.
@@ -34,16 +39,22 @@ Index: gdb/obsd-nat.c
 +obsd_thread_lister (const pid_t pid,
 +		      gdb::function_view<bool (const struct kinfo_proc *)>
 +		      callback)
-+{
+ {
+-  pid_t pid = inferior_ptid.pid ();
+-  struct ptrace_thread_state pts;
 +  int mib[6] = {CTL_KERN, KERN_PROC, KERN_PROC_PID | KERN_PROC_SHOW_THREADS,
 +      pid, sizeof(struct kinfo_proc), 0};
 +  size_t size;
-+
+ 
+-  prune_threads ();
 +  if (sysctl (mib, ARRAY_SIZE (mib), NULL, &size, NULL, 0) == -1 || size == 0)
 +    perror_with_name (("sysctl"));
-+
+ 
+-  if (ptrace (PT_GET_THREAD_FIRST, pid, (caddr_t)&pts, sizeof pts) == -1)
+-    perror_with_name (("ptrace"));
 +  mib[5] = size / sizeof (struct kinfo_proc);
-+
+ 
+-  while (pts.pts_tid != -1)
 +  gdb::unique_xmalloc_ptr<struct kinfo_proc[]> ki
 +    ((struct kinfo_proc *) xcalloc (mib[5], sizeof (struct kinfo_proc)));
 +
@@ -52,13 +63,124 @@ Index: gdb/obsd-nat.c
 +    perror_with_name (("sysctl"));
 +
 +  for (size_t i = 0; i < size / sizeof (struct kinfo_proc); i++)
-+    {
+     {
+-      ptid_t ptid = ptid_t (pid, pts.pts_tid, 0);
 +      struct kinfo_proc *l = &ki[i];
+ 
+-      if (!in_thread_list (this, ptid))
+-	{
+-	  if (inferior_ptid.lwp () == 0)
+-	    thread_change_ptid (this, inferior_ptid, ptid);
+-	  else
+-	    add_thread (this, ptid);
+-	}
++      /* Return true if the specified thread is alive.  */
++      auto thr_alive
++	= [] (struct kinfo_proc *thr_proc)
++	  {
++	    switch (thr_proc->p_stat)
++	      {
++	      case SSLEEP:
++	      case SRUN:
++	      case SONPROC:
++	      case SSTOP:
++		return true;
++	      default:
++		return false;
++	      }
++	  };
+ 
+-      if (ptrace (PT_GET_THREAD_NEXT, pid, (caddr_t)&pts, sizeof pts) == -1)
+-	perror_with_name (("ptrace"));
++      /* Ignore p_tid -1 which is the kinfo_proc for the process
++         also ignore embryonic or demised threads.  */
++      if (l->p_tid == -1 || !thr_alive (l))
++	continue;
++
 +      if (callback (l))
 +	return true;
-+    }
+     }
 +
 +  return false;
+ }
+ 
++/* Fuction to support executing callback for each alive thread */
++
++static void
++for_each_thread (pid_t pid, gdb::function_view<void (ptid_t)> callback)
++{
++  auto fn
++    = [=, &callback] (const struct kinfo_proc *ki)
++      {
++	ptid_t ptid = ptid_t (pid, ki->p_tid, 0);
++	callback (ptid);
++	return false;
++      };
++
++  obsd_thread_lister (pid, fn);
++}
++
++/* Implement the "post_attach" target_ops method.  */
++
++static void
++obsd_add_threads (obsd_nat_target *target, pid_t pid)
++{
++  auto fn
++    = [&target] (ptid_t ptid)
++      {
++	if (!in_thread_list (target, ptid))
++	  {
++	    if (inferior_ptid.lwp () == 0)
++	      thread_change_ptid (target, inferior_ptid, ptid);
++	    else
++	      add_thread (target, ptid);
++	  }
++      };
++
++  for_each_thread (pid, fn);
++}
++
++void
++obsd_nat_target::update_thread_list ()
++{
++  pid_t pid = inferior_ptid.pid ();
++
++  prune_threads ();
++  obsd_add_threads (this, pid);
++}
++
+ /* Enable additional event reporting on a new or existing process.  */
+ 
+ static void
+@@ -143,6 +223,7 @@ void
+ obsd_nat_target::post_attach (int pid)
+ {
+   obsd_enable_proc_events (pid);
++  obsd_add_threads (this, pid);
+ }
+ 
+ /* Implement the virtual inf_ptrace_target::post_startup_inferior method.  */
+@@ -183,4 +264,48 @@ int
+ obsd_nat_target::remove_fork_catchpoint (int pid)
+ {
+   return 0;
++}
++
++/* See obsd-nat.h.  */
++
++bool
++obsd_nat_target::thread_alive (ptid_t ptid)
++{
++  pid_t pid = ptid.pid ();
++  ptid_t::lwp_type tid = ptid.lwp ();
++
++  auto fn
++    = [=] (const struct kinfo_proc *ki)
++      {
++	return ki->p_tid == tid;
++      };
++
++  return obsd_thread_lister (pid, fn);
 +}
 +
 +/* See obsd-nat.h.  */
Index: patches/patch-gdb_obsd-nat_h
===================================================================
RCS file: /cvs/ports/devel/gdb/patches/patch-gdb_obsd-nat_h,v
diff -u -p -u -r1.1 patch-gdb_obsd-nat_h
--- patches/patch-gdb_obsd-nat_h	4 Dec 2025 18:28:32 -0000	1.1
+++ patches/patch-gdb_obsd-nat_h	8 Dec 2025 00:11:51 -0000
@@ -1,12 +1,13 @@
-Add support for thread_name.
+Add support for thread_name and thread_alive.
 
 Index: gdb/obsd-nat.h
 --- gdb/obsd-nat.h.orig
 +++ gdb/obsd-nat.h
-@@ -27,6 +27,7 @@ class obsd_nat_target : public inf_ptrace_target
+@@ -27,6 +27,8 @@ class obsd_nat_target : public inf_ptrace_target
    /* Override some methods to support threads.  */
    std::string pid_to_str (ptid_t) override;
    void update_thread_list () override;
++  bool thread_alive (ptid_t ptid) override;
 +  const char *thread_name (struct thread_info *thr) override;
    ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;