From: Simon Marchi <simon.marchi@efficios.com>
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@efficios.com>,
Matthieu Longo <matthieu.longo@arm.com>
Subject: [PATCH 2/2] gdb: use the selected thread's LWP ID when reading Linux procfs files
Date: Thu, 17 Sep 2026 16:16:58 -0400 [thread overview]
Message-ID: <20260917201755.589524-2-simon.marchi@efficios.com> (raw)
In-Reply-To: <20260917201755.589524-1-simon.marchi@efficios.com>
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, renamed below).
This causes GDB to fail to read procfs entries such as cmdline, cwd,
exe, maps, and smaps when it builds the paths from the inferior pid
after the thread-group leader has exited.
A /proc/<lwp> entry exists for every live LWP of the process, and for
the entries listed above its contents are the same as those of
/proc/<pid>. Fix this by building the procfs paths from the LWP id of a
thread GDB believes is alive, rather than from the inferior pid.
Add get_ptid_for_slash_proc, which returns the PTID to read from, and
use it in places that read /proc:
- linux_info_proc
- linux_process_address_in_memtag_page
- linux_find_memory_regions_full
- linux_fill_prpsinfo
- linux_address_in_shadow_stack_mem_range
linux_vsyscall_range_raw also reads /proc, but I did not change it, as
it uses a different pattern, "/proc/<pid>/task/<pid>/maps". It could in
theory suffer from the same problem.
The thread is chosen with any_non_exited_thread_of_inferior, which gives
preference to the selected thread. I think this is actually a good
feature. Some entries in /proc (like stat and status) show some
thread-specific content, so this lets the user pick which thread "info
proc" reports on. Most entries are per-process, the choice of thread
makes no difference for them.
linux_fill_prpsinfo is an exception to the above. It populates the
NT_PRPSINFO note of a core file, which the kernel (for kernel-generated
core files) fills in from the thread group leader, even when that leader
is a zombie:
https://elixir.bootlin.com/linux/v7.2.5/source/fs/binfmt_elf.c#L1901
To keep GDB-generated cores looking like kernel-generated ones, it keeps
reading stat and status from the leader's entry, so the values taken
from those match what the kernel would write (stat and status are still
readable when the thread is zombie). Only the cmdline is read from a
live LWP, since trying to read that one from a zombie leader doesn't
work. This mirrors the kernel's fill_psinfo function, which takes the
program name and arguments from the mm of the thread doing the dump:
https://elixir.bootlin.com/linux/v7.2.5/source/fs/binfmt_elf.c#L1530-L1539
This also fixes a bug, in that core dumps produced while the leader is
zombie would be missing the NT_PRPSINFO note. linux_fill_prpsinfo would
read an empty string from cmdline and return false. A user-visible
consequence of that is that loading back the core would not show the
expected "Core was generated by..." line.
This is still a best-effort: GDB's view of the thread list may be stale,
so the thread selected to do the /proc accesses may actually have
exited but GDB does not know it.
Since the entry "info proc" reads is no longer necessarily that of the
thread group leader, replace its "process <pid>" header with:
Reading /proc for process <pid>
or, when the LWP differs from the PID:
Reading /proc for process <pid> (LWP <lwp>)
Since "info proc" now stores the pid given on the command line in a
ptid_t, whose pid field is an int, reject a value that would not survive
the conversion, rather than printing one value and reading /proc for
another.
Update gdb.base/info-proc.exp for the new header.
Rename gdb.threads/gcore-stale-thread.{exp,c} to
gdb.threads/thread-leader-exited.{exp,c} and modify it in a few ways:
- give the program a second worker thread
- check that we're able to read /proc on Linux even when the leader has
exited (and is selected)
- check that "info proc stat" reports each worker thread's own LWP id
in its "Process:" field, confirming that "info proc" prioritizes the
selected thread
- load the generated core back to verify that we see the "Core was
generated by ..." line, and therefore the NT_PRPSINFO note was
generated
- enable non-stop using the usual save_vars / append to GDBFLAGS
pattern, which fixes a pre-existing bug of not being able to run the
test on the native-extended-gdbserver board.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31207
Change-Id: I1e6ccfae90551b8e96a5644b51c7652bbda593fa
Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
---
gdb/linux-tdep.c | 104 ++++++++++-----
gdb/testsuite/gdb.base/info-proc.exp | 3 +-
.../gdb.threads/gcore-stale-thread.exp | 57 --------
...-stale-thread.c => thread-leader-exited.c} | 30 ++++-
.../gdb.threads/thread-leader-exited.exp | 122 ++++++++++++++++++
5 files changed, 222 insertions(+), 94 deletions(-)
delete mode 100644 gdb/testsuite/gdb.threads/gcore-stale-thread.exp
rename gdb/testsuite/gdb.threads/{gcore-stale-thread.c => thread-leader-exited.c} (64%)
create mode 100644 gdb/testsuite/gdb.threads/thread-leader-exited.exp
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 05ee71fc96f4..2086dd2c1fd7 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -456,6 +456,30 @@ linux_has_shared_address_space (struct gdbarch *gdbarch)
return linux_is_uclinux ();
}
+/* Return the ptid of a thread of the current inferior whose lwp id can be
+ used to build "/proc/<lwp>" paths.
+
+ Through any_non_exited_thread_of_inferior, this function prefers the
+ currently selected thread, if it has not exited. For most information in
+ /proc, it does not matter which thread of a process we pick. But a few
+ entries (like stat counters) are thread-specific. This allows the user
+ to focus a particular thread and get specific information about that
+ thread.
+
+ Throw an error if the inferior has no thread that GDB believes is
+ alive. */
+
+static ptid_t
+get_ptid_for_slash_proc ()
+{
+ if (thread_info *thread
+ = any_non_exited_thread_of_inferior (current_inferior ());
+ thread != nullptr)
+ return thread->ptid;
+
+ error (_("Could not find a non-exited thread to read /proc from."));
+}
+
/* This is how we want PTIDs from core files to be printed. */
static std::string
@@ -841,9 +865,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);
@@ -858,7 +880,16 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
{
char *tem;
- pid = strtoul (args, &tem, 10);
+ /* 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 = strtoul (args, &tem, 10);
+
+ /* ptid_t holds the pid as an int, so reject anything that would not
+ fit an int. */
+ if (pid > INT_MAX)
+ error (_("Invalid process id: %s"), args);
+
+ ptid = ptid_t (pid, pid);
args = tem;
}
else
@@ -868,17 +899,22 @@ 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_ptid_for_slash_proc ();
}
args = skip_spaces (args);
if (args && args[0])
error (_("Too many parameters: %s"), args);
- gdb_printf (_("process %ld\n"), pid);
+ if (ptid.pid () == ptid.lwp ())
+ gdb_printf (_("Reading /proc for process %d\n"), ptid.pid ());
+ else
+ gdb_printf (_("Reading /proc for process %d (LWP %ld)\n"),
+ ptid.pid (), ptid.lwp ());
+
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);
@@ -900,7 +936,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 ())
@@ -910,7 +946,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);
@@ -934,7 +970,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 ())
@@ -944,7 +980,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)
@@ -989,7 +1025,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)
@@ -999,7 +1035,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)
@@ -1670,9 +1706,8 @@ linux_process_address_in_memtag_page (CORE_ADDR address)
if (current_inferior ()->fake_pid_p)
return false;
- pid_t pid = current_inferior ()->pid;
-
- std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+ ptid_t ptid = get_ptid_for_slash_proc ();
+ 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 ());
@@ -1730,7 +1765,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. */
@@ -1743,12 +1777,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_ptid_for_slash_proc ();
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 ());
@@ -1762,7 +1796,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 ());
@@ -1770,7 +1804,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)
@@ -2273,8 +2307,6 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
const char *prog_state;
/* The state of the process. */
char pr_sname;
- /* The PID of the program which generated the corefile. */
- pid_t pid;
/* Process flags. */
unsigned int pr_flag;
/* Process nice value. */
@@ -2284,9 +2316,20 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p)
gdb_assert (p != nullptr);
+ /* The kernel fills NT_PRPSINFO from the thread group leader, even it is a
+ zombie. Do the same here, read the process state from the leader's proc
+ entries.
+
+ However, we can't read cmdline from the a zombie leader thread (it would
+ read empty). Read that one from a thread that is still alive (this is
+ what the kernel does too). It's the same value for the whole process, so
+ the choice of thread does not matter otherwise. */
+ const int leader_id = current_inferior ()->pid;
+ const ptid_t live_ptid = get_ptid_for_slash_proc ();
+
/* Obtaining PID and filename. */
- pid = inferior_ptid.pid ();
- xsnprintf (filename, sizeof (filename), "/proc/%d/cmdline", (int) pid);
+ xsnprintf (filename, sizeof (filename), "/proc/%ld/cmdline",
+ live_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);
@@ -2309,7 +2352,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 = leader_id;
/* Copying the program name. Only the basename matters. */
basename = lbasename (fname.get ());
@@ -2326,7 +2369,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/%d/stat", leader_id);
/* The contents of `/proc/PID/stat'. */
gdb::unique_xmalloc_ptr<char> proc_stat_contents
= target_fileio_read_stralloc (NULL, filename);
@@ -2404,7 +2447,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/%d/status", leader_id);
/* The contents of `/proc/PID/status'. */
gdb::unique_xmalloc_ptr<char> proc_status_contents
= target_fileio_read_stralloc (NULL, filename);
@@ -3209,9 +3252,8 @@ linux_address_in_shadow_stack_mem_range
if (!target_has_execution () || current_inferior ()->fake_pid_p)
return false;
- const int pid = current_inferior ()->pid;
-
- std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+ ptid_t ptid = get_ptid_for_slash_proc ();
+ 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.base/info-proc.exp b/gdb/testsuite/gdb.base/info-proc.exp
index 89eed2ac815a..5310341264eb 100644
--- a/gdb/testsuite/gdb.base/info-proc.exp
+++ b/gdb/testsuite/gdb.base/info-proc.exp
@@ -63,7 +63,8 @@ if {![runto_main]} {
return
}
-gdb_test "info proc" "process ${decimal}.*" "info proc with process"
+gdb_test "info proc" "Reading /proc for process ${decimal}.*" \
+ "info proc with process"
gdb_test "info proc mapping" \
".*Mapped address spaces:.*${hex}${ws}${hex}${ws}${hex}${ws}${hex}.*"
diff --git a/gdb/testsuite/gdb.threads/gcore-stale-thread.exp b/gdb/testsuite/gdb.threads/gcore-stale-thread.exp
deleted file mode 100644
index d0465c87a4ad..000000000000
--- a/gdb/testsuite/gdb.threads/gcore-stale-thread.exp
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2014-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
- }
-}
-
-gdb_gcore_cmd "$corefile" "save a corefile"
-
-# 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"
diff --git a/gdb/testsuite/gdb.threads/gcore-stale-thread.c b/gdb/testsuite/gdb.threads/thread-leader-exited.c
similarity index 64%
rename from gdb/testsuite/gdb.threads/gcore-stale-thread.c
rename to gdb/testsuite/gdb.threads/thread-leader-exited.c
index 9dbeeca5e0e1..998ff4d41d60 100644
--- a/gdb/testsuite/gdb.threads/gcore-stale-thread.c
+++ b/gdb/testsuite/gdb.threads/thread-leader-exited.c
@@ -19,29 +19,49 @@
#include <assert.h>
static pthread_t main_thread;
+static pthread_barrier_t barrier;
+
+/* First worker. */
static void *
start (void *arg)
{
- int i;
-
- i = pthread_join (main_thread, NULL);
+ int i = pthread_join (main_thread, NULL);
assert (i == 0);
+ i = pthread_barrier_wait (&barrier);
+ assert (i == 0 || i == PTHREAD_BARRIER_SERIAL_THREAD);
+
return arg; /* break-here */
}
+/* Second worker. */
+
+static void *
+start_second (void *arg)
+{
+ int i = pthread_barrier_wait (&barrier);
+ assert (i == 0 || i == PTHREAD_BARRIER_SERIAL_THREAD);
+
+ return arg; /* break-here-second */
+}
+
int
main (void)
{
- pthread_t thread;
- int i;
+ pthread_t thread, thread_second;
main_thread = pthread_self ();
+ int i = pthread_barrier_init (&barrier, NULL, 2);
+ assert (i == 0);
+
i = pthread_create (&thread, NULL, start, NULL);
assert (i == 0);
+ i = pthread_create (&thread_second, NULL, start_second, NULL);
+ assert (i == 0);
+
pthread_exit (NULL);
assert (0);
return 0;
diff --git a/gdb/testsuite/gdb.threads/thread-leader-exited.exp b/gdb/testsuite/gdb.threads/thread-leader-exited.exp
new file mode 100644
index 000000000000..1c493c69242e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/thread-leader-exited.exp
@@ -0,0 +1,122 @@
+# Copyright 2014-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/>.
+
+# Check that some things work (like "info proc" and generating core files) when
+# the thread group leader has exited.
+
+standard_testfile
+set corefile [standard_output_file ${testfile}.core]
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable debug] != ""} {
+ return
+}
+
+save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"set non-stop on\""
+ clean_restart ${testfile}
+}
+
+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_breakpoint ${srcfile}:[gdb_get_line_number "break-here-second"]
+
+gdb_test -no-prompt-anchor "continue &" "Continuing\\."
+
+# Wait for both workers to stop and the leader to exit.
+set num_events 0
+gdb_test_multiple "" "wait for the workers to stop" -lbl {
+ -re "break-here \\*/|break-here-second \\*/|\\(id 1\\) exited\\]" {
+ incr num_events
+ if { $num_events < 3 } {
+ exp_continue
+ }
+ }
+}
+
+gdb_assert { $num_events == 3 } "both workers stopped, leader exited"
+
+# Check that "info proc" reads /proc through a thread that is still alive. The
+# format is Linux-specific.
+if { [istarget "*-*-linux*"] } {
+ set binfile_re [string_to_regexp $binfile]
+ gdb_test "info proc" \
+ [multi_line \
+ "Reading /proc for process $decimal \\(LWP $decimal\\)" \
+ "cmdline = '$binfile_re'" \
+ "cwd = '.+'" \
+ "exe = '$binfile_re'"]
+}
+
+gdb_gcore_cmd "$corefile" "save a corefile"
+
+# 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"
+
+# Verify that "info proc stat" reads the /proc entry for the selected thread.
+proc check_info_proc_stat { thread_id } {
+ # Capture the thread LWP id while switching thread. The message is
+ # different depending on native vs remote.
+ set lwp ""
+ gdb_test_multiple "thread $thread_id" "switch to thread $thread_id" {
+ -re -wrap "Switching to thread $thread_id \\(Thread \[^\r\n\]*\\(LWP ($::decimal)\\)\\).*" {
+ set lwp $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ -re -wrap "Switching to thread $thread_id \\(Thread $::decimal\\.($::decimal)\\).*" {
+ set lwp $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ }
+
+ if { $lwp eq "" } {
+ error "unable to capture LWP id for thread $thread_id"
+ return
+ }
+
+ gdb_test "info proc stat" \
+ [multi_line \
+ "Reading /proc for process $::decimal \\(LWP $lwp\\)" \
+ "Process: $lwp" \
+ ".*"] \
+ "info proc stat for thread $thread_id"
+}
+
+if { [istarget "*-*-linux*"] } {
+ check_info_proc_stat 2
+ check_info_proc_stat 3
+}
+
+# Load the core back to verify that GDB managed to fill the NT_PRPSINFO note.
+# A user-visible consequence of not having this note is that GDB doesn't print
+# the "Core was generated by..." line. gdb_core_cmd needs to see this line in
+# order to emit a pass, and it emits a fail in all other cases.
+clean_restart ${testfile}
+gdb_core_cmd $corefile "reload the corefile"
--
2.55.0
next prev parent reply other threads:[~2026-09-17 20:18 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-17 20:16 [PATCH 1/2] gdb: don't return an exited thread from any_non_exited_thread_of_inferior Simon Marchi
2026-09-17 20:16 ` Simon Marchi [this message]
2026-09-21 10:22 ` Matthieu Longo
2026-09-21 13:46 ` Simon Marchi
[not found] ` <20260917201755.589524-2-simon.marchi@efficios.com_5b6b04a9_release>
2026-09-21 14:26 ` [PATCH 2/2] gdb: use the selected thread's LWP ID when reading Linux procfs files Matthieu Longo
2026-09-21 14:57 ` Simon Marchi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260917201755.589524-2-simon.marchi@efficios.com \
--to=simon.marchi@efficios.com \
--cc=gdb-patches@sourceware.org \
--cc=matthieu.longo@arm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox