From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id AZO+OhtLrGpWFhgAWB0awg (envelope-from ) for ; Thu, 17 Sep 2026 16:18:35 -0400 Received: by simark.ca (Postfix, from userid 112) id E77D01E06A; Thu, 17 Sep 2026 16:18:35 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-5.3 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 0A7B41E051 for ; Thu, 17 Sep 2026 16:18:34 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 396F94BAE7C0 for ; Thu, 17 Sep 2026 20:18:27 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 396F94BAE7C0 Received: from simark.ca (simark.ca [158.69.221.121]) by sourceware.org (Postfix) with ESMTPS id ABB9D4BA2E20 for ; Thu, 17 Sep 2026 20:17:59 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org ABB9D4BA2E20 Authentication-Results: sourceware.org; dmarc=fail (p=none dis=none) header.from=efficios.com Authentication-Results: sourceware.org; spf=fail smtp.mailfrom=efficios.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org ABB9D4BA2E20 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=158.69.221.121 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1789676279; cv=none; b=VqleAkWGRQH4/JzEY6zszx2DSRIQj/0EnP0PuQqc9MgJEuOptY3tq6ZTquh9X2SvUiFERawvubq99ZJv+lwWv47tCeV9pDFwygUWeV3OyOmxOmyX47sraAQu7i9T2/w7Ye3dF4byzVpbhfU9K+PuENSOrgdC372IYgoidr+VE5A= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1789676279; c=relaxed/simple; bh=JFbFIWMSLGw6ixhfrKiyp6H54hT0b2DUNyJ/NoKBes4=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=r24df9ZdrCrLvOCTwJ23epXrXbwa/gU9ZYOZYIqLmJVJrTxHuWBtJCwaIIoeRyqVT26RR4FKJ896xlVMKPIaxqiEShOhlyICpglbaT/pjMbFdPuf2wICstxv8Mf1TvndTakUV2E8a6TCPnb1rC5uWCQcSo75QZPC9h7sHEqBYag= ARC-Authentication-Results: i=1; sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org ABB9D4BA2E20 Received: by simark.ca (Postfix) id 07FCB1E066; Thu, 17 Sep 2026 16:17:58 -0400 (EDT) From: Simon Marchi To: gdb-patches@sourceware.org Cc: Simon Marchi , Matthieu Longo 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 Message-ID: <20260917201755.589524-2-simon.marchi@efficios.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917201755.589524-1-simon.marchi@efficios.com> References: <20260917201755.589524-1-simon.marchi@efficios.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org On Linux, /proc/ is keyed by the thread-group leader PID. When the leader has exited, some /proc//... 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/ entry exists for every live LWP of the process, and for the entries listed above its contents are the same as those of /proc/. 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//task//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 " header with: Reading /proc for process or, when the LWP differs from the PID: Reading /proc for process (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 --- 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/" 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 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 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 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 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 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 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 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 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 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 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 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 . - -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 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 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 . + +# 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 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