gdb: only allow one of thread or task on breakpoints or watchpoints

After this mailing list posting:

  https://sourceware.org/pipermail/gdb-patches/2023-February/196607.html

it seems to me that in practice an Ada task maps 1:1 with a GDB
thread, and so it doesn't really make sense to allow uses to give both
a thread and a task within a single breakpoint or watchpoint
condition.

This commit updates GDB so that the user will get an error if both
are specified.

I've added new tests to cover the CLI as well as the Python and Guile
APIs.  For the Python and Guile testing, as far as I can tell, this
was the first testing for this corner of the APIs, so I ended up
adding more than just a single test.

For documentation I've added a NEWS entry, but I've not added anything
to the docs themselves.  Currently we document the commands with a
thread-id or task-id as distinct command, e.g.:

  'break LOCSPEC task TASKNO'
  'break LOCSPEC task TASKNO if ...'
  'break LOCSPEC thread THREAD-ID'
  'break LOCSPEC thread THREAD-ID if ...'

As such, I don't believe there is any indication that combining 'task'
and 'thread' would be expected to work; it seems clear to me in the
above that those four options are all distinct commands.

I think the NEWS entry is enough that if someone is combining these
keywords (it's not clear what the expected behaviour would be in this
case) then they can figure out that this was a deliberate change in
GDB, but for a new user, the manual doesn't suggest combining them is
OK, and any future attempt to combine them will give an error.

Approved-By: Pedro Alves <pedro@palves.net>
This commit is contained in:
Andrew Burgess 2023-02-06 13:04:16 +00:00
parent d088d944a0
commit 0a9ccb9dd7
6 changed files with 153 additions and 5 deletions

View File

@ -50,6 +50,12 @@
to 'max-value-size', GDB will now still print the array, however only
'max-value-size' worth of data will be added into the value history.
* For both the break and watch commands, it is now invalid to use both
the 'thread' and 'task' keywords within the same command. For
example the following commnds will now give an error:
break foo thread 1 task 1
watch var thread 2 task 3
* New commands
maintenance print record-instruction [ N ]

View File

