aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog18
-rw-r--r--gdb/infrun.c47
-rw-r--r--gdb/linux-nat.c271
-rw-r--r--gdb/target.h5
-rw-r--r--gdb/testsuite/ChangeLog13
-rw-r--r--gdb/testsuite/gdb.threads/no-unwaited-for-left.c68
-rw-r--r--gdb/testsuite/gdb.threads/no-unwaited-for-left.exp69
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-1.c61
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-1.exp65
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-2.c70
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-2.exp70
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-3.c100
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-3.exp68
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-4.c92
-rw-r--r--gdb/testsuite/gdb.threads/non-ldr-exc-4.exp67
15 files changed, 959 insertions, 125 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index b6c8ed1..2e3f06a 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,21 @@
+2011-10-28 Pedro Alves <pedro@codesourcery.com>
+
+ * linux-nat.c (linux_nat_filter_event): Remove `options'
+ parameter, and dead code that used it. If we're handling a
+ PTRACE_EVENT_EXEC event, and the thread group leader is no longer
+ in our lwp list, re-add it.
+ (check_zombie_leaders): New.
+ (linux_nat_wait_1): Remove `options' and `pid' locals. Always
+ wait for children with WNOHANG, and always wait for all children.
+ Don't check for no resumed children upfront. Simplify wait loop.
+ Check for zombie thread group leaders after handling all wait
+ statuses. Return TARGET_WAITKIND_NO_RESUMED if there no
+ unwaited-for children left.
+ * infrun.c (fetch_inferior_event): Handle TARGET_WAITKIND_NO_RESUMED.
+ (handle_inferior_event): Handle TARGET_WAITKIND_NO_RESUMED.
+ (normal_stop): Handle TARGET_WAITKIND_NO_RESUMED.
+ * target.h (enum target_waitkind) <TARGET_WAITKIND_NO_RESUMED>: New.
+
2011-10-28 Sterling Augustine <saugustine@google.com>
* psymtab.c (map_symbol_filenames_psymtab): Call QUIT.
diff --git a/gdb/infrun.c b/gdb/infrun.c
index ac2b5ae..e0c59a1 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2773,6 +2773,7 @@ fetch_inferior_event (void *client_data)
if (non_stop
&& ecs->ws.kind != TARGET_WAITKIND_IGNORE
+ && ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
&& ecs->ws.kind != TARGET_WAITKIND_EXITED
&& ecs->ws.kind != TARGET_WAITKIND_SIGNALLED)
/* In non-stop mode, each thread is handled individually. Switch
@@ -2806,6 +2807,7 @@ fetch_inferior_event (void *client_data)
normal_stop ();
if (target_has_execution
+ && ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
&& ecs->ws.kind != TARGET_WAITKIND_EXITED
&& ecs->ws.kind != TARGET_WAITKIND_SIGNALLED
&& ecs->event_thread->step_multi
@@ -3164,8 +3166,24 @@ handle_inferior_event (struct execution_control_state *ecs)
return;
}
+ if (ecs->ws.kind == TARGET_WAITKIND_NO_RESUMED
+ && target_can_async_p () && !sync_execution)
+ {
+ /* There were no unwaited-for children left in the target, but,
+ we're not synchronously waiting for events either. Just
+ ignore. Otherwise, if we were running a synchronous
+ execution command, we need to cancel it and give the user
+ back the terminal. */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: TARGET_WAITKIND_NO_RESUMED (ignoring)\n");
+ prepare_to_wait (ecs);
+ return;
+ }
+
if (ecs->ws.kind != TARGET_WAITKIND_EXITED
- && ecs->ws.kind != TARGET_WAITKIND_SIGNALLED)
+ && ecs->ws.kind != TARGET_WAITKIND_SIGNALLED
+ && ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED)
{
struct inferior *inf = find_inferior_pid (ptid_get_pid (ecs->ptid));
@@ -3182,6 +3200,18 @@ handle_inferior_event (struct execution_control_state *ecs)
/* Always clear state belonging to the previous time we stopped. */
stop_stack_dummy = STOP_NONE;
+ if (ecs->ws.kind == TARGET_WAITKIND_NO_RESUMED)
+ {
+ /* No unwaited-for children left. IOW, all resumed children
+ have exited. */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: TARGET_WAITKIND_NO_RESUMED\n");
+
+ stop_print_frame = 0;
+ stop_stepping (ecs);
+ return;
+ }
+
/* If it's a new process, add it to the thread database. */
ecs->new_thread_event = (!ptid_equal (ecs->ptid, inferior_ptid)
@@ -5824,7 +5854,8 @@ normal_stop (void)
if (!non_stop)
make_cleanup (finish_thread_state_cleanup, &minus_one_ptid);
else if (last.kind != TARGET_WAITKIND_SIGNALLED
- && last.kind != TARGET_WAITKIND_EXITED)
+ && last.kind != TARGET_WAITKIND_EXITED
+ && last.kind != TARGET_WAITKIND_NO_RESUMED)
make_cleanup (finish_thread_state_cleanup, &inferior_ptid);
/* In non-stop mode, we don't want GDB to switch threads behind the
@@ -5843,7 +5874,8 @@ normal_stop (void)
&& !ptid_equal (previous_inferior_ptid, inferior_ptid)
&& target_has_execution
&& last.kind != TARGET_WAITKIND_SIGNALLED
- && last.kind != TARGET_WAITKIND_EXITED)
+ && last.kind != TARGET_WAITKIND_EXITED
+ && last.kind != TARGET_WAITKIND_NO_RESUMED)
{
target_terminal_ours_for_output ();
printf_filtered (_("[Switching to %s]\n"),
@@ -5852,6 +5884,14 @@ normal_stop (void)
previous_inferior_ptid = inferior_ptid;
}
+ if (last.kind == TARGET_WAITKIND_NO_RESUMED)
+ {
+ gdb_assert (sync_execution || !target_can_async_p ());
+
+ target_terminal_ours_for_output ();
+ printf_filtered (_("No unwaited-for children left.\n"));
+ }
+
if (!breakpoints_always_inserted_mode () && target_has_execution)
{
if (remove_breakpoints ())
@@ -6038,6 +6078,7 @@ done:
if (!target_has_execution
|| last.kind == TARGET_WAITKIND_SIGNALLED
|| last.kind == TARGET_WAITKIND_EXITED
+ || last.kind == TARGET_WAITKIND_NO_RESUMED
|| (!inferior_thread ()->step_multi
&& !(inferior_thread ()->control.stop_bpstat
&& inferior_thread ()->control.proceed_to_finish)
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 4595c06..6a7ba7b 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -3176,7 +3176,7 @@ stop_and_resume_callback (struct lwp_info *lp, void *data)
other lwp. In that case set *NEW_PENDING_P to true. */
static struct lwp_info *
-linux_nat_filter_event (int lwpid, int status, int options, int *new_pending_p)
+linux_nat_filter_event (int lwpid, int status, int *new_pending_p)
{
struct lwp_info *lp;
@@ -3191,7 +3191,27 @@ linux_nat_filter_event (int lwpid, int status, int options, int *new_pending_p)
fork, vfork, and clone events, then we'll just add the
new one to our list and go back to waiting for the event
to be reported - the stopped process might be returned
- from waitpid before or after the event is. */
+ from waitpid before or after the event is.
+
+ But note the case of a non-leader thread exec'ing after the
+ leader having exited, and gone from our lists. The non-leader
+ thread changes its tid to the tgid. */
+
+ if (WIFSTOPPED (status) && lp == NULL
+ && (WSTOPSIG (status) == SIGTRAP && status >> 16 == PTRACE_EVENT_EXEC))
+ {
+ /* A multi-thread exec after we had seen the leader exiting. */
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "LLW: Re-adding thread group leader LWP %d.\n",
+ lwpid);
+
+ lp = add_lwp (BUILD_LWP (lwpid, lwpid));
+ lp->stopped = 1;
+ lp->resumed = 1;
+ add_thread (lp->ptid);
+ }
+
if (WIFSTOPPED (status) && !lp)
{
add_to_pid_list (&stopped_pids, lwpid, status);
@@ -3205,33 +3225,6 @@ linux_nat_filter_event (int lwpid, int status, int options, int *new_pending_p)
if (!WIFSTOPPED (status) && !lp)
return NULL;
- /* NOTE drow/2003-06-17: This code seems to be meant for debugging
- CLONE_PTRACE processes which do not use the thread library -
- otherwise we wouldn't find the new LWP this way. That doesn't
- currently work, and the following code is currently unreachable
- due to the two blocks above. If it's fixed some day, this code
- should be broken out into a function so that we can also pick up
- LWPs from the new interface. */
- if (!lp)
- {
- lp = add_lwp (BUILD_LWP (lwpid, GET_PID (inferior_ptid)));
- if (options & __WCLONE)
- lp->cloned = 1;
-
- gdb_assert (WIFSTOPPED (status)
- && WSTOPSIG (status) == SIGSTOP);
- lp->signalled = 1;
-
- if (!in_thread_list (inferior_ptid))
- {
- inferior_ptid = BUILD_LWP (GET_PID (inferior_ptid),
- GET_PID (inferior_ptid));
- add_thread (inferior_ptid);
- }
-
- add_thread (lp->ptid);
- }
-
/* Handle GNU/Linux's syscall SIGTRAPs. */
if (WIFSTOPPED (status) && WSTOPSIG (status) == SYSCALL_SIGTRAP)
{
@@ -3392,6 +3385,71 @@ linux_nat_filter_event (int lwpid, int status, int options, int *new_pending_p)
return lp;
}
+/* Detect zombie thread group leaders, and "exit" them. We can't reap
+ their exits until all other threads in the group have exited. */
+
+static void
+check_zombie_leaders (void)
+{
+ struct inferior *inf;
+
+ ALL_INFERIORS (inf)
+ {
+ struct lwp_info *leader_lp;
+
+ if (inf->pid == 0)
+ continue;
+
+ leader_lp = find_lwp_pid (pid_to_ptid (inf->pid));
+ if (leader_lp != NULL
+ /* Check if there are other threads in the group, as we may
+ have raced with the inferior simply exiting. */
+ && num_lwps (inf->pid) > 1
+ && linux_lwp_is_zombie (inf->pid))
+ {
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "CZL: Thread group leader %d zombie "
+ "(it exited, or another thread execd).\n",
+ inf->pid);
+
+ /* A leader zombie can mean one of two things:
+
+ - It exited, and there's an exit status pending
+ available, or only the leader exited (not the whole
+ program). In the latter case, we can't waitpid the
+ leader's exit status until all other threads are gone.
+
+ - There are 3 or more threads in the group, and a thread
+ other than the leader exec'd. On an exec, the Linux
+ kernel destroys all other threads (except the execing
+ one) in the thread group, and resets the execing thread's
+ tid to the tgid. No exit notification is sent for the
+ execing thread -- from the ptracer's perspective, it
+ appears as though the execing thread just vanishes.
+ Until we reap all other threads except the leader and the
+ execing thread, the leader will be zombie, and the
+ execing thread will be in `D (disc sleep)'. As soon as
+ all other threads are reaped, the execing thread changes
+ it's tid to the tgid, and the previous (zombie) leader
+ vanishes, giving place to the "new" leader. We could try
+ distinguishing the exit and exec cases, by waiting once
+ more, and seeing if something comes out, but it doesn't
+ sound useful. The previous leader _does_ go away, and
+ we'll re-add the new one once we see the exec event
+ (which is just the same as what would happen if the
+ previous leader did exit voluntarily before some other
+ thread execs). */
+
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "CZL: Thread group leader %d vanished.\n",
+ inf->pid);
+ exit_lwp (leader_lp);
+ }
+ }
+}
+
static ptid_t
linux_nat_wait_1 (struct target_ops *ops,
ptid_t ptid, struct target_waitstatus *ourstatus,
@@ -3400,9 +3458,7 @@ linux_nat_wait_1 (struct target_ops *ops,
static sigset_t prev_mask;
enum resume_kind last_resume_kind;
struct lwp_info *lp;
- int options;
int status;
- pid_t pid;
if (debug_linux_nat)
fprintf_unfiltered (gdb_stdlog, "LLW: enter\n");
@@ -3424,41 +3480,14 @@ linux_nat_wait_1 (struct target_ops *ops,
/* Make sure SIGCHLD is blocked. */
block_child_signals (&prev_mask);
- if (ptid_equal (ptid, minus_one_ptid))
- pid = -1;
- else if (ptid_is_pid (ptid))
- /* A request to wait for a specific tgid. This is not possible
- with waitpid, so instead, we wait for any child, and leave
- children we're not interested in right now with a pending
- status to report later. */
- pid = -1;
- else
- pid = GET_LWP (ptid);
-
retry:
lp = NULL;
status = 0;
- options = 0;
-
- /* Make sure that of those LWPs we want to get an event from, there
- is at least one LWP that has been resumed. If there's none, just
- bail out. The core may just be flushing asynchronously all
- events. */
- if (iterate_over_lwps (ptid, resumed_callback, NULL) == NULL)
- {
- ourstatus->kind = TARGET_WAITKIND_IGNORE;
-
- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog, "LLW: exit (no resumed LWP)\n");
-
- restore_child_signals_mask (&prev_mask);
- return minus_one_ptid;
- }
/* First check if there is a LWP with a wait status pending. */
- if (pid == -1)
+ if (ptid_equal (ptid, minus_one_ptid) || ptid_is_pid (ptid))
{
- /* Any LWP that's been resumed will do. */
+ /* Any LWP in the PTID group that's been resumed will do. */
lp = iterate_over_lwps (ptid, status_callback, NULL);
if (lp)
{
@@ -3468,11 +3497,6 @@ retry:
status_to_str (lp->status),
target_pid_to_str (lp->ptid));
}
-
- /* But if we don't find one, we'll have to wait, and check both
- cloned and uncloned processes. We start with the cloned
- processes. */
- options = __WCLONE | WNOHANG;
}
else if (is_lwp (ptid))
{
@@ -3491,12 +3515,6 @@ retry:
status_to_str (lp->status),
target_pid_to_str (lp->ptid));
- /* If we have to wait, take into account whether PID is a cloned
- process or not. And we have to convert it to something that
- the layer beneath us can understand. */
- options = lp->cloned ? __WCLONE : 0;
- pid = GET_LWP (ptid);
-
/* We check for lp->waitstatus in addition to lp->status,
because we can have pending process exits recorded in
lp->status and W_EXITCODE(0,0) == 0. We should probably have
@@ -3557,15 +3575,34 @@ retry:
set_sigint_trap ();
}
- /* Translate generic target_wait options into waitpid options. */
- if (target_options & TARGET_WNOHANG)
- options |= WNOHANG;
+ /* But if we don't find a pending event, we'll have to wait. */
while (lp == NULL)
{
pid_t lwpid;
- lwpid = my_waitpid (pid, &status, options);
+ /* Always use -1 and WNOHANG, due to couple of a kernel/ptrace
+ quirks:
+
+ - If the thread group leader exits while other threads in the
+ thread group still exist, waitpid(TGID, ...) hangs. That
+ waitpid won't return an exit status until the other threads
+ in the group are reapped.
+
+ - When a non-leader thread execs, that thread just vanishes
+ without reporting an exit (so we'd hang if we waited for it
+ explicitly in that case). The exec event is reported to
+ the TGID pid. */
+
+ errno = 0;
+ lwpid = my_waitpid (-1, &status, __WCLONE | WNOHANG);
+ if (lwpid == 0 || (lwpid == -1 && errno == ECHILD))
+ lwpid = my_waitpid (-1, &status, WNOHANG);
+
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "LNW: waitpid(-1, ...) returned %d, %s\n",
+ lwpid, errno ? safe_strerror (errno) : "ERRNO-OK");
if (lwpid > 0)
{
@@ -3573,8 +3610,6 @@ retry:
now have pending events to handle. */
int new_pending;
- gdb_assert (pid == -1 || lwpid == pid);
-
if (debug_linux_nat)
{
fprintf_unfiltered (gdb_stdlog,
@@ -3582,14 +3617,12 @@ retry:
(long) lwpid, status_to_str (status));
}
- lp = linux_nat_filter_event (lwpid, status, options, &new_pending);
+ lp = linux_nat_filter_event (lwpid, status, &new_pending);
/* STATUS is now no longer valid, use LP->STATUS instead. */
status = 0;
- if (lp
- && ptid_is_pid (ptid)
- && ptid_get_pid (lp->ptid) != ptid_get_pid (ptid))
+ if (lp && !ptid_match (lp->ptid, ptid))
{
gdb_assert (lp->resumed);
@@ -3662,69 +3695,65 @@ retry:
store_waitstatus (&lp->waitstatus, lp->status);
}
- if (new_pending)
- goto retry;
-
/* Keep looking. */
lp = NULL;
- continue;
}
- if (lp)
- break;
- else
+ if (new_pending)
{
- if (new_pending)
- goto retry;
+ /* Some LWP now has a pending event. Go all the way
+ back to check it. */
+ goto retry;
+ }
- if (pid == -1)
- {
- /* waitpid did return something. Restart over. */
- options |= __WCLONE;
- }
- continue;
+ if (lp)
+ {
+ /* We got an event to report to the core. */
+ break;
}
+
+ /* Retry until nothing comes out of waitpid. A single
+ SIGCHLD can indicate more than one child stopped. */
+ continue;
}
- if (pid == -1)
- {
- /* Alternate between checking cloned and uncloned processes. */
- options ^= __WCLONE;
+ /* Check for zombie thread group leaders. Those can't be reaped
+ until all other threads in the thread group are. */
+ check_zombie_leaders ();
- /* And every time we have checked both:
- In async mode, return to event loop;
- In sync mode, suspend waiting for a SIGCHLD signal. */
- if (options & __WCLONE)
- {
- if (target_options & TARGET_WNOHANG)
- {
- /* No interesting event. */
- ourstatus->kind = TARGET_WAITKIND_IGNORE;
+ /* If there are no resumed children left, bail. We'd be stuck
+ forever in the sigsuspend call below otherwise. */
+ if (iterate_over_lwps (ptid, resumed_callback, NULL) == NULL)
+ {
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog, "LLW: exit (no resumed LWP)\n");
- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog, "LLW: exit (ignore)\n");
+ ourstatus->kind = TARGET_WAITKIND_NO_RESUMED;
- restore_child_signals_mask (&prev_mask);
- return minus_one_ptid;
- }
+ if (!target_can_async_p ())
+ clear_sigint_trap ();
- sigsuspend (&suspend_mask);
- }
+ restore_child_signals_mask (&prev_mask);
+ return minus_one_ptid;
}
- else if (target_options & TARGET_WNOHANG)
- {
- /* No interesting event for PID yet. */
- ourstatus->kind = TARGET_WAITKIND_IGNORE;
+ /* No interesting event to report to the core. */
+
+ if (target_options & TARGET_WNOHANG)
+ {
if (debug_linux_nat)
fprintf_unfiltered (gdb_stdlog, "LLW: exit (ignore)\n");
+ ourstatus->kind = TARGET_WAITKIND_IGNORE;
restore_child_signals_mask (&prev_mask);
return minus_one_ptid;
}
/* We shouldn't end up here unless we want to try again. */
gdb_assert (lp == NULL);
+
+ /* Block until we get an event reported with SIGCHLD. */
+ sigsuspend (&suspend_mask);
}
if (!target_can_async_p ())
@@ -3813,7 +3842,7 @@ retry:
from among those that have had events. Giving equal priority
to all LWPs that have had events helps prevent
starvation. */
- if (pid == -1)
+ if (ptid_equal (ptid, minus_one_ptid) || ptid_is_pid (ptid))
select_event_lwp (ptid, &lp, &status);
/* Now that we've selected our final event LWP, cancel any
diff --git a/gdb/target.h b/gdb/target.h
index 1adcf46..af7d3d9 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -151,7 +151,10 @@ enum target_waitkind
/* The target has run out of history information,
and cannot run backward any further. */
- TARGET_WAITKIND_NO_HISTORY
+ TARGET_WAITKIND_NO_HISTORY,
+
+ /* There are no resumed children left in the program. */
+ TARGET_WAITKIND_NO_RESUMED
};
struct target_waitstatus
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index f26044b..479676a 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,16 @@
+2011-10-28 Pedro Alves <pedro@codesourcery.com>
+
+ * gdb.threads/no-unwaited-for-left.c: New.
+ * gdb.threads/no-unwaited-for-left.exp: New.
+ * gdb.threads/non-ldr-exc-1.c: New.
+ * gdb.threads/non-ldr-exc-1.exp: New.
+ * gdb.threads/non-ldr-exc-2.c: New.
+ * gdb.threads/non-ldr-exc-2.exp: New.
+ * gdb.threads/non-ldr-exc-3.c: New.
+ * gdb.threads/non-ldr-exc-3.exp: New.
+ * gdb.threads/non-ldr-exc-4.c: New.
+ * gdb.threads/non-ldr-exc-4.exp: New.
+
2011-10-28 Jan Kratochvil <jan.kratochvil@redhat.com>
* gdb.base/async-shell.exp: Skip the testfile for use_gdb_stub.
diff --git a/gdb/testsuite/gdb.threads/no-unwaited-for-left.c b/gdb/testsuite/gdb.threads/no-unwaited-for-left.c
new file mode 100644
index 0000000..d8c32cf
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/no-unwaited-for-left.c
@@ -0,0 +1,68 @@
+/* Copyright 2007, 2011 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 <assert.h>
+#include <unistd.h>
+
+static volatile pthread_t main_thread;
+pthread_barrier_t barrier;
+
+static void *
+thread_a (void *arg)
+{
+ int i;
+
+ return 0; /* break-here */
+}
+
+static void *
+thread_b (void *arg)
+{
+ int i;
+
+ pthread_barrier_wait (&barrier);
+
+ i = pthread_join (main_thread, NULL);
+ assert (i == 0);
+
+ return arg;
+}
+
+int
+main (void)
+{
+ pthread_t thread;
+ int i;
+
+ /* First test resuming only `thread_a', which exits. */
+ i = pthread_create (&thread, NULL, thread_a, NULL);
+ assert (i == 0);
+ pthread_join (thread, NULL);
+
+ /* Then test resuming only the leader, which also exits. */
+ main_thread = pthread_self ();
+
+ pthread_barrier_init (&barrier, NULL, 2);
+
+ i = pthread_create (&thread, NULL, thread_b, NULL);
+ assert (i == 0);
+
+ pthread_barrier_wait (&barrier);
+
+ pthread_exit (NULL); /* break-here-2 */
+ /* NOTREACHED */
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp b/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
new file mode 100644
index 0000000..ed991de
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
@@ -0,0 +1,69 @@
+# Copyright (C) 2007, 2011 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/>.
+
+# Exit of a thread when there are other threads in the inferior should
+# not hang GDB.
+
+set testfile "no-unwaited-for-left"
+set srcfile ${testfile}.c
+set executable ${testfile}
+set binfile ${objdir}/${subdir}/${executable}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+clean_restart ${executable}
+
+if ![runto_main] {
+ return -1
+}
+
+gdb_breakpoint $srcfile:[gdb_get_line_number "break-here"]
+gdb_continue_to_breakpoint "break-here" ".* break-here .*"
+
+# Resume only the second thread
+gdb_test_no_output "set scheduler-locking on" \
+ "enable scheduler-locking, for thread 2"
+
+# Continue. Thread 2 exits, and the main thread was already stopped.
+gdb_test "continue" \
+ "No unwaited-for children left." \
+ "continue stops when thread 2 exits"
+
+gdb_test "info threads" \
+ "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *1 *Thread \[^\r\n\]* \[^\r\n\]*.*The current thread <Thread ID 2> has terminated.*" \
+ "only main thread left, thread 2 terminated"
+
+# Select the main thread, let the third thread start, and stop at the
+# main thread breakpoint.
+gdb_test "thread 1" "" "select main thread"
+gdb_test_no_output "set scheduler-locking off" \
+ "disable scheduler-locking, letting new thread start"
+
+gdb_breakpoint [gdb_get_line_number "break-here-2"]
+gdb_continue_to_breakpoint "break-here-2" ".* break-here-2 .*"
+
+# Let the main thread continue alone.
+gdb_test_no_output "set scheduler-locking on" \
+ "enable scheduler-locking, for main thread"
+# The main thread exits, and thread 3 was already stopped.
+gdb_test "continue" \
+ "No unwaited-for children left." \
+ "continue stops when the main thread exits"
+
+gdb_test "info threads" \
+ "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *3 *Thread \[^\r\n\]* \[^\r\n\]*.*The current thread <Thread ID 1> has terminated.*" \
+ "only thread 3 left, main thread terminated"
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-1.c b/gdb/testsuite/gdb.threads/non-ldr-exc-1.c
new file mode 100644
index 0000000..04b98e9
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-1.c
@@ -0,0 +1,61 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2009, 2010, 2011 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 <stdlib.h>
+#include <assert.h>
+
+static const char *image;
+static char *argv1 = "go away";
+
+static void *
+thread_execler (void *arg)
+{
+ /* Exec ourselves again. */
+ if (execl (image, image, argv1, NULL) == -1) /* break-here */
+ {
+ perror ("execl");
+ abort ();
+ }
+
+ return NULL;
+}
+
+int
+main (int argc, char **argv)
+{
+ pthread_t thread;
+ int i;
+
+ image = argv[0];
+
+ /* Pass "inf" as argument to keep re-execing ad infinitum, which can
+ be useful for manual testing. Passing any other argument exits
+ immediately (and that's what the execl above does by
+ default). */
+ if (argc == 2 && strcmp (argv[1], "inf") == 0)
+ argv1 = argv[1];
+ else if (argc > 1)
+ exit (0);
+
+ i = pthread_create (&thread, NULL, thread_execler, NULL);
+ assert (i == 0);
+ pthread_join (thread, NULL);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-1.exp b/gdb/testsuite/gdb.threads/non-ldr-exc-1.exp
new file mode 100644
index 0000000..edfda33
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-1.exp
@@ -0,0 +1,65 @@
+# Copyright 2009, 2010, 2011 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 when a thread other than the main thread execs, we follow
+# through to the new incarnation of the main thread.
+
+# No exec event support in the remote protocol.
+if { [is_remote target] } then {
+ continue
+}
+
+set testfile "non-ldr-exc-1"
+set srcfile ${testfile}.c
+set executable ${testfile}
+set binfile ${objdir}/${subdir}/${executable}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+proc do_test { lock_sched } {
+ global pf_prefix
+ global executable
+
+ set save_pf_prefix $pf_prefix
+ lappend pf_prefix "lock-sched$lock_sched:"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ set pf_prefix $save_pf_prefix
+ return -1
+ }
+
+ gdb_breakpoint [gdb_get_line_number "break-here"]
+ gdb_continue_to_breakpoint "break-here" ".* break-here .*"
+
+ # Also test with sched-lock to make sure we can follow the
+ # non-leader thread execing even though the main thread wasn't
+ # resumed before the exec.
+ if { $lock_sched } {
+ gdb_test_no_output "set scheduler-locking on"
+ }
+
+ gdb_test "continue" \
+ ".*is executing new program.*Breakpoint 1, main.* at .*" \
+ "continue over exec"
+
+ set pf_prefix $save_pf_prefix
+}
+
+do_test 0
+do_test 1
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-2.c b/gdb/testsuite/gdb.threads/non-ldr-exc-2.c
new file mode 100644
index 0000000..69a21d4
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-2.c
@@ -0,0 +1,70 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2009, 2010, 2011 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 <stdlib.h>
+#include <assert.h>
+
+static const char *image;
+static volatile pthread_t main_thread;
+static char *argv1 = "go away";
+
+static void *
+thread_execler (void *arg)
+{
+ int i;
+
+ i = pthread_join (main_thread, NULL);
+ assert (i == 0);
+
+ /* Exec ourselves again. */
+ if (execl (image, image, argv1, NULL) == -1) /* break-here */
+ {
+ perror ("execl");
+ abort ();
+ }
+
+ return NULL;
+}
+
+int
+main (int argc, char **argv)
+{
+ pthread_t thread;
+ int i;
+
+ image = argv[0];
+
+ /* Pass "inf" as argument to keep re-execing ad infinitum, which can
+ be useful for manual testing. Passing any other argument exits
+ immediately (and that's what the execl above does by
+ default). */
+ if (argc == 2 && strcmp (argv[1], "inf") == 0)
+ argv1 = argv[1];
+ else if (argc > 1)
+ exit (0);
+
+ main_thread = pthread_self ();
+
+ i = pthread_create (&thread, NULL, thread_execler, NULL);
+ assert (i == 0);
+
+ pthread_exit (NULL);
+ /* NOTREACHED */
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-2.exp b/gdb/testsuite/gdb.threads/non-ldr-exc-2.exp
new file mode 100644
index 0000000..0686516
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-2.exp
@@ -0,0 +1,70 @@
+# Copyright 2009, 2010, 2011 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 when a thread other than the main thread execs, we follow
+# through to the new incarnation of the main thread, even if the main
+# thread had already exited before the exec.
+
+# No exec event support in the remote protocol.
+if { [is_remote target] } then {
+ continue
+}
+
+set testfile "non-ldr-exc-2"
+set srcfile ${testfile}.c
+set executable ${testfile}
+set binfile ${objdir}/${subdir}/${executable}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+proc do_test { lock_sched } {
+ global pf_prefix
+ global executable
+
+ set save_pf_prefix $pf_prefix
+ lappend pf_prefix "lock-sched$lock_sched:"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ set pf_prefix $save_pf_prefix
+ return -1
+ }
+
+ gdb_breakpoint [gdb_get_line_number "break-here"]
+ gdb_continue_to_breakpoint "break-here" ".* break-here .*"
+
+ gdb_test "info threads" \
+ "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n\\* 2 *Thread \[^\r\n\]* at \[^\r\n\]*" \
+ "single thread left"
+
+ # Also test with sched-lock to make sure we can follow the
+ # non-leader thread execing even though the main thread wasn't
+ # resumed before the exec.
+ if { $lock_sched } {
+ gdb_test_no_output "set scheduler-locking on"
+ }
+
+ gdb_test "continue" \
+ ".*is executing new program.*Breakpoint 1, main.* at .*" \
+ "continue over exec"
+
+ set pf_prefix $save_pf_prefix
+}
+
+do_test 0
+do_test 1
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-3.c b/gdb/testsuite/gdb.threads/non-ldr-exc-3.c
new file mode 100644
index 0000000..64597e4
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-3.c
@@ -0,0 +1,100 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2009, 2010, 2011 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 <stdlib.h>
+#include <assert.h>
+
+static const char *image;
+static volatile pthread_t main_thread;
+static pthread_barrier_t barrier;
+static char *argv1 = "go away";
+
+static void *
+thread_execler (void *arg)
+{
+ int i;
+
+ pthread_barrier_wait (&barrier);
+
+ i = pthread_join (main_thread, NULL);
+ assert (i == 0);
+
+ /* Exec ourselves again. */
+ if (execl (image, image, argv1, NULL) == -1) /* break-here */
+ {
+ perror ("execl");
+ abort ();
+ }
+
+ return NULL;
+}
+
+static void *
+just_loop (void *arg)
+{
+ unsigned int i;
+
+ pthread_barrier_wait (&barrier);
+
+ for (i = 1; i > 0; i++)
+ usleep (1);
+
+ return NULL;
+}
+
+#define THREADS 5
+
+pthread_t loop_thread[THREADS];
+
+int
+main (int argc, char **argv)
+{
+ pthread_t thread;
+ int i, t;
+
+ image = argv[0];
+
+ /* Pass "inf" as argument to keep re-execing ad infinitum, which can
+ be useful for manual testing. Passing any other argument exits
+ immediately (and that's what the execl above does by
+ default). */
+ if (argc == 2 && strcmp (argv[1], "inf") == 0)
+ argv1 = argv[1];
+ else if (argc > 1)
+ exit (0);
+
+ main_thread = pthread_self ();
+
+ pthread_barrier_init (&barrier, NULL, 2 + THREADS);
+
+ i = pthread_create (&thread, NULL, thread_execler, NULL);
+ assert (i == 0);
+
+ for (t = 0; t < THREADS; t++)
+ {
+ i = pthread_create (&loop_thread[t], NULL, just_loop, NULL);
+ assert (i == 0);
+ }
+
+ pthread_barrier_wait (&barrier);
+
+ pthread_exit (NULL);
+ /* NOTREACHED */
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-3.exp b/gdb/testsuite/gdb.threads/non-ldr-exc-3.exp
new file mode 100644
index 0000000..8eaafcf
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-3.exp
@@ -0,0 +1,68 @@
+# Copyright 2009, 2010, 2011 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 when a thread other than the main thread execs, we follow
+# through to the new incarnation of the main thread, even if the main
+# thread had already exited before the exec. This differs from
+# non-ldr-exc-2.exp in that we have more than two threads in the
+# program when the exec happens.
+
+# No exec event support in the remote protocol.
+if { [is_remote target] } then {
+ continue
+}
+
+set testfile "non-ldr-exc-3"
+set srcfile ${testfile}.c
+set executable ${testfile}
+set binfile ${objdir}/${subdir}/${executable}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+proc do_test { lock_sched } {
+ global pf_prefix
+ global executable
+
+ set save_pf_prefix $pf_prefix
+ lappend pf_prefix "lock-sched$lock_sched:"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ set pf_prefix $save_pf_prefix
+ return -1
+ }
+
+ gdb_breakpoint [gdb_get_line_number "break-here"]
+ gdb_continue_to_breakpoint "break-here" ".* break-here .*"
+
+ # Also test with sched-lock to make sure we can follow the
+ # non-leader thread execing even though the main thread wasn't
+ # resumed before the exec.
+ if { $lock_sched } {
+ gdb_test_no_output "set scheduler-locking on"
+ }
+
+ gdb_test "continue" \
+ ".*is executing new program.*Breakpoint 1, main.* at .*" \
+ "continue over exec"
+
+ set pf_prefix $save_pf_prefix
+}
+
+do_test 0
+do_test 1
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-4.c b/gdb/testsuite/gdb.threads/non-ldr-exc-4.c
new file mode 100644
index 0000000..57dea04
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-4.c
@@ -0,0 +1,92 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2009, 2010, 2011 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 <stdlib.h>
+#include <assert.h>
+
+static const char *image;
+static pthread_barrier_t barrier;
+static char *argv1 = "go away";
+
+static void *
+thread_execler (void *arg)
+{
+ int i;
+
+ pthread_barrier_wait (&barrier);
+
+ /* Exec ourselves again. */
+ if (execl (image, image, argv1, NULL) == -1) /* break-here */
+ {
+ perror ("execl");
+ abort ();
+ }
+
+ return NULL;
+}
+
+static void *
+just_loop (void *arg)
+{
+ unsigned int i;
+
+ pthread_barrier_wait (&barrier);
+
+ for (i = 1; i > 0; i++)
+ usleep (1);
+
+ return NULL;
+}
+
+#define THREADS 5
+
+pthread_t loop_thread[THREADS];
+
+int
+main (int argc, char **argv)
+{
+ pthread_t thread;
+ int i, t;
+
+ image = argv[0];
+
+ /* Pass "inf" as argument to keep re-execing ad infinitum, which can
+ be useful for manual testing. Passing any other argument exits
+ immediately (and that's what the execl above does by
+ default). */
+ if (argc == 2 && strcmp (argv[1], "inf") == 0)
+ argv1 = argv[1];
+ else if (argc > 1)
+ exit (0);
+
+ pthread_barrier_init (&barrier, NULL, 2 + THREADS);
+
+ i = pthread_create (&thread, NULL, thread_execler, NULL);
+ assert (i == 0);
+
+ for (t = 0; t < THREADS; t++)
+ {
+ i = pthread_create (&loop_thread[t], NULL, just_loop, NULL);
+ assert (i == 0);
+ }
+
+ pthread_barrier_wait (&barrier);
+ pthread_join (thread, NULL);
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/non-ldr-exc-4.exp b/gdb/testsuite/gdb.threads/non-ldr-exc-4.exp
new file mode 100644
index 0000000..3ca099b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/non-ldr-exc-4.exp
@@ -0,0 +1,67 @@
+# Copyright 2009, 2010, 2011 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 when a thread other than the main thread execs, we follow
+# through to the new incarnation of the main thread. This differs
+# from non-ldr-exc-1.exp in that we have more than two threads in the
+# program when the exec happens.
+
+# No exec event support in the remote protocol.
+if { [is_remote target] } then {
+ continue
+}
+
+set testfile "non-ldr-exc-4"
+set srcfile ${testfile}.c
+set executable ${testfile}
+set binfile ${objdir}/${subdir}/${executable}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+proc do_test { lock_sched } {
+ global pf_prefix
+ global executable
+
+ set save_pf_prefix $pf_prefix
+ lappend pf_prefix "lock-sched$lock_sched:"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ set pf_prefix $save_pf_prefix
+ return -1
+ }
+
+ gdb_breakpoint [gdb_get_line_number "break-here"]
+ gdb_continue_to_breakpoint "break-here" ".* break-here .*"
+
+ # Also test with sched-lock to make sure we can follow the
+ # non-leader thread execing even though the main thread wasn't
+ # resumed before the exec.
+ if { $lock_sched } {
+ gdb_test_no_output "set scheduler-locking on"
+ }
+
+ gdb_test "continue" \
+ ".*is executing new program.*Breakpoint 1, main.* at .*" \
+ "continue over exec"
+
+ set pf_prefix $save_pf_prefix
+}
+
+do_test 0
+do_test 1