aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/linux-nat.c53
-rw-r--r--gdb/remote.c39
-rw-r--r--gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c114
-rw-r--r--gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp121
-rw-r--r--gdb/testsuite/gdb.threads/pending-fork-event-detach-touch-file.c26
-rw-r--r--gdb/testsuite/gdb.threads/pending-fork-event-detach.c (renamed from gdb/testsuite/gdb.threads/pending-fork-event.c)10
-rw-r--r--gdb/testsuite/gdb.threads/pending-fork-event-detach.exp139
-rw-r--r--gdb/testsuite/gdb.threads/pending-fork-event.exp79
-rw-r--r--gdb/testsuite/lib/gdb.exp15
-rw-r--r--gdbserver/linux-low.cc11
-rw-r--r--gdbserver/linux-low.h27
-rw-r--r--gdbserver/server.cc29
-rw-r--r--gdbserver/target.cc6
-rw-r--r--gdbserver/target.h10
14 files changed, 592 insertions, 87 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 2f8cf49..9d4b05a 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1315,7 +1315,16 @@ get_detach_signal (struct lwp_info *lp)
if (target_is_non_stop_p () && !tp->executing ())
{
if (tp->has_pending_waitstatus ())
- signo = tp->pending_waitstatus ().sig ();
+ {
+ /* If the thread has a pending event, and it was stopped with a
+ signal, use that signal to resume it. If it has a pending
+ event of another kind, it was not stopped with a signal, so
+ resume it without a signal. */
+ if (tp->pending_waitstatus ().kind () == TARGET_WAITKIND_STOPPED)
+ signo = tp->pending_waitstatus ().sig ();
+ else
+ signo = GDB_SIGNAL_0;
+ }
else
signo = tp->stop_signal ();
}
@@ -1367,6 +1376,48 @@ detach_one_lwp (struct lwp_info *lp, int *signo_p)
gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
+ /* If the lwp/thread we are about to detach has a pending fork event,
+ there is a process GDB is attached to that the core of GDB doesn't know
+ about. Detach from it. */
+
+ /* Check in lwp_info::status. */
+ if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
+ {
+ int event = linux_ptrace_get_extended_event (lp->status);
+
+ if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+ {
+ unsigned long child_pid;
+ int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
+ if (ret == 0)
+ detach_one_pid (child_pid, 0);
+ else
+ perror_warning_with_name (_("Failed to detach fork child"));
+ }
+ }
+
+ /* Check in lwp_info::waitstatus. */
+ if (lp->waitstatus.kind () == TARGET_WAITKIND_VFORKED
+ || lp->waitstatus.kind () == TARGET_WAITKIND_FORKED)
+ detach_one_pid (lp->waitstatus.child_ptid ().pid (), 0);
+
+
+ /* Check in thread_info::pending_waitstatus. */
+ thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
+ if (tp->has_pending_waitstatus ())
+ {
+ const target_waitstatus &ws = tp->pending_waitstatus ();
+
+ if (ws.kind () == TARGET_WAITKIND_VFORKED
+ || ws.kind () == TARGET_WAITKIND_FORKED)
+ detach_one_pid (ws.child_ptid ().pid (), 0);
+ }
+
+ /* Check in thread_info::pending_follow. */
+ if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
+ || tp->pending_follow.kind () == TARGET_WAITKIND_FORKED)
+ detach_one_pid (tp->pending_follow.child_ptid ().pid (), 0);
+
if (lp->status != 0)
linux_nat_debug_printf ("Pending %s for %s on detach.",
strsignal (WSTOPSIG (lp->status)),
diff --git a/gdb/remote.c b/gdb/remote.c
index d70acdc..1f977d5 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -5968,6 +5968,32 @@ remote_target::remote_detach_1 (inferior *inf, int from_tty)
if (from_tty && !rs->extended && number_of_live_inferiors (this) == 1)
puts_filtered (_("Ending remote debugging.\n"));
+ /* See if any thread of the inferior we are detaching has a pending fork
+ status. In that case, we must detach from the child resulting from
+ that fork. */
+ for (thread_info *thread : inf->non_exited_threads ())
+ {
+ const target_waitstatus *ws = thread_pending_fork_status (thread);
+
+ if (ws == nullptr)
+ continue;
+
+ remote_detach_pid (ws->child_ptid ().pid ());
+ }
+
+ /* Check also for any pending fork events in the stop reply queue. */
+ remote_notif_get_pending_events (&notif_client_stop);
+ for (stop_reply_up &reply : rs->stop_reply_queue)
+ {
+ if (reply->ptid.pid () != pid)
+ continue;
+
+ if (!is_fork_status (reply->ws.kind ()))
+ continue;
+
+ remote_detach_pid (reply->ws.child_ptid ().pid ());
+ }
+
thread_info *tp = find_thread_ptid (this, inferior_ptid);
/* Check to see if we are detaching a fork parent. Note that if we
@@ -7371,11 +7397,11 @@ remote_target::discard_pending_stop_replies (struct inferior *inf)
/* Leave the notification pending, since the server expects that
we acknowledge it with vStopped. But clear its contents, so
that later on when we acknowledge it, we also discard it. */
+ remote_debug_printf
+ ("discarding in-flight notification: ptid: %s, ws: %s\n",
+ reply->ptid.to_string().c_str(),
+ reply->ws.to_string ().c_str ());
reply->ws.set_ignore ();
-
- if (remote_debug)
- fprintf_unfiltered (gdb_stdlog,
- "discarded in-flight notification\n");
}
/* Discard the stop replies we have already pulled with
@@ -7386,6 +7412,11 @@ remote_target::discard_pending_stop_replies (struct inferior *inf)
{
return event->ptid.pid () == inf->pid;
});
+ for (auto it = iter; it != rs->stop_reply_queue.end (); ++it)
+ remote_debug_printf
+ ("discarding queued stop reply: ptid: %s, ws: %s\n",
+ reply->ptid.to_string().c_str(),
+ reply->ws.to_string ().c_str ());
rs->stop_reply_queue.erase (iter, rs->stop_reply_queue.end ());
}
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c b/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c
new file mode 100644
index 0000000..5b9f994
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c
@@ -0,0 +1,114 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2021 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 <errno.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define NUM_FORKING_THREADS 12
+
+static pthread_barrier_t barrier;
+static volatile int should_exit = 0;
+
+static void
+sigusr1_handler (int sig, siginfo_t *siginfo, void *context)
+{
+ should_exit = 1;
+}
+
+static void *
+forking_thread (void *arg)
+{
+ /* Wait for all forking threads to have spawned before fork-spamming. */
+ pthread_barrier_wait (&barrier);
+
+ while (!should_exit)
+ {
+ pid_t pid = fork ();
+ if (pid == 0)
+ {
+ /* Child */
+ exit (8);
+ }
+ else
+ {
+ /* Parent */
+ int status;
+ int ret = waitpid (pid, &status, 0);
+ assert (ret == pid);
+ assert (WIFEXITED (status));
+ assert (WEXITSTATUS (status) == 8);
+ }
+ }
+
+ return NULL;
+}
+
+static void
+break_here_first (void)
+{
+}
+
+static pid_t my_pid;
+
+int
+main (void)
+{
+ pthread_t forking_threads[NUM_FORKING_THREADS];
+ int ret;
+ struct sigaction sa;
+ int i;
+
+ /* Just to make sure we don't run for ever. */
+ alarm (30);
+
+ my_pid = getpid ();
+
+ break_here_first ();
+
+ pthread_barrier_init (&barrier, NULL, NUM_FORKING_THREADS);
+
+ memset (&sa, 0, sizeof (sa));
+ sa.sa_sigaction = sigusr1_handler;
+ ret = sigaction (SIGUSR1, &sa, NULL);
+ assert (ret == 0);
+
+ for (i = 0; i < NUM_FORKING_THREADS; ++i)
+ {
+ ret = pthread_create (&forking_threads[i], NULL, forking_thread, NULL);
+ assert (ret == 0);
+ }
+
+ for (i = 0; i < NUM_FORKING_THREADS; ++i)
+ {
+ ret = pthread_join (forking_threads[i], NULL);
+ assert (ret == 0);
+ }
+
+ FILE *f = fopen (TOUCH_FILE_PATH, "w");
+ assert (f != NULL);
+ ret = fclose (f);
+ assert (ret == 0);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp b/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp
new file mode 100644
index 0000000..9a3ac83
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp
@@ -0,0 +1,121 @@
+# Copyright 2021 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/>.
+
+# Detach a running program that constantly forks, verify that we correctly
+# detach all fork children, for which events are pending.
+#
+# The strategy is:
+#
+# - Resume a program in background (continue &) with many threads that
+# constantly fork and wait for their fork children to exit.
+# - Detach the program. If testing against GDBserver, hope that the detach
+# CLI command is processed while there is a stop reply pending in the
+# remote target.
+# - Signal the parent program to exit, by sending it a SIGUSR1 signal.
+# - Have the parent write a flag file to the filesystem just before exiting.
+# - If a pending fork child is mistakenly still attached, it will prevent its
+# parent thread from waitpid'ing it, preventing the main thread from joining
+# it, prevent it from writing the flag file, failing the test.
+
+standard_testfile
+
+if { [is_remote target] } {
+ # If the target is remote, write the file in whatever the current working
+ # directory is, with a somewhat unique name.
+ set touch_file_path ${testfile}-flag
+} else {
+ set touch_file_path [standard_output_file flag]
+}
+
+if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \
+ executable [list debug "additional_flags=-DTOUCH_FILE_PATH=\"$touch_file_path\""]] != "" } {
+ return
+}
+
+proc do_test { } {
+ remote_file target delete $::touch_file_path
+ gdb_assert { ![remote_file target exists $::touch_file_path] } "file does not exist before test"
+
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"set non-stop on\""
+ clean_restart $::binfile
+ }
+
+ if { ![runto break_here_first] } {
+ return
+ }
+
+ set pid [get_integer_valueof "my_pid" -1]
+ if { $pid == -1 } {
+ error "could not get inferior pid"
+ }
+
+ gdb_test_no_output "set print inferior-events off"
+
+ gdb_test_multiple "continue &" "" {
+ -re "Continuing.\r\n$::gdb_prompt " {
+ pass $gdb_test_name
+ }
+ }
+
+ set wait_time_s 2
+
+ if { [info exists ::server_spawn_id] } {
+ # Let the program run for 2 seconds, during which it will fork many times.
+ # When running against GDBserver, this makes server print a ton of
+ # "Detaching from process X" message, to the point where its output buffer
+ # gets full and it hangs in a write to stdout. During these 2 seconds,
+ # drain the messages from GDBserver to keep that from happening.
+ gdb_test_multiple "" "flush server output" {
+ -timeout $wait_time_s
+ -i $::server_spawn_id
+ -re ".+" {
+ exp_continue -continue_timer
+ }
+
+ timeout {
+ pass $gdb_test_name
+ }
+ }
+ } else {
+ # Not using GDBserver, just sleep 2 seconds.
+ sleep $wait_time_s
+ }
+
+ gdb_test "detach" "Detaching from program: .*"
+
+ if { [info exists ::server_spawn_id] } {
+ # Drain GDBserver's output buffer, in the (unlikely) event that enough
+ # messages were output to fill the buffer between the moment we stopped
+ # consuming it and the moment GDBserver detached the process.
+ gdb_test_multiple "" "" {
+ -i $::server_spawn_id
+ -re ".+" {
+ exp_continue
+ }
+ -re "^$" {}
+ }
+ }
+
+ remote_exec target "kill -USR1 ${pid}"
+ gdb_assert { [target_file_exists_with_timeout $::touch_file_path] } "file exists after detach"
+
+ # Don't leave random files on the target system.
+ if { [is_remote target] } {
+ remote_file target delete $::touch_file_path
+ }
+}
+
+do_test
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event-detach-touch-file.c b/gdb/testsuite/gdb.threads/pending-fork-event-detach-touch-file.c
new file mode 100644
index 0000000..5536381
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach-touch-file.c
@@ -0,0 +1,26 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2021 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 <stdio.h>
+
+int
+main (void)
+{
+ FILE *f = fopen (TOUCH_FILE_PATH, "w");
+ fclose (f);
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event.c b/gdb/testsuite/gdb.threads/pending-fork-event-detach.c
index a39ca75..ecfed98 100644
--- a/gdb/testsuite/gdb.threads/pending-fork-event.c
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach.c
@@ -32,18 +32,22 @@ break_here (void)
static void
do_fork (void)
{
- pthread_barrier_wait (&barrier);
-
while (!release_forking_thread);
if (FORK_FUNCTION () == 0)
- _exit (0);
+ {
+ /* We create the file in a separate program that we exec: if FORK_FUNCTION
+ is vfork, we shouldn't do anything more than an exec. */
+ execl (TOUCH_FILE_BIN, TOUCH_FILE_BIN, NULL);
+ }
}
static void *
thread_func (void *p)
{
+ pthread_barrier_wait (&barrier);
+
#if defined(MAIN_THREAD_FORKS)
break_here ();
#elif defined(OTHER_THREAD_FORKS)
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event-detach.exp b/gdb/testsuite/gdb.threads/pending-fork-event-detach.exp
new file mode 100644
index 0000000..433660a
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach.exp
@@ -0,0 +1,139 @@
+# Copyright (C) 2021 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/>.
+
+# Then, test that if we detach an inferior with a pending fork child, that
+# child is correctly detached and resumes execution normally. There are two
+# kinds of "pending fork child" we test:
+#
+# - resulting of a fork catchpoint: we stop at a fork catchpoint and detach.
+# - resulting of an all-stop stop on top of a non-stop target, where a fork
+# event is saved as a pending wait status. To test this, we stepi a thread
+# while another one forks. The stepi generally completes at least as fast
+# as the fork, so we have a chance that the stop due to the stepi being
+# complete is shown to the user while the fork event is saved for later.
+#
+# To verify that the child process is detached and resumes execution, we have
+# it write a file on the filesystem. If we don't see the file after a certain
+# delay, it means the child was likely not detached, and the test fails.
+#
+# At the same time, this tests that having this pending fork event does not
+# cause other problems in general. For example, a buggy GDB / GDBserver combo
+# would notice the thread of the child process of the (still unprocessed) fork
+# event, and erroneously create a new inferior for it. Once fixed, the child
+# process' thread is hidden by whoever holds the pending fork event.
+
+standard_testfile .c -touch-file.c
+
+set touch_file_bin $binfile-touch-file
+
+if { [is_remote target] } {
+ # If the target is remote, write the file in whatever the current working
+ # directory is, with a somewhat unique name.
+ set touch_file_path ${testfile}-flag
+} else {
+ set touch_file_path [standard_output_file flag]
+}
+
+set opts [list debug "additional_flags=-DTOUCH_FILE_PATH=\"$touch_file_path\""]
+if { [gdb_compile "$srcdir/$subdir/$srcfile2" $touch_file_bin executable $opts] != "" } {
+ return
+}
+
+proc do_test { target-non-stop who_forks fork_function stop_mode } {
+ set opts [list \
+ debug \
+ "additional_flags=-DFORK_FUNCTION=$fork_function" \
+ "additional_flags=-DTOUCH_FILE_BIN=\"$::touch_file_bin\""]
+
+ # WHO_FORKS says which of the main or other thread calls (v)fork. The
+ # thread that does not call (v)fork is the one who tries to step.
+ if { $who_forks == "main" } {
+ lappend opts "additional_flags=-DMAIN_THREAD_FORKS"
+ set this_binfile ${::binfile}-main-${fork_function}
+ } elseif { $who_forks == "other" } {
+ lappend opts "additional_flags=-DOTHER_THREAD_FORKS"
+ set this_binfile ${::binfile}-other-${fork_function}
+ } else {
+ error "invalid who_forks value: $who_forks"
+ }
+
+ if { [gdb_compile_pthreads "$::srcdir/$::subdir/$::srcfile" $this_binfile executable $opts] != "" } {
+ return
+ }
+
+ remote_file target delete $::touch_file_path
+ gdb_assert { ![remote_file target exists $::touch_file_path] } "file does not exist before test"
+
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
+ clean_restart $this_binfile
+ }
+
+ if {![runto_main]} {
+ fail "could not run to main"
+ return
+ }
+
+ # Run until breakpoint in the second thread.
+ gdb_test "break break_here" "Breakpoint $::decimal.*"
+ gdb_continue_to_breakpoint "thread started"
+
+ # Delete the breakpoint so the thread doesn't do a step-over.
+ delete_breakpoints
+
+ # Let the forking thread make progress during the step.
+ gdb_test "p release_forking_thread = 1" " = 1"
+
+ # There are two "pending fork child" modes we can test here:
+ #
+ # - catch: set up a "catch fork" / "catch vfork" and run to it.
+ # - stepi: stepi the non-forking thread while the forking thread,
+ # well, forks.
+ if { $stop_mode == "catch" } {
+ gdb_test "catch fork"
+ gdb_test "catch vfork"
+ gdb_test "continue" "hit Catchpoint $::decimal.*fork.*"
+ } elseif { $stop_mode == "stepi" } {
+ # stepi the non-forking thread.
+ gdb_test "stepi"
+ } else {
+ error "invalid stop_mode value: $stop_mode"
+ }
+
+ # Make sure there's still a single inferior.
+ gdb_test "info inferior" {\* 1 [^\r\n]+}
+
+ gdb_test "detach"
+
+ # After being detached, the fork child creates file ::TOUCH_FILE_PATH.
+ # Seeing this file tells us the fork child was detached and executed
+ # successfully.
+ gdb_assert { [target_file_exists_with_timeout $::touch_file_path] } "file exists after detach"
+
+ # Don't leave random files on the target system.
+ if { [is_remote target] } {
+ remote_file target delete $::touch_file_path
+ }
+}
+
+foreach_with_prefix target-non-stop { auto on off } {
+ foreach_with_prefix who_forks { main other } {
+ foreach_with_prefix fork_function { fork vfork } {
+ foreach_with_prefix stop_mode { stepi catch } {
+ do_test ${target-non-stop} $who_forks $fork_function $stop_mode
+ }
+ }
+ }
+}
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event.exp b/gdb/testsuite/gdb.threads/pending-fork-event.exp
deleted file mode 100644
index 51af07f..0000000
--- a/gdb/testsuite/gdb.threads/pending-fork-event.exp
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (C) 2021 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 we handle well, in all-stop, a fork happening in a thread B while
-# doing a step-like command in a thread A.
-#
-# A buggy GDB / GDBserver combo would notice the thread of the child process
-# of the (still unprocessed) fork event, and erroneously create a new inferior
-# for it. Once fixed, the child process' thread is hidden by whoever holds the
-# pending fork event.
-
-standard_testfile
-
-proc do_test { target-non-stop who_forks fork_function } {
-
- set opts [list debug "additional_flags=-DFORK_FUNCTION=$fork_function"]
-
- # WHO_FORKS says which of the main or other thread calls (v)fork. The
- # thread that does not call (v)fork is the one who tries to step.
- if { $who_forks == "main" } {
- lappend opts "additional_flags=-DMAIN_THREAD_FORKS"
- set this_binfile ${::binfile}-main-${fork_function}
- } elseif { $who_forks == "other" } {
- lappend opts "additional_flags=-DOTHER_THREAD_FORKS"
- set this_binfile ${::binfile}-other-${fork_function}
- } else {
- error "invalid who_forks value: $who_forks"
- }
-
- if { [gdb_compile_pthreads "$::srcdir/$::subdir/$::srcfile" $this_binfile executable $opts] != "" } {
- return
- }
-
- save_vars { ::GDBFLAGS } {
- append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
- clean_restart $this_binfile
- }
-
- if {![runto_main]} {
- fail "could not run to main"
- return
- }
-
- # Run until breakpoint in the second thread.
- gdb_test "break break_here" "Breakpoint $::decimal.*"
- gdb_continue_to_breakpoint "thread started"
-
- # Delete the breakpoint so the thread doesn't do a step-over.
- delete_breakpoints
-
- # Let the forking thread make progress during the step.
- gdb_test "p release_forking_thread = 1" " = 1"
-
- # stepi the non-forking thread.
- gdb_test "stepi"
-
- # Make sure there's still a single inferior.
- gdb_test "info inferior" {\* 1 [^\r\n]+}
-}
-
-foreach_with_prefix target-non-stop { auto on off } {
- foreach_with_prefix who_forks { main other } {
- foreach_with_prefix fork_function { fork vfork } {
- do_test ${target-non-stop} $who_forks $fork_function
- }
- }
-}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 70fa2b3..8b7445b 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -8317,5 +8317,20 @@ proc require { fn arg1 {arg2 ""} } {
return -code return 0
}
+# Wait up to ::TIMEOUT seconds for file PATH to exist on the target system.
+# Return 1 if it does exist, 0 otherwise.
+
+proc target_file_exists_with_timeout { path } {
+ for {set i 0} {$i < $::timeout} {incr i} {
+ if { [remote_file target exists $path] } {
+ return 1
+ }
+
+ sleep 1
+ }
+
+ return 0
+}
+
# Always load compatibility stuff.
load_lib future.exp
diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 34991df..8788804 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -7130,6 +7130,17 @@ linux_process_target::thread_pending_parent (thread_info *thread)
return get_lwp_thread (parent);
}
+thread_info *
+linux_process_target::thread_pending_child (thread_info *thread)
+{
+ lwp_info *child = get_thread_lwp (thread)->pending_child ();
+
+ if (child == nullptr)
+ return nullptr;
+
+ return get_lwp_thread (child);
+}
+
/* Default implementation of linux_target_ops method "set_pc" for
32-bit pc register which is literally named "pc". */
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index 819f915..b563537 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -312,6 +312,7 @@ public:
#endif
thread_info *thread_pending_parent (thread_info *thread) override;
+ thread_info *thread_pending_child (thread_info *thread) override;
bool supports_catch_syscall () override;
@@ -750,6 +751,32 @@ struct lwp_info
return this->fork_relative;
}
+ /* If this LWP is the parent of a fork child we haven't reported to GDB yet,
+ return that child, else nullptr. */
+ lwp_info *pending_child () const
+ {
+ if (this->fork_relative == nullptr)
+ return nullptr;
+
+ gdb_assert (this->fork_relative->fork_relative == this);
+
+ /* In a fork parent/child relationship, the parent has a status pending and
+ the child does not, and a thread can only be in one such relationship
+ at most. So we can recognize who is the parent based on which one has
+ a pending status. */
+ gdb_assert (!!this->status_pending_p
+ != !!this->fork_relative->status_pending_p);
+
+ if (!this->status_pending_p)
+ return nullptr;
+
+ const target_waitstatus &ws = this->waitstatus;
+ gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
+ || ws.kind () == TARGET_WAITKIND_VFORKED);
+
+ return this->fork_relative;
+ }
+
/* Backlink to the parent object. */
struct thread_info *thread = nullptr;
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 8dde6fb..27e2aba 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -1250,6 +1250,35 @@ handle_detach (char *own_buf)
/* We'll need this after PROCESS has been destroyed. */
int pid = process->pid;
+ /* If this process has an unreported fork child, that child is not known to
+ GDB, so GDB won't take care of detaching it. We must do it here.
+
+ Here, we specifically don't want to use "safe iteration", as detaching
+ another process might delete the next thread in the iteration, which is
+ the one saved by the safe iterator. We will never delete the currently
+ iterated on thread, so standard iteration should be safe. */
+ for (thread_info *thread : all_threads)
+ {
+ /* Only threads that are of the process we are detaching. */
+ if (thread->id.pid () != pid)
+ continue;
+
+ /* Only threads that have a pending fork event. */
+ thread_info *child = target_thread_pending_child (thread);
+ if (child == nullptr)
+ continue;
+
+ process_info *fork_child_process = get_thread_process (child);
+ gdb_assert (fork_child_process != nullptr);
+
+ int fork_child_pid = fork_child_process->pid;
+
+ if (detach_inferior (fork_child_process) != 0)
+ warning (_("Failed to detach fork child %s, child of %s"),
+ target_pid_to_str (ptid_t (fork_child_pid)).c_str (),
+ target_pid_to_str (thread->id).c_str ());
+ }
+
if (detach_inferior (process) != 0)
write_enn (own_buf);
else
diff --git a/gdbserver/target.cc b/gdbserver/target.cc
index 136b510..aa3d424 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -841,6 +841,12 @@ process_stratum_target::thread_pending_parent (thread_info *thread)
return nullptr;
}
+thread_info *
+process_stratum_target::thread_pending_child (thread_info *thread)
+{
+ return nullptr;
+}
+
bool
process_stratum_target::supports_software_single_step ()
{
diff --git a/gdbserver/target.h b/gdbserver/target.h
index 1b0a120..331a21a 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -492,6 +492,10 @@ public:
else nullptr. */
virtual thread_info *thread_pending_parent (thread_info *thread);
+ /* If THREAD is the parent of a fork child that was not reported to GDB,
+ return this child, else nullptr. */
+ virtual thread_info *thread_pending_child (thread_info *thread);
+
/* Returns true if the target can software single step. */
virtual bool supports_software_single_step ();
@@ -708,6 +712,12 @@ target_thread_pending_parent (thread_info *thread)
return the_target->thread_pending_parent (thread);
}
+static inline thread_info *
+target_thread_pending_child (thread_info *thread)
+{
+ return the_target->thread_pending_child (thread);
+}
+
int read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len);
int set_desired_thread ();