[non-stop] 10/10 handle exited threads [was: Re: [non-stop] 10/10 split user/internal threads]

Pedro Alves
2008-07-02T03:39:41Z

[-- Attachment #1: Type: text/plain, Size: 506 bytes --]

Here's the rewrite of this patch to use cleanups and refcounting on the
thread_info objects to prevent them from being deleted while the thread
is held in a cleanup. This version completelly drops the
external/internal thread separation, and sticks with inferior_ptid
to mean current thread. Exited threads are kept in the thread list
until safe to delete. Most commands aren't allowed while the
user has an exited thread selected.

Tested on x86_64-unknown-linux-gnu -m32, sync/async.

--
Pedro Alves

[-- Attachment #2: 010-exited_threads_ref_count.diff --]
[-- Type: text/x-diff, Size: 45834 bytes --]

2008-07-02 Pedro Alves <pedro@codesourcery.com>

Exited threads.

* thread.c (enum thread_state): New.
(thread_state main_thread_running): Delete, in favor of...
(thread_state main_thread_state): ... this. Update throughout.
(clear_thread_inferior_resources): New, split from free_thread.
(free_thread): Call clear_thread_inferior_resources.
(init_thread_list): Set main thread to stopped state.
(add_thread_silent): Take care of PTID reuses.
(delete_thread): If deleting inferior_ptid or a thread with
refcount > 0, mark it as exited, but still keep it in the list.
Only notify of thread exits, if we haven't done so yet.
(iterate_over_threads): Make it safe to delete threads while
iterating over them.
(do_captured_list_thread_ids): Don't account for exited threads.
(thread_alive): Check for the THREAD_EXITED state, and don't set
ptid to -1 on exited threads.
(set_running): Update to account for extra possible states.
(is_thread_state): New.
(is_stopped, is_exited): New.
(is_running): Implement in terms of is_thread_state.
(any_running): Update.
(print_thread_info): Update. Account for exited threads. Don't
warn about missed frame restoring here, it's done in the cleanup.
(switch_to_thread): Don't read from a thread that has gone.
(restore_current_thread): In non-stop mode, do a full context
switch.
(restore_selected_frame): Add a frame_level argument. Rewrite.
(struct current_thread_cleanup): Add selected_frame_level and
was_stopped members.
(do_restore_current_thread_cleanup): Check if thread was stopped
and still is, and if the target has registers, stack and memory
before restoring the selected frame. Don't delete the cleanup
argument here.
(restore_current_thread_cleanup_dtor): New.
(make_cleanup_restore_current_thread): Remove all arguments.
Rewrite.
(thread_apply_all_command): Update. Prune threads.
(thread_apply_command): Update.
(thread_command): Account for currently selected exited thread.
(do_captured_thread_select): Check for a running thread. Prune
threads.
(_initialize_thread): Make "info threads", "thread", "thread
apply", and "thread apply all" appliable without a selected thread.
* gdbthread.h (struct thread_info): Replace running_ by state_.
Add refcount.
(is_exited, is_stopped): Declare.
(make_cleanup_restore_current_thread): Remove all arguments.
* infrun.c: Include "event-top.h".
(fetch_inferior_event): In non-stop mode, restore selected thread
and frame after handling the event and running breakpoint
commands. Display GDB prompt if needed.
(normal_stop): In non-stop mode, don't print thread switching
notice.
* cli/cli-decode.c (set_cmd_no_selected_thread_ok)
(get_cmd_no_selected_thread_ok): New.
* cli/cli-decode.h (CMD_NO_SELECTED_THREAD_OK): New.
(set_cmd_no_selected_thread_ok, get_cmd_no_selected_thread_ok):
Declare.
* cli/cli-cmds.c: Set "pwd", "help", "info", "show" as
no-selected-thread ok.
* top.c (execute_command): Check for non no-selected-thread-ok
commands.
* linux-nat.c (struct saved_ptids, threads_to_delete)
(record_dead_thread, prune_lwps): Delete.
(exit_lwp): Unconditionally delete thread.
(linux_nat_resume): Remove prune_lwps call.
* infcmd.c (proceed_thread_callback): Check if !is_stopped instead
of is_running. Adjust to make_cleanup_restore_current_thread
interface change.
* mi/mi-main.c (mi_cmd_execute): Only allow a few commands if the
selected thread has exited.
* inf-loop.c (inferior_event_handler): Don't display the prompt
here.
* varobj.c (c_value_of_root): Update.
* Makefile.in (infrun.o): Depend on $(event_top_h).
* defs.h (make_cleanup_dtor): Declare.
* utils.c (make_cleanup_dtor): New.

---
gdb/Makefile.in | 2
gdb/cli/cli-cmds.c | 5
gdb/cli/cli-decode.c | 12 +
gdb/cli/cli-decode.h | 11 +
gdb/defs.h | 3
gdb/gdbthread.h | 30 ++-
gdb/inf-loop.c | 16 -
gdb/infcmd.c | 12 -
gdb/infrun.c | 26 ++
gdb/linux-nat.c | 48 -----
gdb/mi/mi-main.c | 18 +
gdb/thread.c | 468 +++++++++++++++++++++++++++++++++++----------------
gdb/top.c | 19 +-
gdb/utils.c | 8
gdb/varobj.c | 3
15 files changed, 455 insertions(+), 226 deletions(-)

Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c 2008-07-01 23:03:37.000000000 +0100
+++ src/gdb/thread.c 2008-07-02 03:10:06.000000000 +0100
@@ -41,7 +41,6 @@
#include "ui-out.h"
#include "observer.h"
#include "annotate.h"
-
#include "cli/cli-decode.h"

/* Definition of struct thread_info exported to gdbthread.h */
@@ -65,7 +64,16 @@ static void thread_apply_command (char *
static void restore_current_thread (ptid_t);
static void prune_threads (void);

-static int main_thread_running = 0;
+/* Frontend view of the thread state. Possible extensions: stepping,
+ finishing, until(ling),... */
+enum thread_state
+{
+ THREAD_STOPPED,
+ THREAD_RUNNING,
+ THREAD_EXITED,
+};
+
+static enum thread_state main_thread_state = THREAD_STOPPED;
static int main_thread_executing = 0;

void
@@ -86,16 +94,25 @@ delete_step_resume_breakpoint (void *arg
}

static void
-free_thread (struct thread_info *tp)
+clear_thread_inferior_resources (struct thread_info *tp)
{
/* NOTE: this will take care of any left-over step_resume breakpoints,
but not any user-specified thread-specific breakpoints. We can not
delete the breakpoint straight-off, because the inferior might not
be stopped at the moment. */
if (tp->step_resume_breakpoint)
- tp->step_resume_breakpoint->disposition = disp_del_at_next_stop;
+ {
+ tp->step_resume_breakpoint->disposition = disp_del_at_next_stop;
+ tp->step_resume_breakpoint = NULL;
+ }

bpstat_clear (&tp->stop_bpstat);
+}
+
+static void
+free_thread (struct thread_info *tp)
+{
+ clear_thread_inferior_resources (tp);

/* FIXME: do I ever need to call the back-end to give it a
chance at this private data before deleting the thread? */
@@ -111,7 +128,7 @@ init_thread_list (void)
struct thread_info *tp, *tpnext;

highest_thread_num = 0;
- main_thread_running = 0;
+ main_thread_state = THREAD_STOPPED;
main_thread_executing = 0;

if (!thread_list)
@@ -131,6 +148,49 @@ add_thread_silent (ptid_t ptid)
{
struct thread_info *tp;

+ tp = find_thread_pid (ptid);
+ if (tp)
+ /* Found an old thread with the same id. It has to be dead,
+ otherwise we wouldn't be adding a new thread with the same id.
+ The OS is reusing this id --- delete it, and recreate a new
+ one. */
+ {
+ /* In addition to deleting the thread, if this is the current
+ thread, then we need to also get rid of the current infrun
+ context, and take care that delete_thread doesn't really
+ delete the thread if it is inferior_ptid. Create a new
+ template thread in the list with an invalid ptid, context
+ switch to it, delete the original thread, reset the new
+ thread's ptid, and switch to it. */
+
+ if (ptid_equal (inferior_ptid, ptid))
+ {
+ tp = xmalloc (sizeof (*tp));
+ memset (tp, 0, sizeof (*tp));
+ tp->ptid = minus_one_ptid;
+ tp->num = ++highest_thread_num;
+ tp->next = thread_list;
+ thread_list = tp;
+ context_switch_to (minus_one_ptid);
+
+ /* Now we can delete it. */
+ delete_thread (ptid);
+
+ /* Since the context is already set to this new thread,
+ reset it's ptid, and reswitch inferior_ptid to it. */
+ tp->ptid = ptid;
+ switch_to_thread (ptid);
+
+ observer_notify_new_thread (tp);
+
+ /* All done. */
+ return tp;
+ }
+ else
+ /* Just go ahead and delete it. */
+ delete_thread (ptid);
+ }
+
tp = (struct thread_info *) xmalloc (sizeof (*tp));
memset (tp, 0, sizeof (*tp));
tp->ptid = ptid;
@@ -163,6 +223,10 @@ add_thread (ptid_t ptid)
return add_thread_with_info (ptid, NULL);
}

+/* Delete thread PTID and notify of thread exit. If this is
+ inferior_ptid, don't actually delete it, but tag it as exited and
+ do the notification. If PTID is the user selected thread, clear
+ it. */
void
delete_thread (ptid_t ptid)
{
@@ -177,12 +241,35 @@ delete_thread (ptid_t ptid)
if (!tp)
return;

+ /* If this is the current thread, or there's code out there that
+ relies on it existing (refcout > 0) we can't delete yet. Mark it
+ as exited, and notify it. */
+ if (tp->refcount > 0
+ || ptid_equal (tp->ptid, inferior_ptid))
+ {
+ if (tp->state_ != THREAD_EXITED)
+ {
+ observer_notify_thread_exit (tp);
+
+ /* Tag it as exited. */
+ tp->state_ = THREAD_EXITED;
+
+ /* Clear breakpoints, etc. associated with this thread. */
+ clear_thread_inferior_resources (tp);
+ }
+
+ /* Will be really deleted some other time. */
+ return;
+ }
+
if (tpprev)
tpprev->next = tp->next;
else
thread_list = tp->next;

- observer_notify_thread_exit (tp);
+ /* Notify thread exit, but only if we haven't already. */
+ if (tp->state_ != THREAD_EXITED)
+ observer_notify_thread_exit (tp);

free_thread (tp);
}
@@ -230,11 +317,14 @@ struct thread_info *
iterate_over_threads (int (*callback) (struct thread_info *, void *),
void *data)
{
- struct thread_info *tp;
+ struct thread_info *tp, *next;

- for (tp = thread_list; tp; tp = tp->next)
- if ((*callback) (tp, data))
- return tp;
+ for (tp = thread_list; tp; tp = next)
+ {
+ next = tp->next;
+ if ((*callback) (tp, data))
+ return tp;
+ }

return NULL;
}
@@ -313,6 +403,8 @@ do_captured_list_thread_ids (struct ui_o

for (tp = thread_list; tp; tp = tp->next)
{
+ if (tp->state_ == THREAD_EXITED)
+ continue;
num++;
ui_out_field_int (uiout, "thread-id", tp->num);
}
@@ -463,13 +555,10 @@ save_infrun_state (ptid_t ptid,
static int
thread_alive (struct thread_info *tp)
{
- if (PIDGET (tp->ptid) == -1)
+ if (tp->state_ == THREAD_EXITED)
return 0;
if (!target_thread_alive (tp->ptid))
- {
- tp->ptid = pid_to_ptid (-1); /* Mark it as dead */
- return 0;
- }
+ return 0;
return 1;
}

@@ -501,9 +590,11 @@ set_running (ptid_t ptid, int running)
the main thread is always present in the thread list. If it's
not, the first call to context_switch will mess up GDB internal
state. */
- if (running && !main_thread_running && !suppress_resume_observer)
+ if (running
+ && main_thread_state != THREAD_RUNNING
+ && !suppress_resume_observer)
observer_notify_target_resumed (ptid);
- main_thread_running = running;
+ main_thread_state = running ? THREAD_RUNNING : THREAD_STOPPED;
return;
}

@@ -515,25 +606,31 @@ set_running (ptid_t ptid, int running)
int any_started = 0;
for (tp = thread_list; tp; tp = tp->next)
{
- if (running && !tp->running_)
- any_started = 1;
- tp->running_ = running;
+ if (tp->state_ == THREAD_EXITED)
+ continue;
+ if (running && tp->state_ == THREAD_STOPPED)
+ any_started = 1;
+ tp->state_ = running ? THREAD_RUNNING : THREAD_STOPPED;
}
if (any_started && !suppress_resume_observer)
observer_notify_target_resumed (ptid);
}
else
{
+ int started = 0;
tp = find_thread_pid (ptid);
gdb_assert (tp);
- if (running && !tp->running_ && !suppress_resume_observer)
- observer_notify_target_resumed (ptid);
- tp->running_ = running;
- }
+ gdb_assert (tp->state_ != THREAD_EXITED);
+ if (running && tp->state_ == THREAD_STOPPED)
+ started = 1;
+ tp->state_ = running ? THREAD_RUNNING : THREAD_STOPPED;
+ if (started && !suppress_resume_observer)
+ observer_notify_target_resumed (ptid);
+ }
}

-int
-is_running (ptid_t ptid)
+static int
+is_thread_state (ptid_t ptid, enum thread_state state)
{
struct thread_info *tp;

@@ -541,11 +638,41 @@ is_running (ptid_t ptid)
return 0;

if (!thread_list)
- return main_thread_running;
+ return main_thread_state == state;

tp = find_thread_pid (ptid);
gdb_assert (tp);
- return tp->running_;
+ return tp->state_ == state;
+}
+
+int
+is_stopped (ptid_t ptid)
+{
+ /* Without execution, this property is always true. */
+ if (!target_has_execution)
+ return 1;
+
+ return is_thread_state (ptid, THREAD_STOPPED);
+}
+
+int
+is_exited (ptid_t ptid)
+{
+ /* Without execution, this property is always false. */
+ if (!target_has_execution)
+ return 0;
+
+ return is_thread_state (ptid, THREAD_EXITED);
+}
+
+int
+is_running (ptid_t ptid)
+{
+ /* Without execution, this property is always false. */
+ if (!target_has_execution)
+ return 0;
+
+ return is_thread_state (ptid, THREAD_RUNNING);
}

int
@@ -557,10 +684,10 @@ any_running (void)
return 0;

if (!thread_list)
- return main_thread_running;
+ return main_thread_state == THREAD_RUNNING;

for (tp = thread_list; tp; tp = tp->next)
- if (tp->running_)
+ if (tp->state_ == THREAD_RUNNING)
return 1;

return 0;
@@ -612,7 +739,7 @@ set_executing (ptid_t ptid, int executin
/* Prints the list of threads and their details on UIOUT.
This is a version of 'info_thread_command' suitable for
use from MI.
- If REQESTED_THREAD is not -1, it's the GDB id of the thread
+ If REQUESTED_THREAD is not -1, it's the GDB id of the thread
that should be printed. Otherwise, all threads are
printed. */
void
@@ -620,24 +747,18 @@ print_thread_info (struct ui_out *uiout,
{
struct thread_info *tp;
ptid_t current_ptid;
- struct frame_info *cur_frame;
struct cleanup *old_chain;
- struct frame_id saved_frame_id;
char *extra_info;
int current_thread = -1;

- /* Backup current thread and selected frame. */
- if (!is_running (inferior_ptid))
- saved_frame_id = get_frame_id (get_selected_frame (NULL));
- else
- saved_frame_id = null_frame_id;
-
- old_chain = make_cleanup_restore_current_thread (inferior_ptid, saved_frame_id);
- make_cleanup_ui_out_list_begin_end (uiout, "threads");
-
prune_threads ();
target_find_new_threads ();
current_ptid = inferior_ptid;
+
+ /* We'll be switching threads temporarily. */
+ old_chain = make_cleanup_restore_current_thread ();
+
+ make_cleanup_ui_out_list_begin_end (uiout, "threads");
for (tp = thread_list; tp; tp = tp->next)
{
struct cleanup *chain2;
@@ -645,13 +766,16 @@ print_thread_info (struct ui_out *uiout,
if (requested_thread != -1 && tp->num != requested_thread)
continue;

+ if (ptid_equal (tp->ptid, current_ptid))
+ current_thread = tp->num;
+
+ if (tp->state_ == THREAD_EXITED)
+ continue;
+
chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);

if (ptid_equal (tp->ptid, current_ptid))
- {
- current_thread = tp->num;
- ui_out_text (uiout, "* ");
- }
+ ui_out_text (uiout, "* ");
else
ui_out_text (uiout, " ");

@@ -659,15 +783,19 @@ print_thread_info (struct ui_out *uiout,
ui_out_text (uiout, " ");
ui_out_field_string (uiout, "target-id", target_tid_to_str (tp->ptid));

- extra_info = target_extra_thread_info (tp);
- if (extra_info)
+ if (tp->state_ != THREAD_EXITED)
{
- ui_out_text (uiout, " (");
- ui_out_field_string (uiout, "details", extra_info);
- ui_out_text (uiout, ")");
+ extra_info = target_extra_thread_info (tp);
+ if (extra_info)
+ {
+ ui_out_text (uiout, " (");
+ ui_out_field_string (uiout, "details", extra_info);
+ ui_out_text (uiout, ")");
+ }
+ ui_out_text (uiout, " ");
}
- ui_out_text (uiout, " ");
- if (tp->running_)
+
+ if (tp->state_ == THREAD_RUNNING)
ui_out_text (uiout, "(running)\n");
else
{
@@ -689,24 +817,15 @@ print_thread_info (struct ui_out *uiout,

if (requested_thread == -1)
{
- gdb_assert (current_thread != -1 || !thread_list);
+ gdb_assert (current_thread != -1
+ || !thread_list);
if (current_thread != -1 && ui_out_is_mi_like_p (uiout))
ui_out_field_int (uiout, "current-thread-id", current_thread);
- }
-
- if (is_running (inferior_ptid))
- return;

- /* If case we were not able to find the original frame, print the
- new selected frame. */
- if (frame_find_by_id (saved_frame_id) == NULL)
- {
- warning (_("Couldn't restore frame in current thread, at frame 0"));
- /* For MI, we should probably have a notification about
- current frame change. But this error is not very likely, so
- don't bother for now. */
- if (!ui_out_is_mi_like_p (uiout))
- print_stack_frame (get_selected_frame (NULL), 0, LOCATION);
+ if (current_thread != -1 && is_exited (current_ptid))
+ ui_out_message (uiout, 0, "\n\
+The current thread <Thread ID %d> has terminated. See `help thread'.\n",
+ current_thread);
}
}

@@ -736,7 +855,10 @@ switch_to_thread (ptid_t ptid)
reinit_frame_cache ();
registers_changed ();

- if (!is_executing (ptid))
+ /* We don't check for is_stopped, because we're called at times
+ while in the TARGET_RUNNING state, e.g., while handling an
+ internal event. */
+ if (!is_exited (ptid) && !is_executing (ptid))
stop_pc = read_pc ();
else
stop_pc = ~(CORE_ADDR) 0;
@@ -747,21 +869,64 @@ restore_current_thread (ptid_t ptid)
{
if (!ptid_equal (ptid, inferior_ptid))
{
- switch_to_thread (ptid);
+ if (non_stop)
+ context_switch_to (ptid);
+ else
+ switch_to_thread (ptid);
}
}

static void
-restore_selected_frame (struct frame_id a_frame_id)
+restore_selected_frame (struct frame_id a_frame_id, int frame_level)
{
- struct frame_info *selected_frame_info = NULL;
+ struct frame_info *frame = NULL;
+ int count;

- if (frame_id_eq (a_frame_id, null_frame_id))
- return;
+ gdb_assert (frame_level >= 0);

- if ((selected_frame_info = frame_find_by_id (a_frame_id)) != NULL)
+ /* Restore by level first, check if the frame id is the same as
+ expected. If that fails, try restoring by frame id. If that
+ fails, nothing to do, just warn the user. */
+
+ count = frame_level;
+ frame = find_relative_frame (get_current_frame (), &count);
+ if (count == 0
+ && frame != NULL
+ /* Either the frame ids match, of they're both invalid.
+ The later case is not failsafe, but since it's highly
+ unlikelly the search by level finds the wrong frame,
+ it's 99.9(9)% of the times (for all practical
+ purposes) safe. */
+ && (frame_id_eq (get_frame_id (frame), a_frame_id)
+ /* Note: could be better to check every frame_id
+ member for equality here. */
+ || (!frame_id_p (get_frame_id (frame))
+ && !frame_id_p (a_frame_id))))
{
- select_frame (selected_frame_info);
+ /* Cool, all is fine. */
+ select_frame (frame);
+ return;
+ }
+
+ frame = frame_find_by_id (a_frame_id);
+ if (frame != NULL)
+ {
+ /* Cool, refound it. */
+ select_frame (frame);
+ return;
+ }
+
+ /* Nothing else to do, the frame layout really changed.
+ Tell the user. */
+ if (!ui_out_is_mi_like_p (uiout))
+ {
+ warning (_("\
+Couldn't restore frame #%d in current thread, at reparsed frame #0\n"),
+ frame_level);
+ /* For MI, we should probably have a notification about
+ current frame change. But this error is not very
+ likely, so don't bother for now. */
+ print_stack_frame (frame, 1, SRC_LINE);
}
}

@@ -769,31 +934,66 @@ struct current_thread_cleanup
{
ptid_t inferior_ptid;
struct frame_id selected_frame_id;
+ int selected_frame_level;
+ int was_stopped;
};

static void
do_restore_current_thread_cleanup (void *arg)
{
+ struct thread_info *tp;
struct current_thread_cleanup *old = arg;
restore_current_thread (old->inferior_ptid);

- /* A command like 'thread apply all $exec_command&' may change the
- running state of the originally selected thread, so we have to
- recheck it here. */
- if (!is_running (old->inferior_ptid))
- restore_selected_frame (old->selected_frame_id);
+ /* The running state of the originally selected thread may have
+ changed, so we have to recheck it here. */
+ if (old->was_stopped
+ && is_stopped (inferior_ptid)
+ && target_has_registers
+ && target_has_stack
+ && target_has_memory)
+ restore_selected_frame (old->selected_frame_id,
+ old->selected_frame_level);
+}
+
+static void
+restore_current_thread_cleanup_dtor (void *arg)
+{
+ struct current_thread_cleanup *old = arg;
+ struct thread_info *tp;
+ tp = find_thread_pid (old->inferior_ptid);
+ if (tp)
+ tp->refcount--;
xfree (old);
}

struct cleanup *
-make_cleanup_restore_current_thread (ptid_t inferior_ptid,
- struct frame_id a_frame_id)
+make_cleanup_restore_current_thread (void)
{
- struct current_thread_cleanup *old
- = xmalloc (sizeof (struct current_thread_cleanup));
+ struct thread_info *tp;
+ struct frame_info *frame;
+ struct current_thread_cleanup *old;
+
+ old = xmalloc (sizeof (struct current_thread_cleanup));
old->inferior_ptid = inferior_ptid;
- old->selected_frame_id = a_frame_id;
- return make_cleanup (do_restore_current_thread_cleanup, old);
+ old->was_stopped = is_stopped (inferior_ptid);
+ if (old->was_stopped
+ && target_has_registers
+ && target_has_stack
+ && target_has_memory)
+ frame = get_selected_frame (NULL);
+ else
+ frame = NULL;
+
+ old->selected_frame_id = get_frame_id (frame);
+ old->selected_frame_level = frame_relative_level (frame);
+
+ tp = find_thread_pid (inferior_ptid);
+ if (tp)
+ tp->refcount++;
+
+ return make_cleanup_dtor (do_restore_current_thread_cleanup, old,
+ restore_current_thread_cleanup_dtor);
}

/* Apply a GDB command to a list of threads. List syntax is a whitespace
@@ -809,27 +1009,17 @@ static void
thread_apply_all_command (char *cmd, int from_tty)
{
struct thread_info *tp;
- struct cleanup *old_chain = make_cleanup (null_cleanup, 0);
+ struct cleanup *old_chain;
char *saved_cmd;
- struct frame_id saved_frame_id;
- ptid_t current_ptid;
- int thread_has_changed = 0;

if (cmd == NULL || *cmd == '\000')
error (_("Please specify a command following the thread ID list"));
-
- current_ptid = inferior_ptid;
-
- if (!is_running (inferior_ptid))
- saved_frame_id = get_frame_id (get_selected_frame (NULL));
- else
- saved_frame_id = null_frame_id;
- make_cleanup_restore_current_thread (inferior_ptid, saved_frame_id);

- /* It is safe to update the thread list now, before
- traversing it for "thread apply all". MVS */
+ prune_threads ();
target_find_new_threads ();

+ old_chain = make_cleanup_restore_current_thread ();
+
/* Save a copy of the command in case it is clobbered by
execute_command */
saved_cmd = xstrdup (cmd);
@@ -848,13 +1038,7 @@ thread_apply_all_command (char *cmd, int
strcpy (cmd, saved_cmd); /* Restore exact command used previously */
}

- if (!ptid_equal (current_ptid, inferior_ptid))
- thread_has_changed = 1;
-
do_cleanups (old_chain);
- /* Print stack frame only if we changed thread. */
- if (thread_has_changed && !is_running (inferior_ptid))
- print_stack_frame (get_current_frame (), 1, SRC_LINE);
}

static void
@@ -863,11 +1047,7 @@ thread_apply_command (char *tidlist, int
char *cmd;
char *p;
struct cleanup *old_chain;
- struct cleanup *saved_cmd_cleanup_chain;
char *saved_cmd;
- struct frame_id saved_frame_id;
- ptid_t current_ptid;
- int thread_has_changed = 0;

if (tidlist == NULL || *tidlist == '\000')
error (_("Please specify a thread ID list"));
@@ -877,18 +1057,10 @@ thread_apply_command (char *tidlist, int
if (*cmd == '\000')
error (_("Please specify a command following the thread ID list"));

- current_ptid = inferior_ptid;
-
- if (!is_running (inferior_ptid))
- saved_frame_id = get_frame_id (get_selected_frame (NULL));
- else
- saved_frame_id = null_frame_id;
- old_chain = make_cleanup_restore_current_thread (inferior_ptid, saved_frame_id);
-
/* Save a copy of the command in case it is clobbered by
execute_command */
saved_cmd = xstrdup (cmd);
- saved_cmd_cleanup_chain = make_cleanup (xfree, (void *) saved_cmd);
+ old_chain = make_cleanup (xfree, saved_cmd);
while (tidlist < cmd)
{
struct thread_info *tp;
@@ -926,26 +1098,24 @@ thread_apply_command (char *tidlist, int
warning (_("Thread %d has terminated."), start);
else
{
+ make_cleanup_restore_current_thread ();
+
if (non_stop)
context_switch_to (tp->ptid);
else
switch_to_thread (tp->ptid);
+
printf_filtered (_("\nThread %d (%s):\n"), tp->num,
target_tid_to_str (inferior_ptid));
execute_command (cmd, from_tty);
- strcpy (cmd, saved_cmd); /* Restore exact command used previously */
+
+ /* Restore exact command used previously. */
+ strcpy (cmd, saved_cmd);
}
}
}

- if (!ptid_equal (current_ptid, inferior_ptid))
- thread_has_changed = 1;
-
- do_cleanups (saved_cmd_cleanup_chain);
do_cleanups (old_chain);
- /* Print stack frame only if we changed thread. */
- if (thread_has_changed)
- print_stack_frame (get_current_frame (), 1, SRC_LINE);
}

/* Switch to the specified thread. Will dispatch off to thread_apply_command
@@ -956,11 +1126,17 @@ thread_command (char *tidstr, int from_t
{
if (!tidstr)
{
- /* Don't generate an error, just say which thread is current. */
if (target_has_stack)
- printf_filtered (_("[Current thread is %d (%s)]\n"),
- pid_to_thread_id (inferior_ptid),
- target_tid_to_str (inferior_ptid));
+ {
+ if (is_exited (inferior_ptid))
+ printf_filtered (_("[Current thread is %d (%s) (exited)]\n"),
+ pid_to_thread_id (inferior_ptid),
+ target_tid_to_str (inferior_ptid));
+ else
+ printf_filtered (_("[Current thread is %d (%s)]\n"),
+ pid_to_thread_id (inferior_ptid),
+ target_tid_to_str (inferior_ptid));
+ }
else
error (_("No stack."));
return;
@@ -1008,10 +1184,16 @@ do_captured_thread_select (struct ui_out
ui_out_text (uiout, target_tid_to_str (inferior_ptid));
ui_out_text (uiout, ")]");

- if (!tp->running_)
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
- else
+ /* Note that we can't reach this with an exited thread, due to the
+ thread_alive check above. */
+ if (tp->state_ == THREAD_RUNNING)
ui_out_text (uiout, "(running)\n");
+ else
+ print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+
+ /* Since the current thread may have changed, see if there is any
+ exited thread we can now delete. */
+ prune_threads ();

return GDB_RC_OK;
}
@@ -1037,19 +1219,25 @@ _initialize_thread (void)
c = add_info ("threads", info_threads_command,
_("IDs of currently known threads."));
set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);

c = add_prefix_cmd ("thread", class_run, thread_command, _("\
Use this command to switch between threads.\n\
The new thread ID must be currently known."),
&thread_cmd_list, "thread ", 1, &cmdlist);
set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);

- add_prefix_cmd ("apply", class_run, thread_apply_command,
- _("Apply a command to a list of threads."),
- &thread_apply_list, "thread apply ", 1, &thread_cmd_list);
+ c = add_prefix_cmd ("apply", class_run, thread_apply_command,
+ _("Apply a command to a list of threads."),
+ &thread_apply_list, "thread apply ", 1, &thread_cmd_list);
+ set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);

- add_cmd ("all", class_run, thread_apply_all_command,
- _("Apply a command to all threads."), &thread_apply_list);
+ c = add_cmd ("all", class_run, thread_apply_all_command,
+ _("Apply a command to all threads."), &thread_apply_list);
+ set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);

if (!xdb_commands)
add_com_alias ("t", "thread", class_run, 1);
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/gdbthread.h 2008-07-01 23:50:48.000000000 +0100
@@ -45,15 +45,21 @@ struct thread_info
use is_executing instead. */
int executing_;

- /* Frontend view of the running state. Note that this is different
- from EXECUTING. When the thread is stopped internally while
- handling an internal event, like a software single-step
- breakpoint, executing will be false, but running will still be
- true. As a possible future extension, this could turn into
- enum { stopped, stepping, finishing, until(ling), ... } */
+ /* Frontend view of the thread state. Note that the RUNNING/STOPPED
+ states are different from EXECUTING. When the thread is stopped
+ internally while handling an internal event, like a software
+ single-step breakpoint, EXECUTING will be false, but running will
+ still be true. As a possible future extension, this could turn
+ into enum { stopped, exited, stepping, finishing, until(ling),
+ running ... } */
/* This field is internal to thread.c. Never access it directly,
use is_running instead. */
- int running_;
+ int state_;
+
+ /* If this is > 0, then it means there's code out there that relies
+ on this thread being listed. Don't delete it from the lists even
+ if we detect it exiting. */
+ int refcount;

/* State from wait_for_inferior */
CORE_ADDR prev_pc;
@@ -202,6 +208,13 @@ extern int is_running (ptid_t ptid);
/* Reports if any thread is known to be running right now. */
extern int any_running (void);

+/* Is this thread listed, but known to have exited? We keep it listed
+ (but not visible) until it's safe to delete. */
+extern int is_exited (ptid_t ptid);
+
+/* Is this thread stopped? */
+extern int is_stopped (ptid_t ptid);
+
/* Marks thread PTID as executing, or as stopped.
If PIDGET (PTID) is -1, marks all threads. */
extern void set_executing (ptid_t ptid, int executing);
@@ -218,8 +231,7 @@ extern int print_thread_events;

extern void print_thread_info (struct ui_out *uiout, int thread);

-extern struct cleanup *make_cleanup_restore_current_thread (ptid_t,
- struct frame_id);
+extern struct cleanup *make_cleanup_restore_current_thread (void);

#endif /* GDBTHREAD_H */
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2008-07-01 23:03:37.000000000 +0100
+++ src/gdb/infrun.c 2008-07-01 23:50:48.000000000 +0100
@@ -48,6 +48,7 @@

#include "gdb_assert.h"
#include "mi/mi-common.h"
+#include "event-top.h"

/* Prototypes for local functions */

@@ -1526,11 +1527,20 @@ fetch_inferior_event (void *client_data)
{
struct execution_control_state ecss;
struct execution_control_state *ecs = &ecss;
+ struct cleanup *old_chain = make_cleanup (null_cleanup, NULL);
+ int was_sync = sync_execution;

memset (ecs, 0, sizeof (*ecs));

overlay_cache_invalid = 1;

+ if (non_stop)
+ /* In non-stop mode, the user/frontend should not notice a thread
+ switch due to internal events. Make sure we reverse to the
+ user selected thread and frame after handling the event and
+ running any breakpoint commands. */
+ make_cleanup_restore_current_thread ();
+
/* We have to invalidate the registers BEFORE calling target_wait
because they can be loaded from the target while in target_wait.
This makes remote debugging a bit more efficient for those
@@ -1567,6 +1577,14 @@ fetch_inferior_event (void *client_data)
else
inferior_event_handler (INF_EXEC_COMPLETE, NULL);
}
+
+ /* Revert thread and frame. */
+ do_cleanups (old_chain);
+
+ /* If the inferior was in sync execution mode, and now isn't,
+ restore the prompt. */
+ if (was_sync && !sync_execution)
+ display_gdb_prompt (0);
}

/* Prepare an execution control state for looping through a
@@ -3705,6 +3723,11 @@ normal_stop (void)

get_last_target_status (&last_ptid, &last);

+ /* In non-stop mode, we don't want GDB to switch threads on the
+ user's back, to avoid races where the user is typing a command to
+ apply to thread x, but GDB switches to thread y before the user
+ finishes entering the command. */
+
/* As with the notification of thread events, we want to delay
notifying the user that we've switched thread context until
the inferior actually stops.
@@ -3712,7 +3735,8 @@ normal_stop (void)
There's no point in saying anything if the inferior has exited.
Note that SIGNALLED here means "exited with a signal", not
"received a signal". */
- if (!ptid_equal (previous_inferior_ptid, inferior_ptid)
+ if (!non_stop
+ && !ptid_equal (previous_inferior_ptid, inferior_ptid)
&& target_has_execution
&& last.kind != TARGET_WAITKIND_SIGNALLED
&& last.kind != TARGET_WAITKIND_EXITED)
Index: src/gdb/cli/cli-decode.c
===================================================================
--- src.orig/gdb/cli/cli-decode.c 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/cli/cli-decode.c 2008-07-01 23:50:48.000000000 +0100
@@ -117,6 +117,18 @@ get_cmd_async_ok (struct cmd_list_elemen
return cmd->flags & CMD_ASYNC_OK;
}

+void
+set_cmd_no_selected_thread_ok (struct cmd_list_element *cmd)
+{
+ cmd->flags |= CMD_NO_SELECTED_THREAD_OK;
+}
+
+int
+get_cmd_no_selected_thread_ok (struct cmd_list_element *cmd)
+{
+ return cmd->flags & CMD_NO_SELECTED_THREAD_OK;
+}
+
enum cmd_types
cmd_type (struct cmd_list_element *cmd)
{
Index: src/gdb/cli/cli-decode.h
===================================================================
--- src.orig/gdb/cli/cli-decode.h 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/cli/cli-decode.h 2008-07-01 23:50:48.000000000 +0100
@@ -51,6 +51,10 @@ cmd_types;
/* This flag is set if the command is allowed during async execution. */
#define CMD_ASYNC_OK 0x8

+/* This flag is set if the command is allowed to run when the target
+ has execution, but there's no selected thread. */
+#define CMD_NO_SELECTED_THREAD_OK 0x10
+
struct cmd_list_element
{
/* Points to next command in this list. */
@@ -253,6 +257,13 @@ extern void set_cmd_async_ok (struct cmd
/* Return true if command is async-ok. */
extern int get_cmd_async_ok (struct cmd_list_element *);

+/* Mark command as ok to call when there is no selected thread. There
+ is no way to disable this once set. */
+extern void set_cmd_no_selected_thread_ok (struct cmd_list_element *);
+
+/* Return true if command is no-selected-thread-ok. */
+extern int get_cmd_no_selected_thread_ok (struct cmd_list_element *);
+
extern struct cmd_list_element *lookup_cmd (char **,
struct cmd_list_element *, char *,
int, int);
Index: src/gdb/top.c
===================================================================
--- src.orig/gdb/top.c 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/top.c 2008-07-01 23:50:48.000000000 +0100
@@ -362,6 +362,8 @@ do_chdir_cleanup (void *old_dir)
/* Execute the line P as a command.
Pass FROM_TTY as second argument to the defining function. */

+/* Execute command P, in the current user context. */
+
void
execute_command (char *p, int from_tty)
{
@@ -415,12 +417,19 @@ execute_command (char *p, int from_tty)

c = lookup_cmd (&p, cmdlist, "", 0, 1);

- /* If the target is running, we allow only a limited set of
- commands. */
+ /* If the selected thread has terminated, we allow only a
+ limited set of commands. */
if (target_can_async_p ()
- && ((!non_stop && any_running ())
- || (non_stop && is_running (inferior_ptid)))
- && !get_cmd_async_ok (c))
+ && is_exited (inferior_ptid)
+ && !get_cmd_no_selected_thread_ok (c))
+ error (_("\
+Cannot execute this command without a live selected thread. See `help thread'."));
+ /* If the target is running, we allow only a limited set of
+ commands. */
+ else if (target_can_async_p ()
+ && ((!non_stop && any_running ())
+ || (non_stop && is_running (inferior_ptid)))
+ && !get_cmd_async_ok (c))
error (_("Cannot execute this command while the target is running."));

/* Pass null arg rather than an empty one. */
Index: src/gdb/linux-nat.c
===================================================================
--- src.orig/gdb/linux-nat.c 2008-07-01 23:50:47.000000000 +0100
+++ src/gdb/linux-nat.c 2008-07-02 02:47:29.000000000 +0100
@@ -1043,48 +1043,12 @@ linux_nat_switch_fork (ptid_t new_ptid)
{
struct lwp_info *lp;

- init_thread_list ();
init_lwp_list ();
lp = add_lwp (new_ptid);
- add_thread_silent (new_ptid);
lp->stopped = 1;
-}
-
-/* Record a PTID for later deletion. */
-
-struct saved_ptids
-{
- ptid_t ptid;
- struct saved_ptids *next;
-};
-static struct saved_ptids *threads_to_delete;
-
-static void
-record_dead_thread (ptid_t ptid)
-{
- struct saved_ptids *p = xmalloc (sizeof (struct saved_ptids));
- p->ptid = ptid;
- p->next = threads_to_delete;
- threads_to_delete = p;
-}

-/* Delete any dead threads which are not the current thread. */
-
-static void
-prune_lwps (void)
-{
- struct saved_ptids **p = &threads_to_delete;
-
- while (*p)
- if (! ptid_equal ((*p)->ptid, inferior_ptid))
- {
- struct saved_ptids *tmp = *p;
- delete_thread (tmp->ptid);
- *p = tmp->next;
- xfree (tmp);
- }
- else
- p = &(*p)->next;
+ init_thread_list ();
+ add_thread_silent (new_ptid);
}

/* Handle the exit of a single thread LP. */
@@ -1099,11 +1063,7 @@ exit_lwp (struct lwp_info *lp)
if (print_thread_events)
printf_unfiltered (_("[%s exited]\n"), target_pid_to_str (lp->ptid));

- /* Core GDB cannot deal with us deleting the current thread. */
- if (!ptid_equal (lp->ptid, inferior_ptid))
- delete_thread (lp->ptid);
- else
- record_dead_thread (lp->ptid);
+ delete_thread (lp->ptid);
}

delete_lwp (lp->ptid);
@@ -1610,8 +1570,6 @@ linux_nat_resume (ptid_t ptid, int step,
signo ? strsignal (signo) : "0",
target_pid_to_str (inferior_ptid));

- prune_lwps ();
-
if (target_can_async_p ())
/* Block events while we're here. */
linux_nat_async_events (sigchld_sync);
Index: src/gdb/infcmd.c
===================================================================
--- src.orig/gdb/infcmd.c 2008-07-01 23:50:47.000000000 +0100
+++ src/gdb/infcmd.c 2008-07-01 23:50:48.000000000 +0100
@@ -611,7 +611,7 @@ start_command (char *args, int from_tty)
static int
proceed_thread_callback (struct thread_info *thread, void *arg)
{
- if (is_running (thread->ptid))
+ if (!is_stopped (thread->ptid))
return 0;

context_switch_to (thread->ptid);
@@ -696,19 +696,13 @@ Can't resume all threads and specify pro

if (non_stop && all_threads)
{
- struct cleanup *old_chain;
- struct frame_id saved_frame_id;
-
/* Don't error out if the current thread is running, because
there may be other stopped threads. */
+ struct cleanup *old_chain;

/* Backup current thread and selected frame. */
- if (!is_running (inferior_ptid))
- saved_frame_id = get_frame_id (get_selected_frame (NULL));
- else
- saved_frame_id = null_frame_id;
+ old_chain = make_cleanup_restore_current_thread ();

- old_chain = make_cleanup_restore_current_thread (inferior_ptid, saved_frame_id);
iterate_over_threads (proceed_thread_callback, NULL);

/* Restore selected ptid. */
Index: src/gdb/cli/cli-cmds.c
===================================================================
--- src.orig/gdb/cli/cli-cmds.c 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/cli/cli-cmds.c 2008-07-01 23:50:48.000000000 +0100
@@ -1245,6 +1245,8 @@ The commands below can be used to select
c = add_com ("pwd", class_files, pwd_command, _("\
Print working directory. This is used for your program as well."));
set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);
+
c = add_cmd ("cd", class_files, cd_command, _("\
Set working directory to DIR for debugger and program being debugged.\n\
The change does not take effect for the program being debugged\n\
@@ -1285,6 +1287,7 @@ when GDB is started."), gdbinit);
_("Print list of commands."));
set_cmd_completer (c, command_completer);
set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);
add_com_alias ("q", "quit", class_support, 1);
add_com_alias ("h", "help", class_support, 1);

@@ -1314,6 +1317,7 @@ Without an argument, history expansion i
Generic command for showing things about the program being debugged."),
&infolist, "info ", 0, &cmdlist);
set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);
add_com_alias ("i", "info", class_info, 1);

add_com ("complete", class_obscure, complete_command,
@@ -1323,6 +1327,7 @@ Generic command for showing things about
Generic command for showing things about the debugger."),
&showlist, "show ", 0, &cmdlist);
set_cmd_async_ok (c);
+ set_cmd_no_selected_thread_ok (c);
/* Another way to get at the same thing. */
add_info ("set", show_command, _("Show all GDB settings."));

Index: src/gdb/mi/mi-main.c
===================================================================
--- src.orig/gdb/mi/mi-main.c 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/mi/mi-main.c 2008-07-02 02:47:55.000000000 +0100
@@ -1059,6 +1059,24 @@ mi_cmd_execute (struct mi_parse *parse)

if (parse->cmd->argv_func != NULL)
{
+ if (target_can_async_p ()
+ && target_has_execution
+ && (is_exited (inferior_ptid))
+ && (strcmp (parse->command, "thread-info") != 0
+ && strcmp (parse->command, "thread-list-ids") != 0
+ && strcmp (parse->command, "thread-select") != 0))
+ {
+ struct ui_file *stb;
+ stb = mem_fileopen ();
+
+ fputs_unfiltered ("Cannot execute command ", stb);
+ fputstr_unfiltered (parse->command, '"', stb);
+ fputs_unfiltered (" without a selected thread", stb);
+
+ make_cleanup_ui_file_delete (stb);
+ error_stream (stb);
+ }
+
if ((!non_stop && any_running ())
|| (non_stop && is_running (inferior_ptid)))
{
Index: src/gdb/inf-loop.c
===================================================================
--- src.orig/gdb/inf-loop.c 2008-07-01 23:03:37.000000000 +0100
+++ src/gdb/inf-loop.c 2008-07-01 23:50:48.000000000 +0100
@@ -116,20 +116,8 @@ inferior_event_handler (enum inferior_ev
bpstat_do_actions (&stop_bpstat);
}

- /* If no breakpoint command resumed the inferior, prepare for
- interaction with the user. */
- if (!is_running (inferior_ptid))
- {
- if (was_sync)
- {
- display_gdb_prompt (0);
- }
- else
- {
- if (exec_done_display_p)
- printf_unfiltered (_("completed.\n"));
- }
- }
+ if (!was_sync && !is_running (inferior_ptid) && exec_done_display_p)
+ printf_unfiltered (_("completed.\n"));
break;

case INF_EXEC_CONTINUE:
Index: src/gdb/varobj.c
===================================================================
--- src.orig/gdb/varobj.c 2008-07-01 23:03:27.000000000 +0100
+++ src/gdb/varobj.c 2008-07-01 23:50:48.000000000 +0100
@@ -2198,8 +2198,7 @@ c_value_of_root (struct varobj **var_han
/* Not a root var */
return NULL;

- back_to = make_cleanup_restore_current_thread (
- inferior_ptid, get_frame_id (deprecated_safe_get_selected_frame ()));
+ back_to = make_cleanup_restore_current_thread ();

/* Determine whether the variable is still around. */
if (var->root->valid_block == NULL || var->root->floating)
Index: src/gdb/Makefile.in
===================================================================
--- src.orig/gdb/Makefile.in 2008-07-01 23:03:37.000000000 +0100
+++ src/gdb/Makefile.in 2008-07-02 03:11:08.000000000 +0100
@@ -2335,7 +2335,7 @@ infrun.o: infrun.c $(defs_h) $(gdb_strin
$(gdbcore_h) $(gdbcmd_h) $(cli_script_h) $(target_h) $(gdbthread_h) \
$(annotate_h) $(symfile_h) $(top_h) $(inf_loop_h) $(regcache_h) \
$(value_h) $(observer_h) $(language_h) $(solib_h) $(gdb_assert_h) \
- $(mi_common_h) $(main_h)
+ $(mi_common_h) $(main_h) $(event_top_h)
inf-ttrace.o: inf-ttrace.c $(defs_h) $(command_h) $(gdbcore_h) \
$(gdbthread_h) $(inferior_h) $(target_h) \
$(gdb_assert_h) $(gdb_string_h) $(inf_child_h) $(inf_ttrace_h)
Index: src/gdb/defs.h
===================================================================
--- src.orig/gdb/defs.h 2008-07-02 00:32:28.000000000 +0100
+++ src/gdb/defs.h 2008-07-02 00:33:06.000000000 +0100
@@ -333,6 +333,9 @@ typedef void (make_cleanup_ftype) (void

extern struct cleanup *make_cleanup (make_cleanup_ftype *, void *);

+extern struct cleanup *make_cleanup_dtor (make_cleanup_ftype *, void *,
+ void (*dtor) (void *));
+
extern struct cleanup *make_cleanup_freeargv (char **);

struct ui_file;
Index: src/gdb/utils.c
===================================================================
--- src.orig/gdb/utils.c 2008-07-02 00:32:29.000000000 +0100
+++ src/gdb/utils.c 2008-07-02 00:34:00.000000000 +0100
@@ -208,6 +208,14 @@ make_cleanup (make_cleanup_ftype *functi
}

struct cleanup *
+make_cleanup_dtor (make_cleanup_ftype *function, void *arg,
+ void (*dtor) (void *))
+{
+ return make_my_cleanup2 (&cleanup_chain,
+ function, arg, dtor);
+}
+
+struct cleanup *
make_final_cleanup (make_cleanup_ftype *function, void *arg)
{
return make_my_cleanup (&final_cleanup_chain, function, arg);