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.
This commit is contained in:
Tom Tromey 2021-08-30 06:24:12 -06:00
parent 8a18382f94
commit bc75fb44c5
6 changed files with 277 additions and 18 deletions

View File

@ -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.

View File

@ -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<qcs_flags>;
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<gdb::option::option_def_group, 1>
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<std::pair<int, thread_info_ref>> 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<std::pair<int, thread_info_ref>> 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);
}

View File

@ -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

View File

@ -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<int> ada_task,
const char *cmd, int from_tty,
const qcs_flags &flags);
#endif /* GDBTHREAD_H */

View File

@ -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:.*"

View File

@ -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<int> 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);
}
}