@@ -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 ()
{
@@ -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 ();
@@ -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 ());
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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"