Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Martin KOCH <Martin.KOCH@bachmann.info>
To: <gdb-patches@sourceware.org>
Cc: <simark@simark.ca>, Martin KOCH <Martin.KOCH@bachmann.info>
Subject: [PATCH v2] gdb/remote: fix assertions when attaching in non-stop mode
Date: Fri, 19 Jun 2026 11:25:35 +0200	[thread overview]
Message-ID: <20260619092535.2476689-1-Martin.KOCH@bachmann.info> (raw)
In-Reply-To: <15e03eb6-40b4-4f69-a96b-57e3650e021f@simark.ca>

When connecting to a remote target in non-stop mode against a
multi-threaded inferior stopped at raise(SIGSTOP), three internal-error
assertions can fire in sequence:

  remote.c:546: mark_async_event_handler: Assertion 'this->is_async_p ()' failed.
  thread.c:429: set_pending_waitstatus: Assertion 'this->internal_state () ==
                THREAD_INT_STOPPED || ...' failed.
  thread.c:426: set_pending_waitstatus: Assertion '!this->has_pending_waitstatus ()' failed.

The first one is what PR 30630 reports and is fixed with the PR proposed
by Mikhail Terekhov, but with the fix the two other assertions surface:

 * thread.c:429: addressed by reordering set_internal_state / set_state
   to run before set_pending_waitstatus.
 * thread.c:426: addressed by clearing any existing pending wait
   status before installing the new one, when gdbserver delivers
   multiple events for the same thread.

These assertions are reached through
remote.c:process_initial_stop_replies, which only runs on the initial
connection to a remote target, not via GDB's own "attach" command.  The
new test gdb.threads/attach-non-stop-stopped.exp reproduces the issue:
it starts gdbserver attached to a multi-threaded inferior that is
already stopped via a pending SIGSTOP, then connects GDB to it in
non-stop mode.  Without this fix the connection trips the assertions
above; with it, the connection succeeds.

[1] https://sourceware.org/pipermail/gdb-patches/2023-October/202937.html

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30630
Signed-off-by: Martin KOCH <Martin.KOCH@bachmann.info>
---
 gdb/remote.c                                  |  14 ++-
 .../gdb.threads/attach-non-stop-stopped.c     |  78 +++++++++++++
 .../gdb.threads/attach-non-stop-stopped.exp   | 104 ++++++++++++++++++
 3 files changed, 191 insertions(+), 5 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/attach-non-stop-stopped.c
 create mode 100644 gdb/testsuite/gdb.threads/attach-non-stop-stopped.exp

diff --git a/gdb/remote.c b/gdb/remote.c
index 2961664cf33..4148301bc6f 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -5237,13 +5237,17 @@ remote_target::process_initial_stop_replies (int from_tty)
 	  ws.set_stopped (sig);
 	}
 
-      if (ws.kind () != TARGET_WAITKIND_STOPPED
-	  || ws.sig () != GDB_SIGNAL_0)
-	evthread->set_pending_waitstatus (ws);
-
       set_internal_state (this, event_ptid, THREAD_INT_STOPPED);
       set_state (this, event_ptid, THREAD_STOPPED);
       get_remote_thread_info (evthread)->set_not_resumed ();
+
+      if (ws.kind () != TARGET_WAITKIND_STOPPED
+	  || ws.sig () != GDB_SIGNAL_0)
+	{
+	  if (evthread->has_pending_waitstatus ())
+	    evthread->clear_pending_waitstatus ();
+	  evthread->set_pending_waitstatus (ws);
+	}
     }
 
   /* "Notice" the new inferiors before anything related to
@@ -8384,7 +8388,7 @@ remote_target::queued_stop_reply (ptid_t ptid)
   remote_state *rs = get_remote_state ();
   stop_reply_up r = remote_notif_remove_queued_reply (ptid);
 
-  if (!rs->stop_reply_queue.empty () && target_can_async_p ())
+  if (!rs->stop_reply_queue.empty () && target_is_async_p ())
     {
       /* There's still at least an event left.  */
       rs->mark_async_event_handler ();
