diff options
-rw-r--r-- | gdb/testsuite/gdb.threads/pending-fork-event.c | 82 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/pending-fork-event.exp | 79 | ||||
-rw-r--r-- | gdbserver/linux-low.cc | 11 | ||||
-rw-r--r-- | gdbserver/linux-low.h | 29 | ||||
-rw-r--r-- | gdbserver/server.cc | 6 | ||||
-rw-r--r-- | gdbserver/target.cc | 6 | ||||
-rw-r--r-- | gdbserver/target.h | 10 |
7 files changed, 223 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.threads/pending-fork-event.c b/gdb/testsuite/gdb.threads/pending-fork-event.c new file mode 100644 index 0000000..a39ca75 --- /dev/null +++ b/gdb/testsuite/gdb.threads/pending-fork-event.c @@ -0,0 +1,82 @@ +/* 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 <pthread.h> +#include <unistd.h> +#include <assert.h> + +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 new file mode 100644 index 0000000..51af07f --- /dev/null +++ b/gdb/testsuite/gdb.threads/pending-fork-event.exp @@ -0,0 +1,79 @@ +# 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/gdbserver/linux-low.cc b/gdbserver/linux-low.cc index d214aff..34991df 100644 --- a/gdbserver/linux-low.cc +++ b/gdbserver/linux-low.cc @@ -7119,6 +7119,17 @@ linux_process_target::thread_handle (ptid_t ptid, gdb_byte **handle, } #endif +thread_info * +linux_process_target::thread_pending_parent (thread_info *thread) +{ + lwp_info *parent = get_thread_lwp (thread)->pending_parent (); + + if (parent == nullptr) + return nullptr; + + return get_lwp_thread (parent); +} + /* 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 05067ff..819f915 100644 --- a/gdbserver/linux-low.h +++ b/gdbserver/linux-low.h @@ -311,6 +311,8 @@ public: int *handle_len) override; #endif + thread_info *thread_pending_parent (thread_info *thread) override; + bool supports_catch_syscall () override; /* Return the information to access registers. This has public @@ -721,6 +723,33 @@ struct pending_signal struct lwp_info { + /* If this LWP is a fork child that wasn't reported to GDB yet, return + its parent, else nullptr. */ + lwp_info *pending_parent () 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->fork_relative->status_pending_p) + return nullptr; + + const target_waitstatus &ws + = this->fork_relative->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 b1d4c92..8dde6fb 100644 --- a/gdbserver/server.cc +++ b/gdbserver/server.cc @@ -1656,6 +1656,12 @@ handle_qxfer_threads_worker (thread_info *thread, struct buffer *buffer) gdb_byte *handle; bool handle_status = target_thread_handle (ptid, &handle, &handle_len); + /* If this is a fork or vfork child (has a fork parent), GDB does not yet + know about this process, and must not know about it until it gets the + corresponding (v)fork event. Exclude this thread from the list. */ + if (target_thread_pending_parent (thread) != nullptr) + return; + write_ptid (ptid_s, ptid); buffer_xml_printf (buffer, "<thread id=\"%s\"", ptid_s); diff --git a/gdbserver/target.cc b/gdbserver/target.cc index bfa8605..136b510 100644 --- a/gdbserver/target.cc +++ b/gdbserver/target.cc @@ -835,6 +835,12 @@ process_stratum_target::thread_handle (ptid_t ptid, gdb_byte **handle, return false; } +thread_info * +process_stratum_target::thread_pending_parent (thread_info *thread) +{ + return nullptr; +} + bool process_stratum_target::supports_software_single_step () { diff --git a/gdbserver/target.h b/gdbserver/target.h index 6c863c8..1b0a120 100644 --- a/gdbserver/target.h +++ b/gdbserver/target.h @@ -488,6 +488,10 @@ public: virtual bool thread_handle (ptid_t ptid, gdb_byte **handle, int *handle_len); + /* If THREAD is a fork child that was not reported to GDB, return its parent + else nullptr. */ + virtual thread_info *thread_pending_parent (thread_info *thread); + /* Returns true if the target can software single step. */ virtual bool supports_software_single_step (); @@ -698,6 +702,12 @@ void done_accessing_memory (void); #define target_thread_handle(ptid, handle, handle_len) \ the_target->thread_handle (ptid, handle, handle_len) +static inline thread_info * +target_thread_pending_parent (thread_info *thread) +{ + return the_target->thread_pending_parent (thread); +} + int read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len); int set_desired_thread (); |