From: Simon Marchi Date: Wed, 1 Dec 2021 14:40:03 +0000 (-0500) Subject: gdb, gdbserver: detach fork child when detaching from fork parent X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=df5ad102009c;p=binutils-gdb.git gdb, gdbserver: detach fork child when detaching from fork parent 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 --- diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 2f8cf498b73..9d4b05a2167 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 d70acdc26c0..1f977d57fba 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 (¬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 @@ -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 00000000000..5b9f9940f83 --- /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 . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 00000000000..9a3ac835eae --- /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 . + +# 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 00000000000..5536381847b --- /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 . */ + +#include + +int +main (void) +{ + FILE *f = fopen (TOUCH_FILE_PATH, "w"); + fclose (f); + return 0; +} diff --git a/gdb/testsuite/gdb.threads/pending-fork-event-detach.c b/gdb/testsuite/gdb.threads/pending-fork-event-detach.c new file mode 100644 index 00000000000..ecfed98fdfd --- /dev/null +++ b/gdb/testsuite/gdb.threads/pending-fork-event-detach.c @@ -0,0 +1,86 @@ +/* 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 . */ + +#include +#include +#include + +static volatile int release_forking_thread = 0; +static int x; +static pthread_barrier_t barrier; + +static void +break_here (void) +{ + x++; +} + +static void +do_fork (void) +{ + while (!release_forking_thread); + + if (FORK_FUNCTION () == 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) + do_fork (); +#else +# error "MAIN_THREAD_FORKS or OTHER_THREAD_FORKS must be defined" +#endif +} + + +int +main (void) +{ + pthread_t thread; + int ret; + + pthread_barrier_init (&barrier, NULL, 2); + + alarm (30); + ret = pthread_create (&thread, NULL, thread_func, NULL); + assert (ret == 0); + + pthread_barrier_wait (&barrier); + +#if defined(MAIN_THREAD_FORKS) + do_fork (); +#elif defined(OTHER_THREAD_FORKS) + break_here (); +#else +# error "MAIN_THREAD_FORKS or OTHER_THREAD_FORKS must be defined" +#endif + + pthread_join (thread, NULL); + + return 0; +} 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 00000000000..433660a07ae --- /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 . + +# 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.c b/gdb/testsuite/gdb.threads/pending-fork-event.c deleted file mode 100644 index a39ca75a49a..00000000000 --- a/gdb/testsuite/gdb.threads/pending-fork-event.c +++ /dev/null @@ -1,82 +0,0 @@ -/* 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 . */ - -#include -#include -#include - -static volatile int release_forking_thread = 0; -static int x; -static pthread_barrier_t barrier; - -static void -break_here (void) -{ - x++; -} - -static void -do_fork (void) -{ - pthread_barrier_wait (&barrier); - - while (!release_forking_thread); - - if (FORK_FUNCTION () == 0) - _exit (0); - -} - -static void * -thread_func (void *p) -{ -#if defined(MAIN_THREAD_FORKS) - break_here (); -#elif defined(OTHER_THREAD_FORKS) - do_fork (); -#else -# error "MAIN_THREAD_FORKS or OTHER_THREAD_FORKS must be defined" -#endif -} - - -int -main (void) -{ - pthread_t thread; - int ret; - - pthread_barrier_init (&barrier, NULL, 2); - - alarm (30); - ret = pthread_create (&thread, NULL, thread_func, NULL); - assert (ret == 0); - - pthread_barrier_wait (&barrier); - -#if defined(MAIN_THREAD_FORKS) - do_fork (); -#elif defined(OTHER_THREAD_FORKS) - break_here (); -#else -# error "MAIN_THREAD_FORKS or OTHER_THREAD_FORKS must be defined" -#endif - - pthread_join (thread, NULL); - - return 0; -} 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 51af07f56bd..00000000000 --- 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 . - -# 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 70fa2b3a801..8b7445b7581 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 34991df449b..87888044c1f 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 819f915ea9a..b563537216a 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 8dde6fb0729..27e2aba0121 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 136b5104de8..aa3d42462f5 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 1b0a1201d75..331a21aa57a 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 ();