diff --git a/gdb/testsuite/gdb.threads/attach-non-stop-stopped.c b/gdb/testsuite/gdb.threads/attach-non-stop-stopped.c
new file mode 100644
index 00000000000..c58562892e9
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/attach-non-stop-stopped.c
@@ -0,0 +1,78 @@
+/* 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/>.  */
+
+/* This program creates a few threads and then stops the whole process
+   with a pending SIGSTOP, leaving it group-stopped.  It is intended to
+   be started outside of GDB and then attached to.  See
+   attach-non-stop-stopped.exp.  */
+
+#include <pthread.h>
+#include <unistd.h>
+#include <signal.h>
+#include <assert.h>
+
+/* Number of threads we'll create, in addition to the main thread.  */
+#define N_THREADS 4
+
+/* Used to make sure all threads are running before we stop the
+   process.  */
+static pthread_barrier_t barrier;
+
+/* Entry point for threads.  Loops forever.  */
+
+static void *
+thread_func (void *arg)
+{
+  pthread_barrier_wait (&barrier);
+
+  while (1)
+    sleep (1);
+
+  return NULL;
+}
+
+int
+main (void)
+{
+  pthread_t threads[N_THREADS];
+  int i;
+
+  /* Safety net, in case the process is left running.  */
+  alarm (300);
+
+  pthread_barrier_init (&barrier, NULL, N_THREADS + 1);
+
+  for (i = 0; i < N_THREADS; ++i)
+    {
+      int rc = pthread_create (&threads[i], NULL, thread_func, NULL);
+      assert (rc == 0);
+    }
+
+  /* Wait until all threads are up and running.  */
+  pthread_barrier_wait (&barrier);
+
+  /* Stop the whole process (all threads) with a real, pending SIGSTOP.
+     When GDB later attaches in non-stop mode against a remote target,
+     each thread is reported with this pending signal.  That is what
+     used to trigger the assertions described in PR gdb/30630.  */
+  raise (SIGSTOP);
+
+  while (1)
+    sleep (1);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/attach-non-stop-stopped.exp b/gdb/testsuite/gdb.threads/attach-non-stop-stopped.exp
new file mode 100644
index 00000000000..af68f8f781a
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/attach-non-stop-stopped.exp
@@ -0,0 +1,104 @@
+# 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/>.
+
+# Regression test for PR gdb/30630.
+#
+# Start gdbserver attached to a multi-threaded inferior that is already
+# stopped (here, via a pending SIGSTOP), then connect GDB to it in
+# non-stop mode.
+
+load_lib gdbserver-support.exp
+
+require allow_gdbserver_tests
+require can_spawn_for_attach
+
+standard_testfile
+
+if {[build_executable "failed to prepare" $testfile $srcfile \
+	 {debug pthreads}] == -1} {
+    return
+}
+
+# Number of threads the test program creates (see N_THREADS in .c file)
+set n_threads 4
+set total [expr {$n_threads + 1}]
+
+# Spawn the program.  It creates its threads and then stops the whole
+# process with a pending SIGSTOP.
+set test_spawn_id [spawn_wait_for_attach $binfile]
+set testpid [spawn_id_get_pid $test_spawn_id]
+
+save_vars { GDBFLAGS } {
+    append GDBFLAGS " -ex \"set non-stop on\""
+
+    if { ![is_remote host] && ![is_remote target] } {
+	append GDBFLAGS " -ex \"set sysroot\""
+    }
+
+    clean_restart $testfile
+}
+
+gdb_test "disconnect" ".*"
+
+# Start gdbserver already attached to the stopped process.
+set res [gdbserver_start "--attach" $testpid]
+if { [llength $res] == 0 } {
+    kill_wait_spawned_process $test_spawn_id
+    unsupported "gdbserver --attach failed"
+    return
+}
+set gdbserver_gdbport [lindex $res 1]
+
+# Connect to gdbserver in non-stop mode.  Without the fix for PR 30630,
+# GDB hits an internal error here.
+set connected 0
+gdb_test_multiple "target remote $gdbserver_gdbport" \
+    "connect to gdbserver in non-stop mode" {
+    -re "Non-stop mode requested, but remote does not support non-stop.*$gdb_prompt $" {
+	unsupported $gdb_test_name
+    }
+    -re "Remote debugging using .*$gdb_prompt $" {
+	pass $gdb_test_name
+	set connected 1
+    }
+}
+
+if { !$connected } {
+    kill_wait_spawned_process $test_spawn_id
+    return
+}
+
+# GDB survived the connection.  As a sanity check, confirm it is
+# responsive and that all threads are present.
+for {set attempt 0} {$attempt < 10} {incr attempt} {
+    set thread_count 0
+    gdb_test_multiple "info threads" "" {
+	-re "\r\n\[ *\]+$decimal\[ \t\]+(Thread|LWP|process)\[^\r\n\]*" {
+	    incr thread_count
+	    exp_continue
+	}
+	-re "$gdb_prompt " {
+	}
+    }
+    if {$thread_count >= $total} {
+	break
+    }
+    sleep 1
+}
+gdb_assert {$thread_count == $total} "all threads present"
+
+gdb_test "detach" "Detaching from.*"
+
+kill_wait_spawned_process $test_spawn_id

base-commit: ddc575edf104eb477018a32b31074beffb179034
-- 
2.43.0


  reply	other threads:[~2026-06-19  9:26 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-11  5:36 [PATCH] " Martin KOCH
2026-06-17  3:44 ` Simon Marchi
2026-06-19  9:25   ` Martin KOCH [this message]
2026-07-08 18:57     ` [PATCH v2] " 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=20260619092535.2476689-1-Martin.KOCH@bachmann.info \
    --to=martin.koch@bachmann.info \
    --cc=gdb-patches@sourceware.org \
    --cc=simark@simark.ca \
    /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