aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog11
-rw-r--r--gdb/linux-nat.c97
-rw-r--r--gdb/testsuite/ChangeLog7
-rw-r--r--gdb/testsuite/gdb.threads/fork-plus-threads.c115
-rw-r--r--gdb/testsuite/gdb.threads/fork-plus-threads.exp69
5 files changed, 243 insertions, 56 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 4d604de..40403f9 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,14 @@
+2015-07-30 Pedro Alves <palves@redhat.com>
+ Simon Marchi <simon.marchi@ericsson.com>
+
+ PR threads/18600
+ * linux-nat.c (linux_handle_extended_wait): On CLONE event, always
+ mark the new thread as resumed. Remove STOPPING parameter.
+ (wait_lwp): Adjust call to linux_handle_extended_wait.
+ (linux_nat_filter_event): Adjust call to
+ linux_handle_extended_wait.
+ (resume_stopped_resumed_lwps): Add debug output.
+
2015-07-30 Pierre Langlois <pierre.langlois@arm.com>
* arch-utils.c (default_fast_tracepoint_valid_at): Remove unused
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index b33abb0..966c6a8 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -2000,8 +2000,7 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
true, the new LWP remains stopped, otherwise it is continued. */
static int
-linux_handle_extended_wait (struct lwp_info *lp, int status,
- int stopping)
+linux_handle_extended_wait (struct lwp_info *lp, int status)
{
int pid = ptid_get_lwp (lp->ptid);
struct target_waitstatus *ourstatus = &lp->waitstatus;
@@ -2071,7 +2070,7 @@ linux_handle_extended_wait (struct lwp_info *lp, int status,
ourstatus->kind = TARGET_WAITKIND_FORKED;
else if (event == PTRACE_EVENT_VFORK)
ourstatus->kind = TARGET_WAITKIND_VFORKED;
- else
+ else if (event == PTRACE_EVENT_CLONE)
{
struct lwp_info *new_lp;
@@ -2086,43 +2085,7 @@ linux_handle_extended_wait (struct lwp_info *lp, int status,
new_lp = add_lwp (ptid_build (ptid_get_pid (lp->ptid), new_pid, 0));
new_lp->cloned = 1;
new_lp->stopped = 1;
-
- if (WSTOPSIG (status) != SIGSTOP)
- {
- /* This can happen if someone starts sending signals to
- the new thread before it gets a chance to run, which
- have a lower number than SIGSTOP (e.g. SIGUSR1).
- This is an unlikely case, and harder to handle for
- fork / vfork than for clone, so we do not try - but
- we handle it for clone events here. We'll send
- the other signal on to the thread below. */
-
- new_lp->signalled = 1;
- }
- else
- {
- struct thread_info *tp;
-
- /* When we stop for an event in some other thread, and
- pull the thread list just as this thread has cloned,
- we'll have seen the new thread in the thread_db list
- before handling the CLONE event (glibc's
- pthread_create adds the new thread to the thread list
- before clone'ing, and has the kernel fill in the
- thread's tid on the clone call with
- CLONE_PARENT_SETTID). If that happened, and the core
- had requested the new thread to stop, we'll have
- killed it with SIGSTOP. But since SIGSTOP is not an
- RT signal, it can only be queued once. We need to be
- careful to not resume the LWP if we wanted it to
- stop. In that case, we'll leave the SIGSTOP pending.
- It will later be reported as GDB_SIGNAL_0. */
- tp = find_thread_ptid (new_lp->ptid);
- if (tp != NULL && tp->stop_requested)
- new_lp->last_resume_kind = resume_stop;
- else
- status = 0;
- }
+ new_lp->resumed = 1;
/* If the thread_db layer is active, let it record the user
level thread id and status, and add the thread to GDB's
@@ -2136,19 +2099,23 @@ linux_handle_extended_wait (struct lwp_info *lp, int status,
}
/* Even if we're stopping the thread for some reason
- internal to this module, from the user/frontend's
- perspective, this new thread is running. */
+ internal to this module, from the perspective of infrun
+ and the user/frontend, this new thread is running until
+ it next reports a stop. */
set_running (new_lp->ptid, 1);
- if (!stopping)
- {
- set_executing (new_lp->ptid, 1);
- /* thread_db_attach_lwp -> lin_lwp_attach_lwp forced
- resume_stop. */
- new_lp->last_resume_kind = resume_continue;
- }
+ set_executing (new_lp->ptid, 1);
- if (status != 0)
+ if (WSTOPSIG (status) != SIGSTOP)
{
+ /* This can happen if someone starts sending signals to
+ the new thread before it gets a chance to run, which
+ have a lower number than SIGSTOP (e.g. SIGUSR1).
+ This is an unlikely case, and harder to handle for
+ fork / vfork than for clone, so we do not try - but
+ we handle it for clone events here. */
+
+ new_lp->signalled = 1;
+
/* We created NEW_LP so it cannot yet contain STATUS. */
gdb_assert (new_lp->status == 0);
@@ -2162,7 +2129,6 @@ linux_handle_extended_wait (struct lwp_info *lp, int status,
new_lp->status = status;
}
- new_lp->resumed = !stopping;
return 1;
}
@@ -2353,7 +2319,7 @@ wait_lwp (struct lwp_info *lp)
fprintf_unfiltered (gdb_stdlog,
"WL: Handling extended status 0x%06x\n",
status);
- linux_handle_extended_wait (lp, status, 1);
+ linux_handle_extended_wait (lp, status);
return 0;
}
@@ -3155,7 +3121,7 @@ linux_nat_filter_event (int lwpid, int status)
fprintf_unfiltered (gdb_stdlog,
"LLW: Handling extended status 0x%06x\n",
status);
- if (linux_handle_extended_wait (lp, status, 0))
+ if (linux_handle_extended_wait (lp, status))
return NULL;
}
@@ -3675,9 +3641,28 @@ resume_stopped_resumed_lwps (struct lwp_info *lp, void *data)
{
ptid_t *wait_ptid_p = data;
- if (lp->stopped
- && lp->resumed
- && !lwp_status_pending_p (lp))
+ if (!lp->stopped)
+ {
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "RSRL: NOT resuming LWP %s, not stopped\n",
+ target_pid_to_str (lp->ptid));
+ }
+ else if (!lp->resumed)
+ {
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "RSRL: NOT resuming LWP %s, not resumed\n",
+ target_pid_to_str (lp->ptid));
+ }
+ else if (lwp_status_pending_p (lp))
+ {
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "RSRL: NOT resuming LWP %s, has pending status\n",
+ target_pid_to_str (lp->ptid));
+ }
+ else
{
struct regcache *regcache = get_thread_regcache (lp->ptid);
struct gdbarch *gdbarch = get_regcache_arch (regcache);
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 171784e..06ca987 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,10 @@
+2015-07-30 Simon Marchi <simon.marchi@ericsson.com>
+ Pedro Alves <palves@redhat.com>
+
+ PR threads/18600
+ * gdb.threads/fork-plus-threads.c: New file.
+ * gdb.threads/fork-plus-threads.exp: New file.
+
2015-07-29 Patrick Palka <patrick@parcs.ath.cx>
* gdb.base/batch-preserve-term-settings.exp
diff --git a/gdb/testsuite/gdb.threads/fork-plus-threads.c b/gdb/testsuite/gdb.threads/fork-plus-threads.c
new file mode 100644
index 0000000..780a4b8
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/fork-plus-threads.c
@@ -0,0 +1,115 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2015 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 <pthread.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+/* Number of times the main process forks. */
+#define NFORKS 10
+
+/* Number of threads by each fork child. */
+#define NTHREADS 10
+
+static void *
+thread_func (void *arg)
+{
+ /* Empty. */
+}
+
+static void
+fork_child (void)
+{
+ pthread_t threads[NTHREADS];
+ int i;
+ int ret;
+
+ for (i = 0; i < NTHREADS; i++)
+ {
+ ret = pthread_create (&threads[i], NULL, thread_func, NULL);
+ assert (ret == 0);
+ }
+
+ for (i = 0; i < NTHREADS; i++)
+ {
+ ret = pthread_join (threads[i], NULL);
+ assert (ret == 0);
+ }
+}
+
+int
+main (void)
+{
+ pid_t childs[NFORKS];
+ int i;
+ int status;
+ int num_exited = 0;
+
+ /* Don't run forever if the wait loop below gets stuck. */
+ alarm (180);
+
+ for (i = 0; i < NFORKS; i++)
+ {
+ pid_t pid;
+
+ pid = fork ();
+
+ if (pid > 0)
+ {
+ /* Parent. */
+ childs[i] = pid;
+ }
+ else if (pid == 0)
+ {
+ /* Child. */
+ fork_child ();
+ return 0;
+ }
+ else
+ {
+ perror ("fork");
+ return 1;
+ }
+ }
+
+ while (num_exited != NFORKS)
+ {
+ pid_t pid = wait (&status);
+
+ if (pid == -1)
+ {
+ perror ("wait");
+ return 1;
+ }
+
+ if (WIFEXITED (status))
+ {
+ num_exited++;
+ }
+ else
+ {
+ printf ("Hmm, unexpected wait status 0x%x from child %d\n", status,
+ pid);
+ }
+ }
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/fork-plus-threads.exp b/gdb/testsuite/gdb.threads/fork-plus-threads.exp
new file mode 100644
index 0000000..53d1102
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/fork-plus-threads.exp
@@ -0,0 +1,69 @@
+# Copyright (C) 2015 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/>.
+
+# This test verifies that threads created by the child fork are
+# properly handled. Specifically, GDB used to have a bug where it
+# would leave child fork threads stuck stopped, even though "info
+# threads" would show them running.
+#
+# See https://sourceware.org/bugzilla/show_bug.cgi?id=18600
+
+standard_testfile
+
+proc do_test { detach_on_fork } {
+ global GDBFLAGS
+ global srcfile testfile
+ global gdb_prompt
+
+ set saved_gdbflags $GDBFLAGS
+ set GDBFLAGS [concat $GDBFLAGS " -ex \"set non-stop on\""]
+
+ if {[prepare_for_testing "failed to prepare" \
+ $testfile $srcfile {debug pthreads}] == -1} {
+ set GDBFLAGS $saved_gdbflags
+ return -1
+ }
+
+ set GDBFLAGS $saved_gdbflags
+
+ if ![runto_main] then {
+ fail "Can't run to main"
+ return 0
+ }
+
+ gdb_test_no_output "set detach-on-fork $detach_on_fork"
+ set test "continue &"
+ gdb_test_multiple $test $test {
+ -re "$gdb_prompt " {
+ pass $test
+ }
+ }
+
+ set test "inferior 1 exited"
+ gdb_test_multiple "" $test {
+ -re "Inferior 1 \(\[^\r\n\]+\) exited normally" {
+ pass $test
+ }
+ }
+
+ gdb_test "info threads" "No threads\." \
+ "no threads left"
+}
+
+foreach detach_on_fork {"on" "off"} {
+ with_test_prefix "detach-on-fork=$detach_on_fork" {
+ do_test $detach_on_fork
+ }
+}