@ -1454,12 +1454,16 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
gdb::observers::breakpoint_modified.notify (b);
}
/* Set the thread for this breakpoint. If THREAD is -1, make the
breakpoint work for any thread. */
/* See breakpoint.h. */
void
breakpoint_set_thread (struct breakpoint *b, int thread)
{
/* It is invalid to set the thread field to anything other than -1 (which
means no thread restriction) if a task restriction is already in
place. */
gdb_assert (thread == -1 || b->task == 0);
int old_thread = b->thread;
b->thread = thread;
@ -1467,12 +1471,16 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
gdb::observers::breakpoint_modified.notify (b);
}
/* Set the task for this breakpoint. If TASK is 0, make the
breakpoint work for any task. */
/* See breakpoint.h. */
void
breakpoint_set_task (struct breakpoint *b, int task)
{
/* It is invalid to set the task field to anything other than 0 (which
means no task restriction) if a thread restriction is already in
place. */
gdb_assert (task == 0 || b->thread == -1);
int old_task = b->task;
b->task = task;
@ -8443,6 +8451,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
gdb_assert (!sals.empty ());
/* At most one of thread or task can be set on any breakpoint. */
gdb_assert (thread == -1 || task == 0);
thread = thread_;
task = task_;
@ -8811,6 +8821,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
if (*thread != -1)
error(_("You can specify only one thread."));
if (*task != 0)
error (_("You can specify only one of thread or task."));
tok = end_tok + 1;
thr = parse_thread_id (tok, &tmptok);
if (tok == tmptok)
@ -8825,6 +8838,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
if (*task != 0)
error(_("You can specify only one task."));
if (*thread != -1)
error (_("You can specify only one of thread or task."));
tok = end_tok + 1;
*task = strtol (tok, &tmptok, 0);
if (tok == tmptok)
@ -8859,7 +8875,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
for (auto &sal : sals)
{
gdb::unique_xmalloc_ptr<char> cond;
int thread_id = 0;
int thread_id = -1;
int task_id = 0;
gdb::unique_xmalloc_ptr<char> remaining;
@ -8874,6 +8890,8 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
find_condition_and_thread (input, sal.pc, &cond, &thread_id,
&task_id, &remaining);
*cond_string = std::move (cond);
/* At most one of thread or task can be set. */
gdb_assert (thread_id == -1 || task_id == 0);
*thread = thread_id;
*task = task_id;
*rest = std::move (remaining);
@ -10091,6 +10109,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
if (thread != -1)
error(_("You can specify only one thread."));
if (task != 0)
error (_("You can specify only one of thread or task."));
/* Extract the thread ID from the next token. */
thr = parse_thread_id (value_start, &endp);
@ -10107,6 +10128,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
if (task != 0)
error(_("You can specify only one task."));
if (thread != -1)
error (_("You can specify only one of thread or task."));
task = strtol (value_start, &tmp, 0);
if (tmp == value_start)
error (_("Junk after task keyword."));
@ -10282,6 +10306,8 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
else
w.reset (new watchpoint (nullptr, bp_type));
/* At most one of thread or task can be set on a watchpoint. */
gdb_assert (thread == -1 || task == 0);
w->thread = thread;
w->task = task;
w->disposition = disp_donttouch;

View File

@ -1673,8 +1673,18 @@ extern void breakpoint_set_commands (struct breakpoint *b,
extern void breakpoint_set_silent (struct breakpoint *b, int silent);
/* Set the thread for this breakpoint. If THREAD is -1, make the
breakpoint work for any thread. Passing a value other than -1 for
THREAD should only be done if b->task is 0; it is not valid to try and
set both a thread and task restriction on a breakpoint. */
extern void breakpoint_set_thread (struct breakpoint *b, int thread);
/* Set the task for this breakpoint. If TASK is 0, make the breakpoint
work for any task. Passing a value other than 0 for TASK should only be
done if b->thread is -1; it is not valid to try and set both a thread
and task restriction on a breakpoint. */
extern void breakpoint_set_task (struct breakpoint *b, int task);
/* Clear the "inserted" flag in all breakpoints. */

View File

@ -773,6 +773,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, newvalue,
_("invalid thread id"));
}
if (bp_smob->bp->task != 0)
scm_misc_error (FUNC_NAME,
_("cannot set both task and thread attributes"),
SCM_EOL);
}
else if (gdbscm_is_false (newvalue))
id = -1;
@ -828,6 +833,11 @@ gdbscm_set_breakpoint_task_x (SCM self, SCM newvalue)
gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, newvalue,
_("invalid task id"));
}
if (bp_smob->bp->thread != -1)
scm_misc_error (FUNC_NAME,
_("cannot set both task and thread attributes"),
SCM_EOL);
}
else if (gdbscm_is_false (newvalue))
id = 0;

View File

@ -270,6 +270,13 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
_("Invalid thread ID."));
return -1;
}
if (self_bp->bp->task != 0)
{
PyErr_SetString (PyExc_RuntimeError,
_("Cannot set both task and thread attributes."));
return -1;
}
}
else if (newvalue == Py_None)
id = -1;
@ -321,6 +328,13 @@ bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
_("Invalid task ID."));
return -1;
}
if (self_bp->bp->thread != -1)
{
PyErr_SetString (PyExc_RuntimeError,
_("Cannot set both task and thread attributes."));
return -1;
}
}
else if (newvalue == Py_None)
id = 0;

View File

