From bc75fb44c5693114b3dc654a2e4b39c9b5a9ca26 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Mon, 30 Aug 2021 06:24:12 -0600 Subject: [PATCH] Implement 'task apply' This adds a 'task apply' command, which is the Ada tasking analogue of 'thread apply'. Unlike 'thread apply', it doesn't offer the 'ascending' flag; but otherwise it's essentially the same. --- gdb/NEWS | 3 + gdb/ada-tasks.c | 204 ++++++++++++++++++++++++++++- gdb/doc/gdb.texinfo | 35 +++++ gdb/gdbthread.h | 17 +++ gdb/testsuite/gdb.ada/rdv_wait.exp | 5 + gdb/thread.c | 31 ++--- 6 files changed, 277 insertions(+), 18 deletions(-) diff --git a/gdb/NEWS b/gdb/NEWS index b0a3a0867ea..13b66286876 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -24,6 +24,9 @@ show varsize-limit These are now deprecated aliases for "set max-value-size" and "show max-value-size". +task apply [all | TASK-IDS...] [FLAG]... COMMAND + Like "thread apply", but applies COMMAND to Ada tasks. + watch [...] task ID Watchpoints can now be restricted to a specific Ada task. diff --git a/gdb/ada-tasks.c b/gdb/ada-tasks.c index 9271859c329..519878ad061 100644 --- a/gdb/ada-tasks.c +++ b/gdb/ada-tasks.c @@ -1472,6 +1472,163 @@ ada_tasks_new_objfile_observer (struct objfile *objfile) ada_tasks_invalidate_inferior_data (inf); } +/* The qcs command line flags for the "task apply" commands. Keep + this in sync with the "frame apply" commands. */ + +using qcs_flag_option_def + = gdb::option::flag_option_def; + +static const gdb::option::option_def task_qcs_flags_option_defs[] = { + qcs_flag_option_def { + "q", [] (qcs_flags *opt) { return &opt->quiet; }, + N_("Disables printing the task information."), + }, + + qcs_flag_option_def { + "c", [] (qcs_flags *opt) { return &opt->cont; }, + N_("Print any error raised by COMMAND and continue."), + }, + + qcs_flag_option_def { + "s", [] (qcs_flags *opt) { return &opt->silent; }, + N_("Silently ignore any errors or empty output produced by COMMAND."), + }, +}; + +/* Create an option_def_group for the "task apply all" options, with + FLAGS as context. */ + +static inline std::array +make_task_apply_all_options_def_group (qcs_flags *flags) +{ + return {{ + { {task_qcs_flags_option_defs}, flags }, + }}; +} + +/* Create an option_def_group for the "task apply" options, with + FLAGS as context. */ + +static inline gdb::option::option_def_group +make_task_apply_options_def_group (qcs_flags *flags) +{ + return {{task_qcs_flags_option_defs}, flags}; +} + +/* Implementation of 'task apply all'. */ + +static void +task_apply_all_command (const char *cmd, int from_tty) +{ + qcs_flags flags; + + auto group = make_task_apply_all_options_def_group (&flags); + gdb::option::process_options + (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); + + validate_flags_qcs ("task apply all", &flags); + + if (cmd == nullptr || *cmd == '\0') + error (_("Please specify a command at the end of 'task apply all'")); + + update_thread_list (); + ada_build_task_list (); + + inferior *inf = current_inferior (); + struct ada_tasks_inferior_data *data = get_ada_tasks_inferior_data (inf); + + /* Save a copy of the thread list and increment each thread's + refcount while executing the command in the context of each + thread, in case the command affects this. */ + std::vector> thr_list_cpy; + + for (int i = 1; i <= data->task_list.size (); ++i) + { + ada_task_info &task = data->task_list[i - 1]; + if (!ada_task_is_alive (&task)) + continue; + + thread_info *tp = find_thread_ptid (inf, task.ptid); + if (tp == nullptr) + warning (_("Unable to compute thread ID for task %s.\n" + "Cannot switch to this task."), + task_to_str (i, &task).c_str ()); + else + thr_list_cpy.emplace_back (i, thread_info_ref::new_reference (tp)); + } + + scoped_restore_current_thread restore_thread; + + for (const auto &info : thr_list_cpy) + if (switch_to_thread_if_alive (info.second.get ())) + thread_try_catch_cmd (info.second.get (), info.first, cmd, + from_tty, flags); +} + +/* Implementation of 'task apply'. */ + +static void +task_apply_command (const char *tidlist, int from_tty) +{ + + if (tidlist == nullptr || *tidlist == '\0') + error (_("Please specify a task ID list")); + + update_thread_list (); + ada_build_task_list (); + + inferior *inf = current_inferior (); + struct ada_tasks_inferior_data *data = get_ada_tasks_inferior_data (inf); + + /* Save a copy of the thread list and increment each thread's + refcount while executing the command in the context of each + thread, in case the command affects this. */ + std::vector> thr_list_cpy; + + number_or_range_parser parser (tidlist); + while (!parser.finished ()) + { + int num = parser.get_number (); + + if (num < 1 || num - 1 >= data->task_list.size ()) + warning (_("no Ada Task with number %d"), num); + else + { + ada_task_info &task = data->task_list[num - 1]; + if (!ada_task_is_alive (&task)) + continue; + + thread_info *tp = find_thread_ptid (inf, task.ptid); + if (tp == nullptr) + warning (_("Unable to compute thread ID for task %s.\n" + "Cannot switch to this task."), + task_to_str (num, &task).c_str ()); + else + thr_list_cpy.emplace_back (num, + thread_info_ref::new_reference (tp)); + } + } + + qcs_flags flags; + const char *cmd = parser.cur_tok (); + + auto group = make_task_apply_options_def_group (&flags); + gdb::option::process_options + (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); + + validate_flags_qcs ("task apply", &flags); + + if (*cmd == '\0') + error (_("Please specify a command following the task ID list")); + + scoped_restore_current_thread restore_thread; + + for (const auto &info : thr_list_cpy) + if (switch_to_thread_if_alive (info.second.get ())) + thread_try_catch_cmd (info.second.get (), info.first, cmd, + from_tty, flags); +} + void _initialize_tasks (); void _initialize_tasks () @@ -1482,11 +1639,52 @@ _initialize_tasks () gdb::observers::new_objfile.attach (ada_tasks_new_objfile_observer, "ada-tasks"); + static struct cmd_list_element *task_cmd_list; + static struct cmd_list_element *task_apply_list; + + /* Some new commands provided by this module. */ add_info ("tasks", info_tasks_command, _("Provide information about all known Ada tasks.")); - add_cmd ("task", class_run, task_command, - _("Use this command to switch between Ada tasks.\n\ + + add_prefix_cmd ("task", class_run, task_command, + _("Use this command to switch between Ada tasks.\n\ Without argument, this command simply prints the current task ID."), - &cmdlist); + &task_cmd_list, 1, &cmdlist); + +#define TASK_APPLY_OPTION_HELP "\ +Prints per-inferior task number followed by COMMAND output.\n\ +\n\ +By default, an error raised during the execution of COMMAND\n\ +aborts \"task apply\".\n\ +\n\ +Options:\n\ +%OPTIONS%" + + static const auto task_apply_opts + = make_task_apply_options_def_group (nullptr); + + static std::string task_apply_help = gdb::option::build_help (_("\ +Apply a command to a list of tasks.\n\ +Usage: task apply ID... [OPTION]... COMMAND\n\ +ID is a space-separated list of IDs of tasks to apply COMMAND on.\n" +TASK_APPLY_OPTION_HELP), task_apply_opts); + + add_prefix_cmd ("apply", class_run, + task_apply_command, + task_apply_help.c_str (), + &task_apply_list, 1, + &task_cmd_list); + + static const auto task_apply_all_opts + = make_task_apply_all_options_def_group (nullptr); + + static std::string task_apply_all_help = gdb::option::build_help (_("\ +Apply a command to all tasks in the current inferior.\n\ +\n\ +Usage: task apply all [OPTION]... COMMAND\n" +TASK_APPLY_OPTION_HELP), task_apply_all_opts); + + add_cmd ("all", class_run, task_apply_all_command, + task_apply_all_help.c_str (), &task_apply_list); } diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 40f3e245a11..d0c5bcf18e1 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -18537,6 +18537,41 @@ from the current task to the given task. #4 0x804aacc in un () at un.adb:5 @end smallexample +@item task apply [@var{task-id-list} | all] [@var{flag}]@dots{} @var{command} +The @code{task apply} command is the Ada tasking analogue of +@code{thread apply} (@pxref{Threads}). It allows you to apply the +named @var{command} to one or more tasks. Specify the tasks that you +want affected using a list of task IDs, or specify @code{all} to apply +to all tasks. + +The @var{flag} arguments control what output to produce and how to +handle errors raised when applying @var{command} to a task. +@var{flag} must start with a @code{-} directly followed by one letter +in @code{qcs}. If several flags are provided, they must be given +individually, such as @code{-c -q}. + +By default, @value{GDBN} displays some task information before the +output produced by @var{command}, and an error raised during the +execution of a @var{command} will abort @code{task apply}. The +following flags can be used to fine-tune this behavior: + +@table @code +@item -c +The flag @code{-c}, which stands for @samp{continue}, causes any +errors in @var{command} to be displayed, and the execution of +@code{task apply} then continues. +@item -s +The flag @code{-s}, which stands for @samp{silent}, causes any errors +or empty output produced by a @var{command} to be silently ignored. +That is, the execution continues, but the task information and errors +are not printed. +@item -q +The flag @code{-q} (@samp{quiet}) disables printing the task +information. +@end table + +Flags @code{-c} and @code{-s} cannot be used together. + @item break @var{location} task @var{taskno} @itemx break @var{location} task @var{taskno} if @dots{} @cindex breakpoints and tasks, in Ada diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index a4c1244a3e1..e229a5f09bd 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -975,4 +975,21 @@ extern void thread_select (const char *tidstr, class thread_info *thr); target to get the name. May return nullptr. */ extern const char *thread_name (thread_info *thread); +/* Switch to thread TP if it is alive. Returns true if successfully + switched, false otherwise. */ + +extern bool switch_to_thread_if_alive (thread_info *thr); + +/* Assuming that THR is the current thread, execute CMD. + If ADA_TASK is not empty, it is the Ada task ID, and will + be printed instead of the thread information. + FLAGS.QUIET controls the printing of the thread information. + FLAGS.CONT and FLAGS.SILENT control how to handle errors. Can throw an + exception if !FLAGS.SILENT and !FLAGS.CONT and CMD fails. */ + +extern void thread_try_catch_cmd (thread_info *thr, + gdb::optional ada_task, + const char *cmd, int from_tty, + const qcs_flags &flags); + #endif /* GDBTHREAD_H */ diff --git a/gdb/testsuite/gdb.ada/rdv_wait.exp b/gdb/testsuite/gdb.ada/rdv_wait.exp index 602443567a7..821adef4b1a 100644 --- a/gdb/testsuite/gdb.ada/rdv_wait.exp +++ b/gdb/testsuite/gdb.ada/rdv_wait.exp @@ -35,3 +35,8 @@ runto "break_me" gdb_test "task 2" \ [join {"\\\[Switching to task 2 \"mit\"\\\].*" \ ".*foo\\.t \\(.*\\).*foo\\.adb:.*"} ""] + +gdb_test "task apply 1 -q frame" ".*pck\\.break_me.*" + +gdb_test "task apply all frame" \ + "Task ID 1:.*pck\\.break_me.*Task ID 2:.*" diff --git a/gdb/thread.c b/gdb/thread.c index ee9f05325cd..6c792eccb8f 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -662,10 +662,9 @@ thread_alive (thread_info *tp) return target_thread_alive (tp->ptid); } -/* Switch to thread TP if it is alive. Returns true if successfully - switched, false otherwise. */ +/* See gdbthreads.h. */ -static bool +bool switch_to_thread_if_alive (thread_info *thr) { scoped_restore_current_thread restore_thread; @@ -1428,23 +1427,25 @@ tp_array_compar_descending (const thread_info_ref &a, const thread_info_ref &b) return (a->per_inf_num > b->per_inf_num); } -/* Assuming that THR is the current thread, execute CMD. - FLAGS.QUIET controls the printing of the thread information. - FLAGS.CONT and FLAGS.SILENT control how to handle errors. Can throw an - exception if !FLAGS.SILENT and !FLAGS.CONT and CMD fails. */ +/* See gdbthread.h. */ -static void -thr_try_catch_cmd (thread_info *thr, const char *cmd, int from_tty, - const qcs_flags &flags) +void +thread_try_catch_cmd (thread_info *thr, gdb::optional ada_task, + const char *cmd, int from_tty, + const qcs_flags &flags) { gdb_assert (is_current_thread (thr)); /* The thread header is computed before running the command since the command can change the inferior, which is not permitted by thread_target_id_str. */ - std::string thr_header = - string_printf (_("\nThread %s (%s):\n"), print_thread_id (thr), - thread_target_id_str (thr).c_str ()); + std::string thr_header; + if (ada_task.has_value ()) + thr_header = string_printf (_("\nTask ID %d:\n"), *ada_task); + else + thr_header = string_printf (_("\nThread %s (%s):\n"), + print_thread_id (thr), + thread_target_id_str (thr).c_str ()); try { @@ -1576,7 +1577,7 @@ thread_apply_all_command (const char *cmd, int from_tty) for (thread_info_ref &thr : thr_list_cpy) if (switch_to_thread_if_alive (thr.get ())) - thr_try_catch_cmd (thr.get (), cmd, from_tty, flags); + thread_try_catch_cmd (thr.get (), {}, cmd, from_tty, flags); } } @@ -1738,7 +1739,7 @@ thread_apply_command (const char *tidlist, int from_tty) continue; } - thr_try_catch_cmd (tp, cmd, from_tty, flags); + thread_try_catch_cmd (tp, {}, cmd, from_tty, flags); } } -- 2.30.2