From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id Qv3PH2C00WpERBcAWB0awg (envelope-from ) for ; Fri, 16 Oct 2026 01:21:36 -0400 Received: by simark.ca (Postfix, from userid 112) id 296D61E090; Fri, 16 Oct 2026 01:21:36 -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 D7B341E04F for ; Fri, 16 Oct 2026 01:21:32 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 15AB94BA23E6 for ; Fri, 16 Oct 2026 05:21:32 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 15AB94BA23E6 Received: from simark.ca (simark.ca [158.69.221.121]) by sourceware.org (Postfix) with ESMTPS id E107C4BA2E1A for ; Fri, 16 Oct 2026 05:21:04 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E107C4BA2E1A 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 E107C4BA2E1A 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=1792128065; cv=none; b=ndDzi9bSofJI+tFXCFPBUxqQ1pbNkbM9DmXbTVLVTfuBAUAexhwOtbUUfXcpE/+FgLo+nUlAMT/LW9KwJPiDwXeVUAsKGPpE3+TSwRczxO9yIxhf5cKA2hihny7n7kYXWgMhy1CM+5mg0rOW2vgDTIX/AYB1WfCmaIT455U8OU4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1792128065; c=relaxed/simple; bh=OvssZ/4LFjxL+ncMDHqmNzdAfCjXfGZZuMKAeTK1uF4=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=LDxvqM3vw4+N12D+6LI4QuGTaQz+qJx/4jg5EgE9gRAiQNPZDIUtRhIu5FbuGjrIufDx7LE3mF4zLjb3Si23NftRuF+mIa5tvhYdrMC4QzJk7puCv5EYctFRVBD57kNbsg/TvYertsSNSRLohX+byg9fDJ5/L94TzKKjUb2D3yw= ARC-Authentication-Results: i=1; sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E107C4BA2E1A Received: by simark.ca (Postfix) id BCCD41E050; Fri, 16 Oct 2026 01:21:02 -0400 (EDT) From: Simon Marchi To: gdb-patches@sourceware.org Cc: Simon Marchi Subject: [PATCH 2/6] gdb/corefile: handle no signalled thread when writing NT_GDB_TDESC Date: Fri, 16 Oct 2026 01:20:07 -0400 Message-ID: <20261016052100.1386999-3-simon.marchi@efficios.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261016052100.1386999-1-simon.marchi@efficios.com> References: <20261016052100.1386999-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 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 . */ + +#include +#include +#include + +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 . + +# 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