aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/linux-nat.c24
-rw-r--r--gdb/remote.c27
-rw-r--r--gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c61
-rw-r--r--gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp140
-rw-r--r--gdbserver/mem-break.cc11
5 files changed, 255 insertions, 8 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 9148dda..1c9756c 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1420,10 +1420,12 @@ linux_nat_target::detach (inferior *inf, int from_tty)
iterate_over_lwps (ptid_t (pid), detach_callback);
- /* Only the initial process should be left right now. */
- gdb_assert (num_lwps (pid) == 1);
-
- main_lwp = find_lwp_pid (ptid_t (pid));
+ /* We have detached from everything except the main thread now, so
+ should only have one thread left. However, in non-stop mode the
+ main thread might have exited, in which case we'll have no threads
+ left. */
+ gdb_assert (num_lwps (pid) == 1
+ || (target_is_non_stop_p () && num_lwps (pid) == 0));
if (forks_exist_p ())
{
@@ -1437,10 +1439,18 @@ linux_nat_target::detach (inferior *inf, int from_tty)
{
target_announce_detach (from_tty);
- /* Pass on any pending signal for the last LWP. */
- int signo = get_detach_signal (main_lwp);
+ /* In non-stop mode it is possible that the main thread has exited,
+ in which case we don't try to detach. */
+ main_lwp = find_lwp_pid (ptid_t (pid));
+ if (main_lwp != nullptr)
+ {
+ /* Pass on any pending signal for the last LWP. */
+ int signo = get_detach_signal (main_lwp);
- detach_one_lwp (main_lwp, &signo);
+ detach_one_lwp (main_lwp, &signo);
+ }
+ else
+ gdb_assert (target_is_non_stop_p ());
detach_success (inf);
}
diff --git a/gdb/remote.c b/gdb/remote.c
index b58dbd4..6927104 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -6165,7 +6165,32 @@ remote_target::remote_detach_pid (int pid)
else if (rs->buf[0] == '\0')
error (_("Remote doesn't know how to detach"));
else
- error (_("Can't detach process."));
+ {
+ /* It is possible that we have an unprocessed exit event for this
+ pid. If this is the case then we can ignore the failure to detach
+ and just pretend that the detach worked, as far as the user is
+ concerned, the process exited immediately after the detach. */
+ bool process_has_already_exited = false;
+ remote_notif_get_pending_events (&notif_client_stop);
+ for (stop_reply_up &reply : rs->stop_reply_queue)
+ {
+ if (reply->ptid.pid () != pid)
+ continue;
+
+ enum target_waitkind kind = reply->ws.kind ();
+ if (kind == TARGET_WAITKIND_EXITED
+ || kind == TARGET_WAITKIND_SIGNALLED)
+ {
+ process_has_already_exited = true;
+ remote_debug_printf
+ ("detach failed, but process already exited");
+ break;
+ }
+ }
+
+ if (!process_has_already_exited)
+ error (_("can't detach process: %s"), (char *) rs->buf.data ());
+ }
}
/* This detaches a program to which we previously attached, using
diff --git a/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c
new file mode 100644
index 0000000..5335842
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c
@@ -0,0 +1,61 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2023 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 <unistd.h>
+
+/* This is set from GDB to allow the main thread to exit. */
+
+volatile int dont_exit_just_yet = 1;
+
+/* Somewhere to place a breakpoint. */
+
+void
+breakpt ()
+{
+ /* Just spin. When the test is started under GDB we never enter the spin
+ loop, but when we attach, the worker thread will be spinning here. */
+ while (dont_exit_just_yet)
+ sleep (1);
+}
+
+/* Thread function, doesn't do anything but hit a breakpoint. */
+
+void *
+thread_worker (void *arg)
+{
+ breakpt ();
+ return NULL;
+}
+
+int
+main ()
+{
+ pthread_t thr;
+
+ alarm (300);
+
+ /* Create a thread. */
+ pthread_create (&thr, NULL, thread_worker, NULL);
+ pthread_detach (thr);
+
+ /* Spin until GDB releases us. */
+ while (dont_exit_just_yet)
+ sleep (1);
+
+ _exit (0);
+}
diff --git a/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp
new file mode 100644
index 0000000..83f5e85
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp
@@ -0,0 +1,140 @@
+# Copyright 2023 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/>.
+
+# Check for a race condition where in non-stop mode, the user might
+# have a thread other than the main (original) thread selected and use
+# the 'detach' command.
+#
+# As GDB tries to detach it is possible that the main thread might
+# exit, the main thread is still running due to non-stop mode.
+#
+# GDB used to assume that the main thread would always exist when
+# processing the detach, clearly this isn't the case, and this
+# assumption would lead to assertion failures and segfaults.
+#
+# Triggering the precise timing is pretty hard, we need the main
+# thread to exit after the user has entered the 'detach' command, but
+# before GDB enters the detach implementation and stops all threads,
+# the window of opportunity for this bug is actually tiny.
+#
+# However, we can trigger this bug 100% from Python, as GDB's
+# event-loop only kicks in once we return from a Python function.
+# Thus, if we have a single Python function that causes the main
+# thread to exit, and then calls detach GDB will not have a chance to
+# handle the main thread exiting before entering the detach code.
+
+standard_testfile
+
+require allow_python_tests
+
+if {[build_executable "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] == -1} {
+ return -1
+}
+
+# Run the test. When SPAWN_INFERIOR is true the inferior is started
+# as a separate process which GDB then attaches too. When
+# SPAWN_INFERIOR is false the inferior is started directly within GDB.
+
+proc run_test { spawn_inferior } {
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"set non-stop on\""
+ clean_restart $::binfile
+ }
+
+ # Setup the inferior. When complete the main thread (#1) will
+ # still be running (due to non-stop mode), while the worker thread
+ # (#2) will be stopped.
+ #
+ # There are two setup modes, when SPAWN_INFERIOR is true we span a
+ # separate process and attach to it, after the attach both threads
+ # are stopped, so it is necessary to resume thread #1.
+ #
+ # When SPAWN_INFERIOR is false we just start the inferior within
+ # GDB, in this case we place a breakpoint that will be hit by
+ # thread #2. When the breakpoint is hit thread #1 will remain
+ # running.
+ if {$spawn_inferior} {
+ set test_spawn_id [spawn_wait_for_attach $::binfile]
+ set testpid [spawn_id_get_pid $test_spawn_id]
+
+ set escapedbinfile [string_to_regexp $::binfile]
+ gdb_test -no-prompt-anchor "attach $testpid" \
+ "Attaching to program.*`?$escapedbinfile'?, process $testpid.*" \
+ "attach to the inferior"
+
+ # Attaching to a multi-threaded application in non-stop mode
+ # can result in thread stops being reported after the prompt
+ # is displayed.
+ #
+ # Send a simple command now just to resync the command prompt.
+ gdb_test "p 1 + 2" " = 3"
+
+ # Set thread 1 (the current thread) running again.
+ gdb_test "continue&"
+ } else {
+ if {![runto_main]} {
+ return -1
+ }
+
+ gdb_breakpoint "breakpt"
+ gdb_continue_to_breakpoint "run to breakpoint"
+ }
+
+ # Switch to thread 2.
+ gdb_test "thread 2" \
+ [multi_line \
+ "Switching to thread 2\[^\r\n\]*" \
+ "#0\\s+.*"]
+
+ # Create a Python function that sets a variable in the inferior and
+ # then detaches. Setting the variable in the inferior will allow the
+ # main thread to exit, we even sleep for a short while in order to
+ # give the inferior a chance to exit.
+ #
+ # However, we don't want GDB to notice the exit before we call detach,
+ # which is why we perform both these actions from a Python function.
+ gdb_test_multiline "Create worker function" \
+ "python" "" \
+ "import time" "" \
+ "def set_and_detach():" "" \
+ " gdb.execute(\"set variable dont_exit_just_yet=0\")" "" \
+ " time.sleep(1)" "" \
+ " gdb.execute(\"detach\")" "" \
+ "end" ""
+
+ # The Python function performs two actions, the first causes the
+ # main thread to exit, while the second detaches from the inferior.
+ #
+ # In both cases the stop arrives while GDB is processing the
+ # detach, however, for remote targets GDB doesn't report the stop,
+ # while for local targets GDB does report the stop.
+ if {![gdb_is_target_remote]} {
+ set stop_re "\\\[Thread.*exited\\\]\r\n"
+ } else {
+ set stop_re ""
+ }
+ gdb_test "python set_and_detach()" \
+ "${stop_re}\\\[Inferior.*detached\\\]"
+}
+
+foreach_with_prefix spawn_inferior { true false } {
+ if {$spawn_inferior && ![can_spawn_for_attach]} {
+ # If spawning (and attaching too) a separate inferior is not
+ # supported for the current board, then skip this test.
+ continue
+ }
+
+ run_test $spawn_inferior
+}
diff --git a/gdbserver/mem-break.cc b/gdbserver/mem-break.cc
index 897b9a2..3bee8bc8 100644
--- a/gdbserver/mem-break.cc
+++ b/gdbserver/mem-break.cc
@@ -976,6 +976,17 @@ static struct gdb_breakpoint *
find_gdb_breakpoint (char z_type, CORE_ADDR addr, int kind)
{
struct process_info *proc = current_process ();
+
+ /* In some situations the current process exits, we inform GDB, but
+ before GDB can acknowledge that the process has exited GDB tries to
+ detach from the inferior. As part of the detach process GDB will
+ remove all breakpoints, which means we can end up here when the
+ current process has already exited and so PROC is nullptr. In this
+ case just claim we can't find (and so delete) the breakpoint, GDB
+ will ignore this error during detach. */
+ if (proc == nullptr)
+ return nullptr;
+
struct breakpoint *bp;
enum bkpt_type type = Z_packet_to_bkpt_type (z_type);