From: Simon Marchi <simon.marchi@efficios.com>
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@efficios.com>
Subject: [PATCH 2/6] gdb/corefile: handle no signalled thread when writing NT_GDB_TDESC
Date: Fri, 16 Oct 2026 01:20:07 -0400 [thread overview]
Message-ID: <20261016052100.1386999-3-simon.marchi@efficios.com> (raw)
In-Reply-To: <20261016052100.1386999-1-simon.marchi@efficios.com>
Commit c14993e9dc5 ("gdb/corefile: write NT_GDB_TDESC based on
signalled thread") made linux_make_corefile_notes and
elf_none_make_corefile_notes use the signalled thread's architecture to
write the NT_GDB_TDESC note:
gdbarch = target_thread_architecture (signalled_thr->ptid);
However, gcore_find_signalled_thread returns nullptr if the selected
thread has exited and no other thread was stopped by a signal. The code
just above already checks for that, but this line does not, leading to
a null pointer dereference.
This can happen in non-stop mode, if the selected thread exits and the
other threads are then interrupted. For instance, with an UBSan build:
(gdb) thread
[Current thread is 2 (LWP 301816) (exited)]
(gdb) gcore
.../gdb/linux-tdep.c:2586:40: runtime error: member access within null pointer of type 'struct thread_info'
(without UBSan it's just a segfault)
Fix this by falling back to the gdbarch passed to the function, which is
what was used before commit c14993e9dc5.
Add a test that reproduces the scenario above.
Change-Id: I058ec7a19dc7425e2728379317ba1dd879904eb9
---
gdb/elf-none-tdep.c | 7 +-
gdb/linux-tdep.c | 7 +-
.../gcore-exited-selected-thread.c | 51 ++++++++++++
.../gcore-exited-selected-thread.exp | 83 +++++++++++++++++++
4 files changed, 144 insertions(+), 4 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/gcore-exited-selected-thread.c
create mode 100644 gdb/testsuite/gdb.threads/gcore-exited-selected-thread.exp
diff --git a/gdb/elf-none-tdep.c b/gdb/elf-none-tdep.c
index 0d063038f5ec..e046d99bf51b 100644
--- a/gdb/elf-none-tdep.c
+++ b/gdb/elf-none-tdep.c
@@ -113,8 +113,11 @@ elf_none_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
/* Include the target description when possible. Some architectures
allow for per-thread gdbarch so we should really be emitting a tdesc
per-thread, however, we don't currently support reading in a
- per-thread tdesc, so just emit the tdesc for the signalled thread. */
- gdbarch = target_thread_architecture (signalled_thr->ptid);
+ per-thread tdesc, so just emit the tdesc for the signalled thread. If
+ there is no signalled thread, fall back to GDBARCH. */
+ if (signalled_thr != nullptr)
+ gdbarch = target_thread_architecture (signalled_thr->ptid);
+
gcore_elf_make_tdesc_note (gdbarch, obfd, ¬e_data, note_size);
return note_data;
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 4f6910694d50..fbc96c0975a7 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2582,8 +2582,11 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
/* Include the target description when possible. Some architectures
allow for per-thread gdbarch so we should really be emitting a tdesc
per-thread, however, we don't currently support reading in a
- per-thread tdesc, so just emit the tdesc for the signalled thread. */
- gdbarch = target_thread_architecture (signalled_thr->ptid);
+ per-thread tdesc, so just emit the tdesc for the signalled thread. If
+ there is no signalled thread, fall back to GDBARCH. */
+ if (signalled_thr != nullptr)
+ gdbarch = target_thread_architecture (signalled_thr->ptid);
+
gcore_elf_make_tdesc_note (gdbarch, obfd, ¬e_data, note_size);
return note_data;
diff --git a/gdb/testsuite/gdb.threads/gcore-exited-selected-thread.c b/gdb/testsuite/gdb.threads/gcore-exited-selected-thread.c
new file mode 100644
index 000000000000..5a2fbf710831
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/gcore-exited-selected-thread.c
@@ -0,0 +1,51 @@
+/* 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 <assert.h>
+#include <pthread.h>
+#include <unistd.h>
+
+static volatile int joined = 0;
+
+static void *
+thread_func (void *arg)
+{
+ return NULL; /* Break here. */
+}
+
+int
+main (void)
+{
+ pthread_t thread;
+ int res;
+
+ alarm (30);
+
+ res = pthread_create (&thread, NULL, thread_func, NULL);
+ assert (res == 0);
+
+ res = pthread_join (thread, NULL);
+ assert (res == 0);
+
+ joined = 1; /* After join. */
+
+ /* Keep running until GDB interrupts us. */
+ while (1)
+ sleep (1);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/gcore-exited-selected-thread.exp b/gdb/testsuite/gdb.threads/gcore-exited-selected-thread.exp
new file mode 100644
index 000000000000..10a89ffa9060
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/gcore-exited-selected-thread.exp
@@ -0,0 +1,83 @@
+# 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/>.
+
+# Test generating a core file when the selected thread has exited and
+# no other thread was stopped by a signal. In that case, there is no
+# "signalled thread" to dump first.
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] == -1 } {
+ return
+}
+
+save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"set non-stop on\""
+ clean_restart $::testfile
+}
+
+gdb_breakpoint [gdb_get_line_number "Break here."]
+gdb_run_cmd
+gdb_test "" "Thread 2 \[^\r\n\]* hit Breakpoint $decimal, thread_func .*" \
+ "run to breakpoint in thread"
+gdb_test "thread 2" "Switching to thread 2 .*" "select thread 2"
+
+# Print a message when the main thread has joined thread 2. This tells
+# us that thread 2 has exited, without relying on GDB reporting the
+# thread exit right away, which it doesn't do with remote targets. Use
+# a dprintf rather than a breakpoint, so that the main thread does not
+# stop with a SIGTRAP and become the signalled thread.
+gdb_test "dprintf [gdb_get_line_number "After join."],\"thread 2 joined\\n\"" \
+ "Dprintf $decimal at .*" "set dprintf after join"
+
+# Resume thread 2 and let it exit. It stays the selected thread.
+gdb_test_multiple "continue &" "continue thread 2 until exit" {
+ -re "Continuing\\.\r\n$gdb_prompt " {
+ exp_continue
+ }
+ -re "thread 2 joined\r\n" {
+ pass $gdb_test_name
+ }
+}
+
+# Stop the main thread. Being interrupted, it doesn't have a stop
+# signal.
+gdb_test_multiple "interrupt -a" "" {
+ -re "^interrupt -a\r\n$gdb_prompt " {
+ exp_continue
+ }
+ -re "Thread 1 \[^\r\n\]* stopped\\.\r\n" {
+ pass $gdb_test_name
+ }
+}
+
+gdb_test "thread" "Current thread is 2 \\(.*\\) \\(exited\\)\\\]" \
+ "selected thread has exited"
+
+set corefile [host_standard_output_file $testfile.core]
+if { [gdb_gcore_cmd $corefile "save a corefile"] != 1 } {
+ return
+}
+
+clean_restart $::testfile
+if { [gdb_core_cmd $corefile "load corefile"] != 1 } {
+ return
+}
+
+# Check that the core file contains only the main thread.
+gdb_test "info threads" \
+ "Id +Target Id +Frame *\r\n\\* 1 +Thread \[^\r\n\]*" \
+ "core file contains main thread"
--
2.56.0
next prev parent reply other threads:[~2026-10-16 5:21 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 5:20 [PATCH 0/6] Fix generating core files when GPU waves are present Simon Marchi
2026-10-16 5:20 ` [PATCH 1/6] gdb/testsuite: make with_rocm_gpu_lock transparent to return Simon Marchi
2026-10-16 5:20 ` Simon Marchi [this message]
2026-10-16 5:20 ` [PATCH 3/6] gdb/amd-dbgapi: handle regno == -1 in fetch_registers and store_registers Simon Marchi
2026-10-16 5:20 ` [PATCH 4/6] gdb/linux-tdep: don't write core file notes for non-Linux threads Simon Marchi
2026-10-16 5:20 ` [PATCH 5/6] gdb/amd-dbgapi: defer TARGET_OBJECT_AUXV xfers to the beneath target Simon Marchi
2026-10-16 5:20 ` [PATCH 6/6] gdb/linux-tdep: ignore non-Linux threads in get_ptid_for_slash_proc 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=20261016052100.1386999-3-simon.marchi@efficios.com \
--to=simon.marchi@efficios.com \
--cc=gdb-patches@sourceware.org \
/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