* [PATCH v1 0/2] gdb: fix for bug 31207
@ 2026-07-28 14:33 Matthieu Longo
2026-07-28 14:33 ` [PATCH v1 1/2] gdb: rename any_thread_of_inferior to any_non_exited_thread_of_inferior Matthieu Longo
2026-07-28 14:33 ` [PATCH v1 2/2] gdb: rely on the first non-exited thread TPID when reading Linux procfs files Matthieu Longo
0 siblings, 2 replies; 3+ messages in thread
From: Matthieu Longo @ 2026-07-28 14:33 UTC (permalink / raw)
To: gdb-patches
Cc: Luis Machado, Luis Machado, Simon Marchi, Thiago Jung Bauermann,
Matthieu Longo
This patch series contains:
- a refactoring suggested by Simon Marchi <simark@simark.ca> in [1] patch 02/10.
- a fix for https://sourceware.org/bugzilla/show_bug.cgi?id=31207.
Those 2 patches were extracted from a previous patch series [1], in order to give more visibility and focus on that fix.
Following up the suggestions of Simon,
1. 'info proc mappings' will print before the thread-group leader exits:
process 814
and after the thread-group leader exited:
process 814 [Note: information where gathered from LWP 816 as the thread-group leader (LWP=814) already exited.]
2. the code selecting the PTID inside was moved to get_process_reference_ptid().
3. a new test in gdb.threads checks that the header is emitted correctly before and after the exit of the thread-group leader.
Regards,
Matthieu
[1]: https://inbox.sourceware.org/gdb-patches/20260707154900.94542-1-matthieu.longo@arm.com/
Matthieu Longo (2):
gdb: rename any_thread_of_inferior to any_non_exited_thread_of_inferior
gdb: rely on the first non-exited thread TPID when reading Linux procfs files
gdb/gdbthread.h | 4 +-
gdb/inferior.c | 18 +++-
gdb/inferior.h | 11 +++
gdb/linux-fork.c | 10 +--
gdb/linux-tdep.c | 90 +++++++++++++------
gdb/mi/mi-main.c | 4 +-
gdb/remote.c | 4 +-
...access-procfs-while-thread-leader-exited.c | 48 ++++++++++
...cess-procfs-while-thread-leader-exited.exp | 78 ++++++++++++++++
gdb/thread.c | 2 +-
gdb/top.c | 2 +-
11 files changed, 227 insertions(+), 44 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.c
create mode 100644 gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.exp
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v1 1/2] gdb: rename any_thread_of_inferior to any_non_exited_thread_of_inferior
2026-07-28 14:33 [PATCH v1 0/2] gdb: fix for bug 31207 Matthieu Longo
@ 2026-07-28 14:33 ` Matthieu Longo
2026-07-28 14:33 ` [PATCH v1 2/2] gdb: rely on the first non-exited thread TPID when reading Linux procfs files Matthieu Longo
1 sibling, 0 replies; 3+ messages in thread
From: Matthieu Longo @ 2026-07-28 14:33 UTC (permalink / raw)
To: gdb-patches
Cc: Luis Machado, Luis Machado, Simon Marchi, Thiago Jung Bauermann,
Matthieu Longo
The name any_thread_of_inferior suggests that the function may return
any thread of the inferior. In practice, it only returns a non-exited
thread.
Rename it to any_non_exited_thread_of_inferior to better reflect its
behavior. This is a preparatory change for the following patch, which
adds another helper with similar semantics.
Suggested-by: Simon Marchi <simark@simark.ca>
[1]: https://inbox.sourceware.org/gdb-patches/590655c1-abed-4a16-b8cc
-762f1d8e6093@simark.ca/
---
gdb/gdbthread.h | 4 ++--
gdb/inferior.c | 6 +++---
gdb/linux-fork.c | 10 +++++-----
gdb/mi/mi-main.c | 4 ++--
gdb/remote.c | 4 ++--
gdb/thread.c | 2 +-
gdb/top.c | 2 +-
7 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 1bdbf621982..6588ae0c6a7 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -779,9 +779,9 @@ struct thread_info *find_thread_by_handle
/* Finds the first thread of the specified inferior. */
extern struct thread_info *first_thread_of_inferior (inferior *inf);
-/* Returns any thread of inferior INF, giving preference to the
+/* Returns any non-exited thread of inferior INF, giving preference to the
current thread. */
-extern struct thread_info *any_thread_of_inferior (inferior *inf);
+extern struct thread_info *any_non_exited_thread_of_inferior (inferior *inf);
/* Returns any non-exited thread of inferior INF, giving preference to
the current thread, and to not executing threads. */
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 1481f46cdd1..ac8c4354b71 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -663,7 +663,7 @@ detach_inferior_command (const char *args, int from_tty)
continue;
}
- thread_info *tp = any_thread_of_inferior (inf);
+ thread_info *tp = any_non_exited_thread_of_inferior (inf);
if (tp == NULL)
{
warning (_("Inferior ID %d has no threads."), num);
@@ -702,7 +702,7 @@ kill_inferior_command (const char *args, int from_tty)
continue;
}
- thread_info *tp = any_thread_of_inferior (inf);
+ thread_info *tp = any_non_exited_thread_of_inferior (inf);
if (tp == NULL)
{
warning (_("Inferior ID %d has no threads."), num);
@@ -771,7 +771,7 @@ inferior_command (const char *args, int from_tty)
{
if (inf != current_inferior ())
{
- thread_info *tp = any_thread_of_inferior (inf);
+ thread_info *tp = any_non_exited_thread_of_inferior (inf);
if (tp == NULL)
error (_("Inferior has no threads."));
diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c
index 92afa4dde6d..446d3284388 100644
--- a/gdb/linux-fork.c
+++ b/gdb/linux-fork.c
@@ -571,7 +571,7 @@ class scoped_switch_fork_info
if (oldinf != newinf)
{
- thread_info *tp = any_thread_of_inferior (newinf);
+ thread_info *tp = any_non_exited_thread_of_inferior (newinf);
switch_to_thread (tp);
m_oldinf = oldinf;
}
@@ -593,7 +593,7 @@ class scoped_switch_fork_info
remove_breakpoints ();
if (m_oldinf != nullptr)
{
- thread_info *tp = any_thread_of_inferior (m_oldinf);
+ thread_info *tp = any_non_exited_thread_of_inferior (m_oldinf);
switch_to_thread (tp);
}
fork_load_infrun_state (m_oldfp);
@@ -836,7 +836,7 @@ print_checkpoints (struct ui_out *uiout, inferior *req_inf, fork_info *req_fi)
if (req_fi != nullptr && req_fi != &fi)
continue;
- thread_info *t = any_thread_of_inferior (inf);
+ thread_info *t = any_non_exited_thread_of_inferior (inf);
bool is_current = fi.ptid.pid () == inf->pid;
ui_out_emit_tuple tuple_emitter (uiout, nullptr);
@@ -1063,7 +1063,7 @@ linux_fork_context (struct fork_info *newfp, int from_tty, inferior *newinf)
if (newinf != current_inferior ())
{
- thread_info *tp = any_thread_of_inferior (newinf);
+ thread_info *tp = any_non_exited_thread_of_inferior (newinf);
switch_to_thread (tp);
inferior_changed = true;
}
@@ -1100,7 +1100,7 @@ restart_command (const char *args, int from_tty)
/* Don't allow switching from a thread/fork that's running. */
inferior *curinf = current_inferior ();
if (curinf->pid != 0
- && any_thread_of_inferior (curinf)->state () == THREAD_RUNNING)
+ && any_non_exited_thread_of_inferior (curinf)->state () == THREAD_RUNNING)
error (_("Cannot execute this command while "
"the selected thread is running."));
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 73c64b82186..6a79805d75d 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -397,7 +397,7 @@ run_one_inferior (inferior *inf, bool start_p)
if (inf->pid != 0)
{
- thread_info *tp = any_thread_of_inferior (inf);
+ thread_info *tp = any_non_exited_thread_of_inferior (inf);
if (tp == NULL)
error (_("Inferior has no threads."));
@@ -1742,7 +1742,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *argv, int argc)
set_current_inferior (new_inferior);
if (new_inferior->pid != 0)
- tp = any_thread_of_inferior (new_inferior);
+ tp = any_non_exited_thread_of_inferior (new_inferior);
if (tp != NULL)
switch_to_thread (tp);
else
diff --git a/gdb/remote.c b/gdb/remote.c
index 194c4cbd9bb..30d0d171d1f 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -5761,7 +5761,7 @@ remote_target::start_remote_1 (int from_tty, int extended_p)
/* Need to switch to a specific thread, because remote_check_symbols
uses INFERIOR_PTID to set the general thread. */
scoped_restore_current_thread restore_thread;
- thread_info *thread = any_thread_of_inferior (inf);
+ thread_info *thread = any_non_exited_thread_of_inferior (inf);
switch_to_thread (thread);
this->remote_check_symbols ();
}
@@ -16175,7 +16175,7 @@ remote_objfile_changed_check_symbols (program_space *pspace)
called very early in the connection process, while the inferior is
being set up, before threads are added. Just skip it, start_remote_1
also calls remote_check_symbols when it's done setting things up. */
- thread_info *thread = any_thread_of_inferior (inf);
+ thread_info *thread = any_non_exited_thread_of_inferior (inf);
if (thread != nullptr)
{
scoped_restore_current_thread restore_thread;
diff --git a/gdb/thread.c b/gdb/thread.c
index 96c733e0629..a7d0b8a45dc 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -675,7 +675,7 @@ first_thread_of_inferior (inferior *inf)
}
thread_info *
-any_thread_of_inferior (inferior *inf)
+any_non_exited_thread_of_inferior (inferior *inf)
{
gdb_assert (inf->pid != 0);
diff --git a/gdb/top.c b/gdb/top.c
index 477c385da61..308b5552fd5 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -1862,7 +1862,7 @@ kill_or_detach (inferior *inf, int from_tty)
if (inf->pid == 0)
return;
- thread_info *thread = any_thread_of_inferior (inf);
+ thread_info *thread = any_non_exited_thread_of_inferior (inf);
if (thread != NULL)
{
switch_to_thread (thread);
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v1 2/2] gdb: rely on the first non-exited thread TPID when reading Linux procfs files
2026-07-28 14:33 [PATCH v1 0/2] gdb: fix for bug 31207 Matthieu Longo
2026-07-28 14:33 ` [PATCH v1 1/2] gdb: rename any_thread_of_inferior to any_non_exited_thread_of_inferior Matthieu Longo
@ 2026-07-28 14:33 ` Matthieu Longo
1 sibling, 0 replies; 3+ messages in thread
From: Matthieu Longo @ 2026-07-28 14:33 UTC (permalink / raw)
To: gdb-patches
Cc: Luis Machado, Luis Machado, Simon Marchi, Thiago Jung Bauermann,
Matthieu Longo
On Linux, /proc/<pid> is keyed by the thread-group leader PID. When
the leader has exited, some /proc/<pid>/... entries become unavailable
even though another thread is still alive. This can happen, for instance,
when the main thread calls pthread_exit() and another thread continues
the execution (existing test: gcore-stale-thread).
This causes GDB to fail to read procfs entries such as cmdline, cwd,
exe, maps, and smaps when it uses 'current_inferior ()->pid' after the
thread-group leader has exited.
Fix this by adding inferior::first_non_exited_thread(), which returns
the PTID of the first non-exited thread of the inferior. Use its LWP ID
when accessing procfs entries that only need a representative live LWP
belonging to the process.
This is a best-effort choice of a thread that is expected to still exist
in the target. Since GDB's view of the threads list may be stale, the
selected thread may already have exited by the time it is accessed.
Callers must therefore still be prepared to handle that case.
Update the following functions:
- linux_info_proc
- linux_process_address_in_memtag_page
- linux_find_memory_regions_full
- linux_fill_prpsinfo
- linux_address_in_shadow_stack_mem_range
to use the first non-exited thread's LWP ID instead of the inferior PID
when constructing procfs paths.
Add a new test in gdb.threads.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31207
---
gdb/inferior.c | 12 +++
gdb/inferior.h | 11 +++
gdb/linux-tdep.c | 90 +++++++++++++------
...access-procfs-while-thread-leader-exited.c | 48 ++++++++++
...cess-procfs-while-thread-leader-exited.exp | 78 ++++++++++++++++
5 files changed, 211 insertions(+), 28 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.c
create mode 100644 gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.exp
diff --git a/gdb/inferior.c b/gdb/inferior.c
index ac8c4354b71..b4a9de51e5f 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -250,6 +250,18 @@ inferior::find_thread (ptid_t ptid)
/* See inferior.h. */
+ptid_t
+inferior::first_non_exited_thread () const
+{
+ auto it = this->ptid_thread_map.cbegin ();
+ if (it != this->ptid_thread_map.cend ())
+ return it->first;
+ else
+ return ptid_t::make_null ();
+}
+
+/* See inferior.h. */
+
void
inferior::clear_thread_list ()
{
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 5c7a52319e7..91dc4e636fd 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -513,6 +513,17 @@ class inferior : public refcounted_object,
/* Find (non-exited) thread PTID of this inferior. */
thread_info *find_thread (ptid_t ptid);
+ /* Return the first non-exited thread of this inferior.
+
+ This is only a best-effort choice of a thread that is expected to still
+ exist in the target. A thread may have exited after GDB last updated its
+ thread list, or GDB/gdbserver may have observed the exit but not yet
+ propagated it through all layers. Therefore the returned thread is not
+ guaranteed to still be alive when it is later accessed. This avoids the
+ common case where the current thread has exited, but callers must still be
+ prepared for the selected thread to no longer exist. */
+ ptid_t first_non_exited_thread () const;
+
/* Delete all threads in the thread list, silently. */
void clear_thread_list ();
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 8c53ffd5e89..9bdcc55a0e1 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -457,6 +457,44 @@ linux_has_shared_address_space (struct gdbarch *gdbarch)
return linux_is_uclinux ();
}
+/* Return a PTID that identifies the current process and can be used to
+ access procfs safely.
+
+ The returned PTID is that of the thread-group leader whenever it is
+ still alive. If the leader has already exited, the PTID of the first
+ non-exited thread in the current inferior is returned instead.
+ This ensures that the returned PTID always refers to a live thread
+ whose procfs entries are present and populated. */
+static ptid_t
+get_process_reference_ptid (bool verbose = false)
+{
+ /* Get the current thread. */
+ thread_info *thr = inferior_thread ();
+
+ /* Construct the PTID of the thread-group leader. On Linux,
+ the leader's LWP ID is equal to the process ID. */
+ ptid_t leader_ptid (thr->ptid.pid (), thr->ptid.pid ());
+
+ /* Use the thread-group leader if it is still alive. Otherwise, use
+ the first thread that has not exited. */
+ thread_info *leader_thr
+ = current_inferior ()->find_thread (leader_ptid);
+ ptid_t ptid = (leader_thr == nullptr
+ ? current_inferior ()->first_non_exited_thread ()
+ : leader_ptid);
+
+ if (!verbose)
+ return ptid;
+
+ if (leader_thr != nullptr)
+ gdb_printf (_("process %d\n"), leader_ptid.pid ());
+ else
+ gdb_printf (_("process %d [Note: information where gathered from LWP %ld " \
+ "as the thread-group leader (LWP=%ld) already exited.]\n"),
+ ptid.pid (), ptid.lwp (), leader_ptid.lwp ());
+ return ptid;
+}
+
/* This is how we want PTIDs from core files to be printed. */
static std::string
@@ -842,9 +880,7 @@ static void
linux_info_proc (struct gdbarch *gdbarch, const char *args,
enum info_proc_what what)
{
- /* A long is used for pid instead of an int to avoid a loss of precision
- compiler warning from the output of strtoul. */
- long pid;
+ ptid_t ptid;
int cmdline_f = (what == IP_MINIMAL || what == IP_CMDLINE || what == IP_ALL);
int cwd_f = (what == IP_MINIMAL || what == IP_CWD || what == IP_ALL);
int environ_f = (what == IP_ENVIRON || what == IP_ALL);
@@ -859,7 +895,8 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
{
char *tem;
- pid = strtoul (args, &tem, 10);
+ long pid = strtoul (args, &tem, 10);
+ ptid = ptid_t (pid, pid);
args = tem;
}
else
@@ -869,17 +906,16 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
if (current_inferior ()->fake_pid_p)
error (_("Can't determine the current process's PID: you must name one."));
- pid = current_inferior ()->pid;
+ ptid = get_process_reference_ptid (true);
}
args = skip_spaces (args);
if (args && args[0])
error (_("Too many parameters: %s"), args);
- gdb_printf (_("process %ld\n"), pid);
if (cmdline_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/cmdline", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/cmdline", ptid.lwp ());
gdb_byte *buffer;
LONGEST len = target_fileio_read_alloc (nullptr, filename, &buffer);
@@ -901,7 +937,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
}
if (cwd_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/cwd", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/cwd", ptid.lwp ());
std::optional<std::string> contents
= target_fileio_readlink (NULL, filename, &target_errno);
if (contents.has_value ())
@@ -911,7 +947,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
}
if (environ_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/environ", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/environ", ptid.lwp ());
gdb_byte *buffer;
LONGEST len = target_fileio_read_alloc (nullptr, filename, &buffer);
@@ -935,7 +971,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
}
if (exe_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/exe", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/exe", ptid.lwp ());
std::optional<std::string> contents
= target_fileio_readlink (NULL, filename, &target_errno);
if (contents.has_value ())
@@ -945,7 +981,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
}
if (mappings_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/maps", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/maps", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> map
= target_fileio_read_stralloc (NULL, filename);
if (map != NULL)
@@ -990,7 +1026,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
}
if (status_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/status", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/status", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> status
= target_fileio_read_stralloc (NULL, filename);
if (status)
@@ -1000,7 +1036,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
}
if (stat_f)
{
- xsnprintf (filename, sizeof filename, "/proc/%ld/stat", pid);
+ xsnprintf (filename, sizeof filename, "/proc/%ld/stat", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> statstr
= target_fileio_read_stralloc (NULL, filename);
if (statstr)
@@ -1671,9 +1707,9 @@ linux_process_address_in_memtag_page (CORE_ADDR address)
if (current_inferior ()->fake_pid_p)
return false;
- pid_t pid = current_inferior ()->pid;
+ ptid_t ptid = get_process_reference_ptid ();
- std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+ std::string smaps_file = string_printf ("/proc/%ld/smaps", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> data
= target_fileio_read_stralloc (NULL, smaps_file.c_str ());
@@ -1731,7 +1767,6 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
linux_dump_mapping_p_ftype *should_dump_mapping_p,
linux_find_memory_region_ftype func)
{
- pid_t pid;
/* Default dump behavior of coredump_filter (0x33), according to
Documentation/filesystems/proc.txt from the Linux kernel
tree. */
@@ -1744,12 +1779,12 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
if (current_inferior ()->fake_pid_p)
return false;
- pid = current_inferior ()->pid;
+ ptid_t ptid = get_process_reference_ptid ();
if (use_coredump_filter)
{
std::string core_dump_filter_name
- = string_printf ("/proc/%d/coredump_filter", pid);
+ = string_printf ("/proc/%ld/coredump_filter", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> coredumpfilterdata
= target_fileio_read_stralloc (NULL, core_dump_filter_name.c_str ());
@@ -1763,7 +1798,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
}
}
- std::string maps_filename = string_printf ("/proc/%d/smaps", pid);
+ std::string maps_filename = string_printf ("/proc/%ld/smaps", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> data
= target_fileio_read_stralloc (NULL, maps_filename.c_str ());
@@ -1771,7 +1806,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
if (data == NULL)
{
/* Older Linux kernels did not support /proc/PID/smaps. */
- maps_filename = string_printf ("/proc/%d/maps", pid);
+ maps_filename = string_printf ("/proc/%ld/maps", ptid.lwp ());
data = target_fileio_read_stralloc (NULL, maps_filename.c_str ());
if (data == nullptr)
@@ -2275,7 +2310,7 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
/* The state of the process. */
char pr_sname;
/* The PID of the program which generated the corefile. */
- pid_t pid;
+ ptid_t ptid = get_process_reference_ptid ();
/* Process flags. */
unsigned int pr_flag;
/* Process nice value. */
@@ -2286,8 +2321,7 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
gdb_assert (p != nullptr);
/* Obtaining PID and filename. */
- pid = inferior_ptid.pid ();
- xsnprintf (filename, sizeof (filename), "/proc/%d/cmdline", (int) pid);
+ xsnprintf (filename, sizeof (filename), "/proc/%ld/cmdline", ptid.lwp ());
/* The full name of the program which generated the corefile. */
gdb_byte *buf = nullptr;
LONGEST buf_len = target_fileio_read_alloc (nullptr, filename, &buf);
@@ -2310,7 +2344,7 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
memset (p, 0, sizeof (*p));
/* Defining the PID. */
- p->pr_pid = pid;
+ p->pr_pid = ptid.pid ();
/* Copying the program name. Only the basename matters. */
basename = lbasename (fname.get ());
@@ -2327,7 +2361,7 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
strncpy (p->pr_psargs, psargs.c_str (), sizeof (p->pr_psargs) - 1);
p->pr_psargs[sizeof (p->pr_psargs) - 1] = '\0';
- xsnprintf (filename, sizeof (filename), "/proc/%d/stat", (int) pid);
+ xsnprintf (filename, sizeof (filename), "/proc/%ld/stat", ptid.lwp ());
/* The contents of `/proc/PID/stat'. */
gdb::unique_xmalloc_ptr<char> proc_stat_contents
= target_fileio_read_stralloc (NULL, filename);
@@ -2405,7 +2439,7 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
/* Finally, obtaining the UID and GID. For that, we read and parse the
contents of the `/proc/PID/status' file. */
- xsnprintf (filename, sizeof (filename), "/proc/%d/status", (int) pid);
+ xsnprintf (filename, sizeof (filename), "/proc/%ld/status", ptid.lwp ());
/* The contents of `/proc/PID/status'. */
gdb::unique_xmalloc_ptr<char> proc_status_contents
= target_fileio_read_stralloc (NULL, filename);
@@ -3206,9 +3240,9 @@ linux_address_in_shadow_stack_mem_range
if (!target_has_execution () || current_inferior ()->fake_pid_p)
return false;
- const int pid = current_inferior ()->pid;
+ ptid_t ptid = get_process_reference_ptid ();
- std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+ std::string smaps_file = string_printf ("/proc/%ld/smaps", ptid.lwp ());
gdb::unique_xmalloc_ptr<char> data
= target_fileio_read_stralloc (nullptr, smaps_file.c_str ());
diff --git a/gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.c b/gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.c
new file mode 100644
index 00000000000..0686b746c06
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.c
@@ -0,0 +1,48 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <pthread.h>
+#include <assert.h>
+
+static pthread_t main_thread;
+
+static void *
+start (void *arg)
+{
+ int i;
+
+ i = pthread_join (main_thread, NULL);
+ assert (i == 0);
+
+ return arg; /* break-here */
+}
+
+int
+main (void)
+{
+ pthread_t thread;
+ int i;
+
+ main_thread = pthread_self ();
+
+ i = pthread_create (&thread, NULL, start, NULL);
+ assert (i == 0);
+
+ pthread_exit (NULL);
+ assert (0);
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.exp b/gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.exp
new file mode 100644
index 00000000000..e84953dfc29
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/access-procfs-while-thread-leader-exited.exp
@@ -0,0 +1,78 @@
+# Copyright 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+standard_testfile
+set corefile [standard_output_file ${testfile}.core]
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable debug] != ""} {
+ return
+}
+
+clean_restart ${testfile}
+
+gdb_test_no_output "set non-stop on"
+
+if {![runto_main]} {
+ return
+}
+
+gdb_test_multiple "info threads" "threads are supported" {
+ -re ".* main .*\r\n$gdb_prompt $" {
+ # OK, threads are supported.
+ }
+ -re "\r\n$gdb_prompt $" {
+ unsupported "gdb does not support threads on this target"
+ return
+ }
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "break-here"]
+# gdb_continue_to_breakpoint does not work as it uses "$gdb_prompt $" regex
+# which does not work due to the output: (gdb) [Thread ... exited]
+set name "continue to breakpoint: break-here"
+gdb_test_multiple "continue" $name {
+ -re "Breakpoint .* (at|in) .* break-here .*\r\n$gdb_prompt " {
+ pass $name
+ }
+}
+
+set test "info proc"
+set fail_re \
+ [multi_line \
+ "process $decimal" \
+ "warning: unable to open /proc file '/proc/$decimal/cmdline'" \
+ "warning: unable to read link '/proc/$decimal/cwd'" \
+ "warning: unable to read link '/proc/$decimal/exe'"]
+set pass_re \
+ [multi_line \
+ "process $decimal \\\[Note: information where gathered from LWP $decimal as the thread-group leader \\(LWP=$decimal\\) already exited\\.\\\]" \
+ "cmdline = '$binfile'" \
+ "cwd = '.+'" \
+ "exe = '$binfile'"]
+
+gdb_test_multiple "$test" $test {
+ -re -wrap $fail_re {
+ fail $test
+ }
+ -re -wrap $pass_re {
+ pass $test
+ }
+}
+
+# Do not run "info threads" before "gcore" as it could workaround the bug
+# by discarding non-current exited threads.
+gdb_test "info threads" \
+ {The current thread <Thread ID 1> has terminated\. See `help thread'\.} \
+ "exited thread is current due to non-stop"
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-28 14:35 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-28 14:33 [PATCH v1 0/2] gdb: fix for bug 31207 Matthieu Longo
2026-07-28 14:33 ` [PATCH v1 1/2] gdb: rename any_thread_of_inferior to any_non_exited_thread_of_inferior Matthieu Longo
2026-07-28 14:33 ` [PATCH v1 2/2] gdb: rely on the first non-exited thread TPID when reading Linux procfs files Matthieu Longo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox