[PATCH v3 7/7] gdb, gdbserver: detach fork child when detaching from fork parent
Simon Marchi
simon.marchi@polymtl.ca
Wed Dec 1 14:45:00 GMT 2021
From: Simon Marchi <simon.marchi@efficios.com>
While working with pending fork events, I wondered what would happen if
the user detached an inferior while a thread of that inferior had a
pending fork event. What happens with the fork child, which is
ptrace-attached by the GDB process (or by GDBserver), but not known to
the core? Sure enough, neither the core of GDB or the target detach the
child process, so GDB (or GDBserver) just stays ptrace-attached to the
process. The result is that the fork child process is stuck, while you
would expect it to be detached and run.
Make GDBserver detach of fork children it knows about. That is done in
the generic handle_detach function. Since a process_info already exists
for the child, we can simply call detach_inferior on it.
GDB-side, make the linux-nat and remote targets detach of fork children
known because of pending fork events. These pending fork events can be
stored in:
- thread_info::pending_waitstatus, if the core has consumed the event
but then saved it for later (for example, because it got the event
while stopping all threads, to present an all-stop stop on top of a
non-stop target)
- thread_info::pending_follow: if we ran to a "catch fork" and we
detach at that moment
Additionally, pending fork events can be in target-specific fields:
- For linux-nat, they can be in lwp_info::status and
lwp_info::waitstatus.
- For the remote target, they could be stored as pending stop replies,
saved in `remote_state::notif_state::pending_event`, if not
acknowledged yet, or in `remote_state::stop_reply_queue`, if
acknowledged. I followed the model of remove_new_fork_children for
this: call remote_notif_get_pending_events to process /
acknowledge any unacknowledged notification, then look through
stop_reply_queue.
Update the gdb.threads/pending-fork-event.exp test (and rename it to
gdb.threads/pending-fork-event-detach.exp) to try to detach the process
while it is stopped with a pending fork event. In order to verify that
the fork child process is correctly detached and resumes execution
outside of GDB's control, make that process create a file in the test
output directory, and make the test wait $timeout seconds for that file
to appear (it happens instantly if everything goes well).
This test catches a bug in linux-nat.c, also reported as PR 28512
("waitstatus.h:300: internal-error: gdb_signal target_waitstatus::sig()
const: Assertion `m_kind == TARGET_WAITKIND_STOPPED || m_kind ==
TARGET_WAITKIND_SIGNALLED' failed.). When detaching a thread with a
pending event, get_detach_signal unconditionally fetches the signal
stored in the waitstatus (`tp->pending_waitstatus ().sig ()`). However,
that is only valid if the pending event is of type
TARGET_WAITKIND_STOPPED, and this is now enforced using assertions (iit
would also be valid for TARGET_WAITKIND_SIGNALLED, but that would mean
the thread does not exist anymore, so we wouldn't be detaching it). Add
a condition in get_detach_signal to access the signal number only if the
wait status is of kind TARGET_WAITKIND_STOPPED, and use GDB_SIGNAL_0
instead (since the thread was not stopped with a signal to begin with).
Add another test, gdb.threads/pending-fork-event-ns.exp, specifically to
verify that we consider events in pending stop replies in the remote
target. This test has many threads constantly forking, and we detach
from the program while the program is executing. That gives us some
chance that we detach while a fork stop reply is stored in the remote
target. To verify that we correctly detach all fork children, we ask
the parent to exit by sending it a SIGUSR1 signal and have it write a
file to the filesystem before exiting. Because the parent's main thread
joins the forking threads, and the forking threads wait for their fork
children to exit, if some fork child is not detach by GDB, the parent
will not write the file, and the test will time out. If I remove the
new remote_detach_pid calls in remote.c, the test fails eventually if I
run it in a loop.
There is a known limitation: we don't remove breakpoints from the
children before detaching it. So the children, could hit a trap
instruction after being detached and crash. I know this is wrong, and
it should be fixed, but I would like to handle that later. The current
patch doesn't fix everything, but it's a step in the right direction.
Change-Id: I6d811a56f520e3cb92d5ea563ad38976f92e93dd
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28512
---
gdb/linux-nat.c | 53 +++++++-
gdb/remote.c | 39 +++++-
.../pending-fork-event-detach-ns.c | 98 ++++++++++++++
.../pending-fork-event-detach-ns.exp | 67 +++++++++
.../pending-fork-event-detach-touch-file.c | 26 ++++
...rk-event.c => pending-fork-event-detach.c} | 10 +-
.../gdb.threads/pending-fork-event-detach.exp | 127 ++++++++++++++++++
.../gdb.threads/pending-fork-event.exp | 79 -----------
gdb/testsuite/lib/gdb.exp | 21 +++
gdbserver/linux-low.cc | 11 ++
gdbserver/linux-low.h | 27 ++++
gdbserver/server.cc | 29 ++++
gdbserver/target.cc | 6 +
gdbserver/target.h | 10 ++
14 files changed, 516 insertions(+), 87 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c
create mode 100644 gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp
create mode 100644 gdb/testsuite/gdb.threads/pending-fork-event-detach-touch-file.c
rename gdb/testsuite/gdb.threads/{pending-fork-event.c => pending-fork-event-detach.c} (89%)
create mode 100644 gdb/testsuite/gdb.threads/pending-fork-event-detach.exp
delete mode 100644 gdb/testsuite/gdb.threads/pending-fork-event.exp
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index f00732d3cd0c..5c5069dda795 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 747f9f15af2f..6d83b40be1af 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -5937,6 +5937,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 (¬if_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
@@ -7340,11 +7366,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
@@ -7355,6 +7381,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 000000000000..7580b104345c
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c
@@ -0,0 +1,98 @@
+#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;
+
+ /* 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 (int i = 0; i < NUM_FORKING_THREADS; ++i)
+ {
+ ret = pthread_create (&forking_threads[i], NULL, forking_thread, NULL);
+ assert (ret == 0);
+ }
+
+ for (int i = 0; i < NUM_FORKING_THREADS; ++i)
+ {
+ ret = pthread_join (forking_threads[i], NULL);
+ assert (ret == 0);
+ }
+
+ printf("WRITING FILE\n");
+ FILE *f = fopen (TOUCH_FILE_PATH, "w");
+ assert (f != NULL);
+ ret = fclose (f);
+ assert (ret == 0);
+ printf("KBYE\n");
+
+ 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 000000000000..2e85cd2ba297
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp
@@ -0,0 +1,67 @@
+# 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
+
+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 { } {
+ file delete $::touch_file_path
+ gdb_assert { ![file_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 "continue &" ".*"
+ sleep 2
+ gdb_test "detach" ".*"
+ remote_exec target "kill -USR1 ${pid}"
+ gdb_assert { [file_exists_with_timeout $::touch_file_path] } "file exists after detach"
+}
+
+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 000000000000..5536381847b1
--- /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
similarity index 89%
rename from gdb/testsuite/gdb.threads/pending-fork-event.c
rename to gdb/testsuite/gdb.threads/pending-fork-event-detach.c
index a39ca75a49ac..ecfed98fdfda 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 000000000000..869ea3e60da6
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach.exp
@@ -0,0 +1,127 @@
+# 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
+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
+ }
+
+ file delete $::touch_file_path
+ gdb_assert { ![file_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 { [file_exists_with_timeout $::touch_file_path] } "file exists after detach"
+}
+
+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 51af07f56bdd..000000000000
--- 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 b145fe8e2e83..313f2a932c43 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -8317,5 +8317,26 @@ proc require { fn arg1 {arg2 ""} } {
return -code return 0
}
+# Return 1 if file PATH exists, else 0.
+
+proc file_exists { path } {
+ return [file exists $path]
+}
+
+# Wait up to ::TIMEOUT seconds for file PATH to exist. Return
+# 1 if it does exist, 0 otherwise.
+
+proc file_exists_with_timeout { path } {
+ for {set i 0} {$i < $::timeout} {incr i} {
+ if { [file_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 34991df449bf..87888044c1f8 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 819f915ea9a3..b563537216a6 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -312,6 +312,7 @@ class linux_process_target : public process_stratum_target
#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 03074fde51da..3dea522b2b72 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -1249,6 +1249,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 136b5104de85..aa3d42462f52 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 1b0a1201d755..331a21aa57a3 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -492,6 +492,10 @@ class process_stratum_target
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 ();
--
2.33.1
More information about the Gdb-patches
mailing list