Index | Thread | Search

From:
Claudio Jeker <cjeker@diehard.n-r-g.com>
Subject:
Re: devel/gdb: implement thread_alive
To:
Kurt Miller <kurt@intricatesoftware.com>
Cc:
jca@wxcvbn.org, pascal@stumpf.co, ports@openbsd.org, tech@openbsd.org
Date:
Mon, 8 Dec 2025 09:05:32 +0100

Download raw body.

Thread
On Mon, Dec 08, 2025 at 12:43:56AM +0000, Kurt Miller wrote:
> 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?

I'm not super stocked about using SIDL or SDEAD outside of the kernel.
My problem with this is that I think we need to move away from a thread
state in the long run and so this would break if that is done.

Also using sysctl to grab all processes is pulling in an extra dependency
which has a similar issue but is less of a concern.
 
> 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;
>   
> 

-- 
:wq Claudio