From 4f8d22e3b4e0e9a71f94f24deb77d2f4753deb85 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Fri, 11 Jul 2008 11:07:39 +0000 Subject: 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, its 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. * defs.h (make_cleanup_dtor): Declare. * utils.c (make_cleanup_dtor): New. * Makefile.in (infrun.o): Depend on $(event_top_h). --- gdb/ChangeLog | 82 +++++++++ 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 | 467 ++++++++++++++++++++++++++++++++++++--------------- gdb/top.c | 19 ++- gdb/utils.c | 8 + gdb/varobj.c | 3 +- 16 files changed, 536 insertions(+), 226 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index df5cd3d..4c5de49 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,87 @@ 2008-07-11 Pedro Alves + 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, its 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. + * defs.h (make_cleanup_dtor): Declare. + * utils.c (make_cleanup_dtor): New. + + * Makefile.in (infrun.o): Depend on $(event_top_h). + +2008-07-11 Pedro Alves + Add "continue -a" and "interrupt -a" options for non-stop mode. * infcmd.c (proceed_thread_callback, do_context_switch_to): New. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index efa18ea..7ae8420 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -2335,7 +2335,7 @@ infrun.o: infrun.c $(defs_h) $(gdb_string_h) $(symtab_h) $(frame_h) \ $(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) diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c index b0f5fa0..efd3137 100644 --- a/gdb/cli/cli-cmds.c +++ b/gdb/cli/cli-cmds.c @@ -1245,6 +1245,8 @@ The commands below can be used to select other frames by number or address."), 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 is enabled."), 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 the program being debugged."), 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.")); diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c index 05a0b27..07bb587 100644 --- a/gdb/cli/cli-decode.c +++ b/gdb/cli/cli-decode.c @@ -117,6 +117,18 @@ get_cmd_async_ok (struct cmd_list_element *cmd) 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) { diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h index f133b3d..c790398 100644 --- a/gdb/cli/cli-decode.h +++ b/gdb/cli/cli-decode.h @@ -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_list_element *); /* 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); diff --git a/gdb/defs.h b/gdb/defs.h index 2195ee8..1f1a4bd 100644 --- a/gdb/defs.h +++ b/gdb/defs.h @@ -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; diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 76b8f6c..3f1d217 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -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; @@ -207,6 +213,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); @@ -223,8 +236,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 */ diff --git a/gdb/inf-loop.c b/gdb/inf-loop.c index 94ced19..0572d8c 100644 --- a/gdb/inf-loop.c +++ b/gdb/inf-loop.c @@ -116,20 +116,8 @@ inferior_event_handler (enum inferior_event_type event_type, 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: diff --git a/gdb/infcmd.c b/gdb/infcmd.c index c62739a..f5fd018 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -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 proceed count simultaneously.")); 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. */ diff --git a/gdb/infrun.c b/gdb/infrun.c index f74b945..44e6790 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -48,6 +48,7 @@ #include "gdb_assert.h" #include "mi/mi-common.h" +#include "event-top.h" /* Prototypes for local functions */ @@ -1530,11 +1531,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 @@ -1571,6 +1581,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 @@ -3709,6 +3727,11 @@ normal_stop (void) get_last_target_status (&last_ptid, &last); + /* In non-stop mode, we don't want GDB to switch threads behind 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. @@ -3716,7 +3739,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) diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index ed13014..967beb4 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -1077,48 +1077,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. */ @@ -1133,11 +1097,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); @@ -1675,8 +1635,6 @@ linux_nat_resume (ptid_t ptid, int step, enum target_signal signo) 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); diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 06e9d63..a82d2f0 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -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))) { diff --git a/gdb/thread.c b/gdb/thread.c index 1ae9adf..c3a63fc 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -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 *, int); 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 its 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; @@ -179,17 +239,44 @@ delete_thread_1 (ptid_t ptid, int silent) if (!tp) return; + /* If this is the current thread, or there's code out there that + relies on it existing (refcount > 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) + { + if (!silent) + 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; - if (!silent) + /* Notify thread exit, but only if we haven't already. */ + if (!silent && tp->state_ != THREAD_EXITED) observer_notify_thread_exit (tp); free_thread (tp); } +/* 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) { @@ -245,11 +332,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; } @@ -328,6 +418,8 @@ do_captured_list_thread_ids (struct ui_out *uiout, void *arg) for (tp = thread_list; tp; tp = tp->next) { + if (tp->state_ == THREAD_EXITED) + continue; num++; ui_out_field_int (uiout, "thread-id", tp->num); } @@ -478,13 +570,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; } @@ -516,9 +605,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; } @@ -530,25 +621,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; @@ -556,11 +653,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 @@ -572,10 +699,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; @@ -627,7 +754,7 @@ set_executing (ptid_t ptid, int executing) /* 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 @@ -635,24 +762,18 @@ print_thread_info (struct ui_out *uiout, int requested_thread) { 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; @@ -660,13 +781,16 @@ print_thread_info (struct ui_out *uiout, int requested_thread) 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, " "); @@ -674,15 +798,19 @@ print_thread_info (struct ui_out *uiout, int requested_thread) 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 { @@ -704,24 +832,15 @@ print_thread_info (struct ui_out *uiout, int requested_thread) 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 has terminated. See `help thread'.\n", + current_thread); } } @@ -751,7 +870,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; @@ -762,21 +884,63 @@ 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; + + gdb_assert (frame_level >= 0); + + /* 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 + latter case is not failsafe, but since it's highly unlikely + the search by level finds the wrong frame, it's 99.9(9)% of + the time (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)))) + { + /* Cool, all is fine. */ + select_frame (frame); + return; + } - if (frame_id_eq (a_frame_id, null_frame_id)) - return; + frame = frame_find_by_id (a_frame_id); + if (frame != NULL) + { + /* Cool, refound it. */ + select_frame (frame); + return; + } - if ((selected_frame_info = frame_find_by_id (a_frame_id)) != NULL) + /* Nothing else to do, the frame layout really changed. + Tell the user. */ + if (!ui_out_is_mi_like_p (uiout)) { - select_frame (selected_frame_info); + 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); } } @@ -784,31 +948,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 @@ -824,27 +1023,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); @@ -863,13 +1052,7 @@ thread_apply_all_command (char *cmd, int from_tty) 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 @@ -878,11 +1061,7 @@ thread_apply_command (char *tidlist, int from_tty) 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")); @@ -892,18 +1071,10 @@ thread_apply_command (char *tidlist, int from_tty) 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; @@ -941,26 +1112,24 @@ thread_apply_command (char *tidlist, int from_tty) 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 @@ -971,11 +1140,17 @@ thread_command (char *tidstr, int from_tty) { 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; @@ -1023,10 +1198,16 @@ do_captured_thread_select (struct ui_out *uiout, void *tidstr) 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; } @@ -1052,19 +1233,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); diff --git a/gdb/top.c b/gdb/top.c index fe4b1d0..86738e7 100644 --- a/gdb/top.c +++ b/gdb/top.c @@ -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. */ diff --git a/gdb/utils.c b/gdb/utils.c index cdea5a6..e1901d1 100644 --- a/gdb/utils.c +++ b/gdb/utils.c @@ -208,6 +208,14 @@ make_cleanup (make_cleanup_ftype *function, void *arg) } 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); diff --git a/gdb/varobj.c b/gdb/varobj.c index e94a35e..3a0bf5e 100644 --- a/gdb/varobj.c +++ b/gdb/varobj.c @@ -2198,8 +2198,7 @@ c_value_of_root (struct varobj **var_handle) /* 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) -- cgit v1.1