From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>
Subject: [PATCH] gdbserver: reset the report_thread_events feature for new connections
Date: Wed, 28 Oct 2026 17:41:54 +0000 [thread overview]
Message-ID: <7845b61991b27ed7e0b91237b00da2fcab3c2d8c.1793209296.git.aburgess@redhat.com> (raw)
I noticed that the report_thread_events feature flag (in gdbserver)
was not being reset to its default state for each new connection.
This flag is turned on when gdbserver receives a QThreadEvents:1
packet, and turned back off with a QThreadEvents:0 packet. GDB
usually sends these packets before and after waiting for some event.
Normally, by the time GDB returns to a user prompt the QThreadEvents:0
packet will have been sent and report_thread_events will have been
returned to its default `off` state.
However, it is possible that if GDB broke the connection with
gdbserver unexpectedly, for example, if GDB crashed, or the connection
dropped, then gdbserver might be left with report_thread_events in the
`on` state.
If this happened, then when a new GDB reconnected, the flag should be
restored to its default `off` state. This is what happens to other
similar flags, e.g. the noack_mode flag. See the code in
gdbserver/server.cc in the captured_main function, at the top of the
'while (1)' loop; this is the code that resets flags for a new
connection. The report_thread_events flag is missing from this block.
This commit adds the reset of report_thread_events to the top of this
while loop, ensuring the flag is reset on each new connection, and
also adds a test for this.
The test is a little artificial as we are trying to simulate an
unexpected disconnection, but in a controlled manner. To do this I
manually send a QThreadEvents:1 packet to turn report_thread_events on
and then disconnect. I then turn the thread-events-packet off and
reconnect. Finally, I 'set remote debug on' and look in the packet
output for an indication that gdbserver is sending thread-event
packets.
Without the fix this test fails, but passes with the patch in this
commit.
---
.../gdb.server/reconnect-thread-events.c | 61 ++++++++
.../gdb.server/reconnect-thread-events.exp | 145 ++++++++++++++++++
gdbserver/server.cc | 1 +
3 files changed, 207 insertions(+)
create mode 100644 gdb/testsuite/gdb.server/reconnect-thread-events.c
create mode 100644 gdb/testsuite/gdb.server/reconnect-thread-events.exp
diff --git a/gdb/testsuite/gdb.server/reconnect-thread-events.c b/gdb/testsuite/gdb.server/reconnect-thread-events.c
new file mode 100644
index 00000000000..c939030451c
--- /dev/null
+++ b/gdb/testsuite/gdb.server/reconnect-thread-events.c
@@ -0,0 +1,61 @@
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include "gdb_watchdog.h"
+
+volatile int ready = 0;
+
+void
+breakpt_before_thread (void)
+{
+}
+
+void
+breakpt_after_thread (void)
+{
+}
+
+static void *
+worker (void *arg)
+{
+ while (!ready)
+ usleep (1000);
+ return NULL;
+}
+
+int
+main (void)
+{
+ pthread_t thr;
+
+ gdb_watchdog (120);
+
+ breakpt_before_thread ();
+
+ if (pthread_create (&thr, NULL, worker, NULL) != 0)
+ abort ();
+
+ breakpt_after_thread ();
+
+ ready = 1;
+ pthread_join (thr, NULL);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.server/reconnect-thread-events.exp b/gdb/testsuite/gdb.server/reconnect-thread-events.exp
new file mode 100644
index 00000000000..4f84bdce11b
--- /dev/null
+++ b/gdb/testsuite/gdb.server/reconnect-thread-events.exp
@@ -0,0 +1,145 @@
+# 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 that gdbserver correctly resets the report_thread_events flag
+# when a new connection is established.
+#
+# In a normal GDB session the report_thread_events will only be
+# switched on (via "QThreadEvents:1" packet) while GDB is waiting for
+# some command to complete, after which the flag is disabled again
+# using a "QThreadEvents:0" packet.
+#
+# However, if GDB unexpectedly disconnects while this feature is
+# turned on, then when a new GDB connects gdbserver should
+# automatically reset this flag. At one point this was not happening.
+# This test exists to check for this case.
+#
+# Connect to gdbserver, manually send QThreadEvents:1 to latch the
+# flag on, disconnect, then reconnect with the thread-events packet
+# disabled. If gdbserver fails to reset the flag, it will send
+# unsolicited T05create:; stop-reply packets for thread creation
+# events. Detect this using 'set remote debug on'.
+
+require {!is_remote target} {!is_remote host}
+
+load_lib gdbserver-support.exp
+
+standard_testfile
+
+require allow_gdbserver_tests
+
+if { [build_executable "build executable" $testfile $srcfile \
+ {debug pthreads}] == -1 } {
+ return
+}
+
+proc reconnection_and_thread_events {} {
+ global GDBFLAGS gdbserver_reconnect_p use_gdb_stub
+
+ save_vars { GDBFLAGS } {
+ # If possible set the sysroot to avoid copying files.
+ if { ![is_remote host] } {
+ set GDBFLAGS "$GDBFLAGS -ex \"set sysroot\""
+ }
+ clean_restart $::testfile
+
+ # Make sure we're disconnected, in case we're testing with an
+ # extended-remote board, therefore already connected.
+ gdb_test "disconnect" ".*"
+ }
+
+ save_vars { gdbserver_reconnect_p use_gdb_stub } {
+ # Setting this flag prevents gdbserver being started with the
+ # --once option; this is required so that we can reconnect.
+ set gdbserver_reconnect_p 1
+
+ # This test always starts gdbserver in --multi mode, so clear
+ # the use_gdb_stub global to allow gdb_start_cmd to work.
+ set use_gdb_stub 0
+
+ set res [gdbserver_start "--multi" "$::binfile"]
+ set gdbserver_protocol "extended-remote"
+ set gdbserver_gdbport [lindex $res 1]
+
+ with_test_prefix "first connection" {
+ gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport
+
+ # Start a new inferior.
+ if { [gdb_start_cmd] < 0 } {
+ fail "start command"
+ return
+ }
+
+ # gdb_start_cmd doesn't consume the prompt. Collect it now.
+ gdb_test "" "" "wait for prompt after start"
+
+ gdb_breakpoint "breakpt_before_thread"
+ gdb_continue_to_breakpoint "before thread creation"
+
+ # Directly send QThreadEvents:1 to latch the flag on in
+ # gdbserver. This is equivalent to what GDB does during resume
+ # when thread events are needed, but avoids depending on infrun
+ # internals that may also send QThreadEvents:0 before we can
+ # disconnect.
+ #
+ # The point here is to emulate an abnormal disconnection; the
+ # flag is turned on, but never turned off.
+ gdb_test "maintenance packet QThreadEvents:1" \
+ "received: .*OK.*" \
+ "send QThreadEvents:1"
+
+ gdb_test "disconnect" ".*"
+ }
+
+ with_test_prefix "second connection" {
+ # Disable both thread-events and thread-options packets so
+ # GDB will not send either on the new connection.
+ gdb_test "set remote thread-events-packet off" ".*"
+ gdb_test "set remote thread-options-packet off" ".*"
+
+ # Reconnect. At this point there is already an inferior
+ # running, we can just pick up the debug session.
+ gdb_target_cmd $gdbserver_protocol $gdbserver_gdbport
+
+ gdb_breakpoint "breakpt_after_thread"
+
+ # Continue past the thread creation with debug remote enabled.
+ # If gdbserver failed to reset the report_thread_events flag,
+ # the thread creation will generate a T05create:; stop-reply
+ # packet.
+ gdb_test_no_output "set debug remote on"
+
+ # Continue. Process the output line by line to avoid buffering
+ # excessive output. Look for a 'T' packet mentioning the
+ # 'create' stop reason, this should only happen when the
+ # thread-events-packet is on.
+ set saw_create false
+ gdb_test_multiple "continue" \
+ "continue without thread-create events" -lbl {
+ -re "\r\n\[^\r\n\]+Packet received: T\[^\r\n\]+create:\[^\r\n\]+(?=\r\n)" {
+ set saw_create true
+ exp_continue
+ }
+ -re "\r\n$::gdb_prompt $" {
+ gdb_assert { !$saw_create } $gdb_test_name
+ }
+ }
+
+ gdb_test_no_output -nopass "set debug remote off"
+ }
+ }
+}
+
+reconnection_and_thread_events
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 74774fcde7c..34a49cec746 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -4621,6 +4621,7 @@ captured_main (int argc, char *argv[])
cs.report_fork_events = 0;
cs.report_vfork_events = 0;
cs.report_exec_events = 0;
+ cs.report_thread_events = 0;
/* Be sure we're out of tfind mode. */
cs.current_traceframe = -1;
cs.cont_thread = null_ptid;
base-commit: b2fe3a449cb5d130ea2fec65bb214601826cdeff
--
2.25.4
reply other threads:[~2026-10-28 17:42 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=7845b61991b27ed7e0b91237b00da2fcab3c2d8c.1793209296.git.aburgess@redhat.com \
--to=aburgess@redhat.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