@ -14,6 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
load_lib "ada.exp"
load_lib "gdb-guile.exp"
load_lib "gdb-python.exp"
require allow_ada_tests
@ -39,10 +41,28 @@ gdb_test "info tasks" \
"\r\n"] \
"info tasks before inserting breakpoint"
# Confirm that the "info threads" output lines up with the tasks list.
gdb_test "info threads" \
[multi_line \
"\\*\\s+1\\s+\[^\r\n\]+\\s\"foo\"\\s\[^\r\n\]+" \
"\\s+2\\s+\[^\r\n\]+\\s\"task_list\\(1\\)\"\\s\[^\r\n\]+" \
"\\s+3\\s+\[^\r\n\]+\\s\"task_list\\(2\\)\"\\s\[^\r\n\]+" \
"\\s+4\\s+\[^\r\n\]+\\s\"task_list\\(3\\)\"\\s\[^\r\n\]+"]
# Check that multiple uses of the 'task' keyword will give an error.
gdb_test "break break_me task 1 task 3" "You can specify only one task\\."
gdb_test "watch j task 1 task 3" "You can specify only one task\\."
# Check that attempting to combine 'task' and 'thread' gives an error.
gdb_test "break break_me task 1 thread 1" \
"You can specify only one of thread or task\\."
gdb_test "break break_me thread 1 task 1" \
"You can specify only one of thread or task\\."
gdb_test "watch j task 1 thread 1" \
"You can specify only one of thread or task\\."
gdb_test "watch j thread 1 task 1" \
"You can specify only one of thread or task\\."
# Insert a breakpoint that should stop only if task 1 stops. Since
# task 1 never calls break_me, this shouldn't actually ever trigger.
# The fact that this breakpoint is created _before_ the next one
@ -68,6 +88,68 @@ set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 3" \
"check info breakpoints for task 3 breakpoint"
# Test the Python API for the breakpoint task attribute.
if {[allow_python_tests]} {
gdb_test_no_output "python bp = gdb.breakpoints()\[$bp_number - 1\]" \
"get gdb.Breakpoint from list"
gdb_test "python print(bp.task)" "3"
gdb_test "python print(bp.thread)" "None"
gdb_test "python bp.thread = 1" \
[multi_line \
"RuntimeError: Cannot set both task and thread attributes\\." \
"Error while executing Python code\\."] \
"try setting the thread, but expect an error"
gdb_test_no_output "python bp.task = None"
gdb_test_no_output "python bp.thread = 1"
gdb_test "python bp.task = 3" \
[multi_line \
"RuntimeError: Cannot set both task and thread attributes\\." \
"Error while executing Python code\\."] \
"try setting the task, but expect an error"
# Reset the breakpoint to the state required for the rest of this
# test.
gdb_test_no_output "python bp.thread = None"
gdb_test_no_output "python bp.task = 3"
}
# Test the Guile API for the breakpoint task attribute.
if {[allow_guile_tests]} {
gdb_install_guile_utils
gdb_install_guile_module
gdb_scm_test_silent_cmd "guile (define blist (breakpoints))" \
"get breakpoint list"
gdb_scm_test_silent_cmd "guile (define bp (list-ref blist (- $bp_number 1)))" \
"get <gdb:breakpoint> from list"
gdb_test "guile (print (breakpoint-task bp))" "= 3"
gdb_test "guile (print (breakpoint-thread bp))" "= #f"
gdb_test "guile (set-breakpoint-thread! bp 1)" \
[multi_line \
"ERROR: In procedure set-breakpoint-thread!:" \
"In procedure gdbscm_set_breakpoint_thread_x: cannot set both task and thread attributes" \
"Error while executing Scheme code."] \
"attempt to set thread, but expect an error"
gdb_scm_test_silent_cmd "guile (set-breakpoint-task! bp #f)" \
"clear breakpoint task attribute"
gdb_scm_test_silent_cmd "guile (set-breakpoint-thread! bp 1)" \
"set breakpoint thread now task is unset"
gdb_test "guile (set-breakpoint-task! bp 1)" \
[multi_line \
"ERROR: In procedure set-breakpoint-task!:" \
"In procedure gdbscm_set_breakpoint_task_x: cannot set both task and thread attributes" \
"Error while executing Scheme code."] \
"attempt to set task, but expect an error"
# Reset the breakpoint to the state required for the rest of this
# test.
gdb_scm_test_silent_cmd "guile (set-breakpoint-thread! bp #f)" \
"clear breakpoint thread attribute"
gdb_scm_test_silent_cmd "guile (set-breakpoint-task! bp 3)" \
"restore breakpoint task attribute"
}
# Continue to that breakpoint. Task 2 should hit it first, and GDB
# is expected to ignore that hit and resume the execution. Only then
# task 3 will hit our breakpoint, and GDB is expected to stop at that