From: Claudio Jeker Subject: Re: devel/gdb: implement thread_alive To: Kurt Miller Cc: jca@wxcvbn.org, pascal@stumpf.co, ports@openbsd.org, tech@openbsd.org Date: Mon, 8 Dec 2025 09:05:32 +0100 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 > ++#include > #include > +#include > #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 > + 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 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 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