@@ -63,6 +63,13 @@
break foo thread 1 task 1
watch var thread 2 task 3
+* Breakpoints can now be inferior-specific. This is similar to the
+ existing thread-specific breakpoint support. Breakpoint conditions
+ can include the 'inferior' keyword followed by an inferior id (as
+ displayed in the 'info inferiors' output). It is invalid to use the
+ 'inferior' keyword with either the 'thread' or 'task' keywords when
+ creating a breakpoint.
+
* New commands
maintenance print record-instruction [ N ]
@@ -109,6 +116,14 @@ info main
without a thread restriction. The same is also true for the 'task'
field of an Ada task-specific breakpoint.
+** The -break-insert command now accepts a '-g thread-group-id' option
+ to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+ and in the result of the -break-insert command can now include an
+ optional 'inferior' field for both the main breakpoint, and each
+ location, when the breakpoint is inferior-specific.
+
* Python API
** The gdb.unwinder.Unwinder.name attribute is now read-only.
@@ -96,7 +96,7 @@ static void create_breakpoints_sal (struct gdbarch *,
gdb::unique_xmalloc_ptr<char>,
gdb::unique_xmalloc_ptr<char>,
enum bptype,
- enum bpdisp, int, int,
+ enum bpdisp, int, int, int,
int,
int, int, int, unsigned);
@@ -320,6 +320,9 @@ struct momentary_breakpoint : public code_breakpoint
disposition = disp_donttouch;
frame_id = frame_id_;
thread = thread_;
+
+ /* The inferior should have been set by the parent constructor. */
+ gdb_assert (inferior == -1);
}
void re_set () override;
@@ -1459,13 +1462,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
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 == -1);
+ /* THREAD should be -1, meaning no thread restriction, or it should be a
+ valid global thread-id, which are greater than zero. */
+ gdb_assert (thread == -1 || thread > 0);
- int old_thread = b->thread;
+ /* It is not valid to set a thread restriction for a breakpoint that
+ already has task or inferior restriction. */
+ gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
+ int old_thread = b->thread;
b->thread = thread;
if (old_thread != thread)
gdb::observers::breakpoint_modified.notify (b);
@@ -1473,16 +1478,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
/* See breakpoint.h. */
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+ /* INFERIOR should be -1, meaning no inferior restriction, or it should
+ be a valid inferior number, which are greater than zero. */
+ gdb_assert (inferior == -1 || inferior > 0);
+
+ /* It is not valid to set an inferior restriction for a breakpoint that
+ already has a task or thread restriction. */
+ gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+ int old_inferior = b->inferior;
+ b->inferior = inferior;
+ if (old_inferior != inferior)
+ gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h. */
+
void
breakpoint_set_task (struct breakpoint *b, int task)
{
- /* It is invalid to set the task field to anything other than -1 (which
- means no task restriction) if a thread restriction is already in
- place. */
- gdb_assert (task == -1 || b->thread == -1);
+ /* TASK should be -1, meaning no task restriction, or it should be a
+ valid task-id, which are greater than zero. */
+ gdb_assert (task == -1 || task > 0);
- int old_task = b->task;
+ /* It is not valid to set a task restriction for a breakpoint that
+ already has a thread or inferior restriction. */
+ gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
+ int old_task = b->task;
b->task = task;
if (old_task != task)
gdb::observers::breakpoint_modified.notify (b);
@@ -3157,6 +3183,12 @@ insert_breakpoint_locations (void)
&& !valid_global_thread_id (bl->owner->thread))
continue;
+ /* Or inferior specific breakpoints if the inferior no longer
+ exists. */
+ if (bl->owner->inferior != -1
+ && !valid_global_inferior_id (bl->owner->inferior))
+ continue;
+
switch_to_program_space_and_thread (bl->pspace);
/* For targets that support global breakpoints, there's no need
@@ -3257,6 +3289,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
}
}
+/* Called when inferior INF has been removed from GDB. Remove associated
+ per-inferior breakpoints. */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+ for (breakpoint *b : all_breakpoints_safe ())
+ {
+ if (b->inferior == inf->num && user_breakpoint_p (b))
+ {
+ /* Tell the user the breakpoint has been deleted. But only for
+ breakpoints that would not normally have been deleted at the
+ next stop anyway. */
+ if (b->disposition != disp_del
+ && b->disposition != disp_del_at_next_stop)
+ gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+ b->number, inf->num);
+ delete_breakpoint (b);
+ }
+ }
+}
+
/* See breakpoint.h. */
void
@@ -5466,6 +5521,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
evaluating the condition if this isn't the specified
thread/task. */
if ((b->thread != -1 && b->thread != thread->global_num)
+ || (b->inferior != -1 && b->inferior != thread->inf->num)
|| (b->task != -1 && b->task != ada_get_task_number (thread)))
{
infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6495,6 +6551,8 @@ print_one_breakpoint_location (struct breakpoint *b,
uiout->field_signed ("thread", b->thread);
else if (b->task != -1)
uiout->field_signed ("task", b->task);
+ else if (b->inferior != -1)
+ uiout->field_signed ("inferior", b->inferior);
}
uiout->text ("\n");
@@ -6557,6 +6615,13 @@ print_one_breakpoint_location (struct breakpoint *b,
uiout->text ("\n");
}
+ if (!part_of_multiple && b->inferior != -1)
+ {
+ uiout->text ("\tstop only in inferior ");
+ uiout->field_signed ("inferior", b->inferior);
+ uiout->text ("\n");
+ }
+
if (!part_of_multiple)
{
if (b->hit_count)
@@ -7542,7 +7607,10 @@ delete_longjmp_breakpoint (int thread)
if (b->type == bp_longjmp || b->type == bp_exception)
{
if (b->thread == thread)
- delete_breakpoint (b);
+ {
+ gdb_assert (b->inferior == -1);
+ delete_breakpoint (b);
+ }
}
}
@@ -7553,7 +7621,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
if (b->type == bp_longjmp || b->type == bp_exception)
{
if (b->thread == thread)
- b->disposition = disp_del_at_next_stop;
+ {
+ gdb_assert (b->inferior == -1);
+ b->disposition = disp_del_at_next_stop;
+ }
}
}
@@ -7606,6 +7677,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
ALL_BREAKPOINTS_SAFE (b, b_tmp)
if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
{
+ gdb_assert (b->inferior == -1);
struct breakpoint *dummy_b = b->related_breakpoint;
/* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8440,7 +8512,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
gdb::unique_xmalloc_ptr<char> cond_string_,
gdb::unique_xmalloc_ptr<char> extra_string_,
enum bpdisp disposition_,
- int thread_, int task_, int ignore_count_,
+ int thread_, int task_, int inferior_,
+ int ignore_count_,
int from_tty,
int enabled_, unsigned flags,
int display_canonical_)
@@ -8464,10 +8537,14 @@ 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 == -1);
+ /* At most one of thread, task, or inferior can be set on any breakpoint. */
+ gdb_assert (((thread == -1 ? 0 : 1)
+ + (task == -1 ? 0 : 1)
+ + (inferior == -1 ? 0 : 1)) <= 1);
+
thread = thread_;
task = task_;
+ inferior = inferior_;
cond_string = std::move (cond_string_);
extra_string = std::move (extra_string_);
@@ -8569,7 +8646,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
gdb::unique_xmalloc_ptr<char> cond_string,
gdb::unique_xmalloc_ptr<char> extra_string,
enum bptype type, enum bpdisp disposition,
- int thread, int task, int ignore_count,
+ int thread, int task, int inferior, int ignore_count,
int from_tty,
int enabled, int internal, unsigned flags,
int display_canonical)
@@ -8583,7 +8660,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
std::move (cond_string),
std::move (extra_string),
disposition,
- thread, task, ignore_count,
+ thread, task, inferior, ignore_count,
from_tty,
enabled, flags,
display_canonical);
@@ -8612,7 +8689,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
gdb::unique_xmalloc_ptr<char> cond_string,
gdb::unique_xmalloc_ptr<char> extra_string,
enum bptype type, enum bpdisp disposition,
- int thread, int task, int ignore_count,
+ int thread, int task, int inferior,
+ int ignore_count,
int from_tty,
int enabled, int internal, unsigned flags)
{
@@ -8636,7 +8714,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
std::move (cond_string),
std::move (extra_string),
type, disposition,
- thread, task, ignore_count,
+ thread, task, inferior, ignore_count,
from_tty, enabled, internal, flags,
canonical->special_display);
}
@@ -8766,21 +8844,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
}
}
-/* Given TOK, a string specification of condition and thread, as
- accepted by the 'break' command, extract the condition
- string and thread number and set *COND_STRING and *THREAD.
- PC identifies the context at which the condition should be parsed.
- If no condition is found, *COND_STRING is set to NULL.
- If no thread is found, *THREAD is set to -1. */
+/* Given TOK, a string specification of condition and thread, as accepted
+ by the 'break' command, extract the condition string into *COND_STRING.
+ If no condition string is found then *COND_STRING is set to nullptr.
+
+ If the breakpoint specification has an associated thread, task, or
+ inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+ respectively, otherwise these arguments are set to -1 (for THREAD and
+ INFERIOR) or 0 (for TASK).
+
+ PC identifies the context at which the condition should be parsed. */
static void
find_condition_and_thread (const char *tok, CORE_ADDR pc,
gdb::unique_xmalloc_ptr<char> *cond_string,
- int *thread, int *task,
+ int *thread, int *inferior, int *task,
gdb::unique_xmalloc_ptr<char> *rest)
{
cond_string->reset ();
*thread = -1;
+ *inferior = -1;
*task = -1;
rest->reset ();
bool force = false;
@@ -8797,7 +8880,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
if ((*tok == '"' || *tok == ',') && rest)
{
rest->reset (savestring (tok, strlen (tok)));
- return;
+ break;
}
end_tok = skip_to_space (tok);
@@ -8837,6 +8920,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
if (*task != -1)
error (_("You can specify only one of thread or task."));
+ if (*inferior != -1)
+ error (_("You can specify only one of inferior or thread."));
+
tok = end_tok + 1;
thr = parse_thread_id (tok, &tmptok);
if (tok == tmptok)
@@ -8844,6 +8930,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
*thread = thr->global_num;
tok = tmptok;
}
+ else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+ {
+ if (*inferior != -1)
+ error(_("You can specify only one inferior."));
+
+ if (*task != -1)
+ error (_("You can specify only one of inferior or task."));
+
+ if (*thread != -1)
+ error (_("You can specify only one of inferior or thread."));
+
+ char *tmptok;
+ tok = end_tok + 1;
+ *inferior = strtol (tok, &tmptok, 0);
+ if (tok == tmptok)
+ error (_("Junk after inferior keyword."));
+ if (!valid_global_inferior_id (*inferior))
+ error (_("Unknown inferior number %d."), *inferior);
+ tok = tmptok;
+ }
else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
{
char *tmptok;
@@ -8854,6 +8960,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
if (*thread != -1)
error (_("You can specify only one of thread or task."));
+ if (*inferior != -1)
+ error (_("You can specify only one of inferior or task."));
+
tok = end_tok + 1;
*task = strtol (tok, &tmptok, 0);
if (tok == tmptok)
@@ -8865,7 +8974,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
else if (rest)
{
rest->reset (savestring (tok, strlen (tok)));
- return;
+ break;
}
else
error (_("Junk at end of arguments."));
@@ -8881,7 +8990,7 @@ static void
find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
const char *input,
gdb::unique_xmalloc_ptr<char> *cond_string,
- int *thread, int *task,
+ int *thread, int *inferior, int *task,
gdb::unique_xmalloc_ptr<char> *rest)
{
int num_failures = 0;
@@ -8889,6 +8998,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
{
gdb::unique_xmalloc_ptr<char> cond;
int thread_id = -1;
+ int inferior_id = -1;
int task_id = -1;
gdb::unique_xmalloc_ptr<char> remaining;
@@ -8901,11 +9011,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
try
{
find_condition_and_thread (input, sal.pc, &cond, &thread_id,
- &task_id, &remaining);
+ &inferior_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 == -1);
+ /* A value of -1 indicates that these fields are unset. At most
+ one of these fields should be set (to a value other than -1)
+ at this point. */
+ gdb_assert (((thread_id == -1 ? 1 : 0)
+ + (task_id == -1 ? 1 : 0)
+ + (inferior_id == -1 ? 1 : 0)) >= 2);
*thread = thread_id;
+ *inferior = inferior_id;
*task = task_id;
*rest = std::move (remaining);
break;
@@ -8995,7 +9110,8 @@ int
create_breakpoint (struct gdbarch *gdbarch,
location_spec *locspec,
const char *cond_string,
- int thread, const char *extra_string,
+ int thread, int inferior,
+ const char *extra_string,
bool force_condition, int parse_extra,
int tempflag, enum bptype type_wanted,
int ignore_count,
@@ -9009,6 +9125,10 @@ create_breakpoint (struct gdbarch *gdbarch,
int task = -1;
int prev_bkpt_count = breakpoint_count;
+ gdb_assert (thread == -1 || thread > 0);
+ gdb_assert (inferior == -1 || inferior > 0);
+ gdb_assert (thread == -1 || inferior == -1);
+
gdb_assert (ops != NULL);
/* If extra_string isn't useful, set it to NULL. */
@@ -9084,7 +9204,8 @@ create_breakpoint (struct gdbarch *gdbarch,
const linespec_sals &lsal = canonical.lsals[0];
find_condition_and_thread_for_sals (lsal.sals, extra_string,
- &cond, &thread, &task, &rest);
+ &cond, &thread, &inferior,
+ &task, &rest);
cond_string_copy = std::move (cond);
extra_string_copy = std::move (rest);
}
@@ -9134,7 +9255,7 @@ create_breakpoint (struct gdbarch *gdbarch,
std::move (extra_string_copy),
type_wanted,
tempflag ? disp_del : disp_donttouch,
- thread, task, ignore_count,
+ thread, task, inferior, ignore_count,
from_tty, enabled, internal, flags);
}
else
@@ -9203,7 +9324,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
create_breakpoint (get_current_arch (),
locspec.get (),
- NULL, 0, arg, false, 1 /* parse arg */,
+ NULL,
+ -1 /* thread */, -1 /* inferior */,
+ arg, false, 1 /* parse arg */,
tempflag, type_wanted,
0 /* Ignore count */,
pending_break_support,
@@ -9315,7 +9438,8 @@ dprintf_command (const char *arg, int from_tty)
create_breakpoint (get_current_arch (),
locspec.get (),
- NULL, 0, arg, false, 1 /* parse arg */,
+ NULL, -1, -1,
+ arg, false, 1 /* parse arg */,
0, bp_dprintf,
0 /* Ignore count */,
pending_break_support,
@@ -10071,6 +10195,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
const char *cond_end = NULL;
enum bptype bp_type;
int thread = -1;
+ int inferior = -1;
/* Flag to indicate whether we are going to use masks for
the hardware watchpoint. */
bool use_mask = false;
@@ -10125,12 +10250,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
if (task != -1)
error (_("You can specify only one of thread or task."));
+ if (inferior != -1)
+ error (_("You can specify only one of inferior or thread."));
+
/* Extract the thread ID from the next token. */
thr = parse_thread_id (value_start, &endp);
-
- /* Check if the user provided a valid thread ID. */
- if (*endp != ' ' && *endp != '\t' && *endp != '\0')
- invalid_thread_id_error (value_start);
+ if (value_start == endp)
+ error (_("Junk after thread keyword."));
thread = thr->global_num;
}
@@ -10144,12 +10270,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
if (thread != -1)
error (_("You can specify only one of thread or task."));
+ if (inferior != -1)
+ error (_("You can specify only one of inferior or task."));
+
task = strtol (value_start, &tmp, 0);
if (tmp == value_start)
error (_("Junk after task keyword."));
if (!valid_task_id (task))
error (_("Unknown task %d."), task);
}
+ else if (toklen == 8 && startswith (tok, "inferior"))
+ {
+ /* Support for watchpoints will be added in a later commit. */
+ error (_("Cannot use 'inferior' keyword with watchpoints"));
+ }
else if (toklen == 4 && startswith (tok, "mask"))
{
/* We've found a "mask" token, which means the user wants to
@@ -10322,6 +10456,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
/* At most one of thread or task can be set on a watchpoint. */
gdb_assert (thread == -1 || task == -1);
w->thread = thread;
+ w->inferior = inferior;
w->task = task;
w->disposition = disp_donttouch;
w->pspace = current_program_space;
@@ -12231,7 +12366,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
enum bptype type_wanted,
enum bpdisp disposition,
int thread,
- int task, int ignore_count,
+ int task, int inferior,
+ int ignore_count,
int from_tty, int enabled,
int internal, unsigned flags)
{
@@ -12257,7 +12393,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
std::move (cond_string),
std::move (extra_string),
disposition,
- thread, task, ignore_count,
+ thread, task, inferior, ignore_count,
from_tty, enabled, flags,
canonical->special_display));
@@ -12890,10 +13026,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
if (condition_not_parsed && extra_string != NULL)
{
gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
- int local_thread, local_task;
+ int local_thread, local_task, local_inferior;
find_condition_and_thread_for_sals (sals, extra_string.get (),
&local_cond, &local_thread,
+ &local_inferior,
&local_task, &local_extra);
gdb_assert (cond_string == nullptr);
if (local_cond != nullptr)
@@ -13767,7 +13904,7 @@ trace_command (const char *arg, int from_tty)
create_breakpoint (get_current_arch (),
locspec.get (),
- NULL, 0, arg, false, 1 /* parse arg */,
+ NULL, -1, -1, arg, false, 1 /* parse arg */,
0 /* tempflag */,
bp_tracepoint /* type_wanted */,
0 /* Ignore count */,
@@ -13785,7 +13922,7 @@ ftrace_command (const char *arg, int from_tty)
current_language);
create_breakpoint (get_current_arch (),
locspec.get (),
- NULL, 0, arg, false, 1 /* parse arg */,
+ NULL, -1, -1, arg, false, 1 /* parse arg */,
0 /* tempflag */,
bp_fast_tracepoint /* type_wanted */,
0 /* Ignore count */,
@@ -13823,7 +13960,7 @@ strace_command (const char *arg, int from_tty)
create_breakpoint (get_current_arch (),
locspec.get (),
- NULL, 0, arg, false, 1 /* parse arg */,
+ NULL, -1, -1, arg, false, 1 /* parse arg */,
0 /* tempflag */,
type /* type_wanted */,
0 /* Ignore count */,
@@ -13892,7 +14029,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
current_language);
if (!create_breakpoint (get_current_arch (),
locspec.get (),
- utp->cond_string.get (), -1, addr_str,
+ utp->cond_string.get (), -1, -1, addr_str,
false /* force_condition */,
0 /* parse cond/thread */,
0 /* tempflag */,
@@ -14982,4 +15119,6 @@ This is useful for formatted output in user-defined commands."));
"breakpoint");
gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
"breakpoint");
+ gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+ "breakpoint");
}
@@ -585,7 +585,7 @@ struct breakpoint_ops
struct linespec_result *,
gdb::unique_xmalloc_ptr<char>,
gdb::unique_xmalloc_ptr<char>,
- enum bptype, enum bpdisp, int, int,
+ enum bptype, enum bpdisp, int, int, int,
int, int, int, int, unsigned);
};
@@ -803,6 +803,10 @@ struct breakpoint
care. */
int thread = -1;
+ /* Inferior number for inferior-specific breakpoint, or -1 if this
+ breakpoint is for all inferiors. */
+ int inferior = -1;
+
/* Ada task number for task-specific breakpoint, or -1 if don't
care. */
int task = -1;
@@ -858,7 +862,7 @@ struct code_breakpoint : public breakpoint
gdb::unique_xmalloc_ptr<char> cond_string,
gdb::unique_xmalloc_ptr<char> extra_string,
enum bpdisp disposition,
- int thread, int task, int ignore_count,
+ int thread, int task, int inferior, int ignore_count,
int from_tty,
int enabled, unsigned flags,
int display_canonical);
@@ -1538,6 +1542,7 @@ enum breakpoint_create_flags
extern int create_breakpoint (struct gdbarch *gdbarch,
struct location_spec *locspec,
const char *cond_string, int thread,
+ int inferior,
const char *extra_string,
bool force_condition,
int parse_extra,
@@ -1681,6 +1686,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
extern void breakpoint_set_thread (struct breakpoint *b, int thread);
+/* Set the inferior for breakpoint B to INFERIOR. If INFERIOR is -1, make
+ the breakpoint work for any inferior. */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
/* Set the task for this breakpoint. If TASK is -1, make the breakpoint
work for any task. Passing a value other than -1 for TASK should only
be done if b->thread is -1; it is not valid to try and set both a thread
@@ -3509,6 +3509,57 @@
space as a result of inferior 1 having executed a @code{vfork} call.
@end table
+@menu
+* Inferior-Specific Breakpoints:: Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint. The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}. For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
@node Threads
@section Debugging Programs with Multiple Threads
@@ -4471,8 +4522,9 @@
situation.
It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
@item break
When called without any arguments, @code{break} sets a breakpoint at
@@ -7323,9 +7375,14 @@
Process}), or if @value{GDBN} loses the remote connection
(@pxref{Remote Debugging}), etc. Note that with some targets,
@value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
command.
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
@node Interrupted System Calls
@subsection Interrupted System Calls
@@ -31546,6 +31603,10 @@
If this is a thread-specific breakpoint, then this identifies the
thread in which the breakpoint can trigger.
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
@item task
If this breakpoint is restricted to a particular Ada task, then this
field will hold the task identifier.
@@ -32137,7 +32198,7 @@
@smallexample
-break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
[ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
- [ -p @var{thread-id} ] [ @var{locspec} ]
+ [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
@end smallexample
@noindent
@@ -32200,6 +32261,9 @@
@item -p @var{thread-id}
Restrict the breakpoint to the thread with the specified global
@var{thread-id}.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
@item --qualified
This option makes @value{GDBN} interpret a function name specified as
a complete fully-qualified name.
@@ -3419,7 +3419,10 @@
A @code{gdb.Inferior} object has the following attributes:
@defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}. You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
@end defvar
@anchor{gdbpy_inferior_connection}
@@ -6221,9 +6224,24 @@
@anchor{python_breakpoint_thread}
@defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id. If the breakpoint is not thread-specific, this
-attribute is @code{None}. This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id. If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id. If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
@end defvar
@defvar Breakpoint.task
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
const breakpoint_ops *ops =
breakpoint_ops_for_location_spec (locspec.get (), false);
create_breakpoint (get_current_arch (),
- locspec.get (), NULL, -1, NULL, false,
+ locspec.get (), NULL, -1, -1, NULL, false,
0,
temporary, bp_breakpoint,
0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
else
SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
+ if (bp_smob->bp->inferior != -1 && id != -1)
+ scm_misc_error (FUNC_NAME,
+ _("Cannot have both 'thread' and 'inferior' "
+ "conditions on a breakpoint"), SCM_EOL);
+
breakpoint_set_thread (bp_smob->bp, id);
return SCM_UNSPECIFIED;
@@ -425,17 +425,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
if (run_how == RUN_STOP_AT_MAIN)
{
/* To avoid other inferiors hitting this breakpoint, make it
- inferior-specific using a condition. A better solution would be to
- have proper inferior-specific breakpoint support, in the breakpoint
- machinery. We could then avoid inserting a breakpoint in the program
- spaces unrelated to this inferior. */
- const char *op
- = ((current_language->la_language == language_ada
- || current_language->la_language == language_pascal
- || current_language->la_language == language_m2) ? "=" : "==");
- std::string arg = string_printf
- ("-qualified %s if $_inferior %s %d", main_name (), op,
- current_inferior ()->num);
+ inferior-specific. */
+ std::string arg = string_printf ("-qualified %s inferior %d",
+ main_name (),
+ current_inferior ()->num);
tbreak_command (arg.c_str (), 0);
}
@@ -815,4 +815,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
extern void switch_to_inferior_and_push_target
(inferior *new_inf, bool no_connection, inferior *org_inf);
+/* Return true if ID is a valid global inferior number. */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+ for (inferior *inf : all_inferiors ())
+ if (inf->num == id)
+ return true;
+ return false;
+}
+
#endif /* !defined (INFERIOR_H) */
@@ -254,9 +254,9 @@ enum linespec_token_type
/* List of keywords. This is NULL-terminated so that it can be used
as enum completer. */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
#define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
/* A token of the linespec lexer */
@@ -172,6 +172,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
int hardware = 0;
int temp_p = 0;
int thread = -1;
+ int thread_group = -1;
int ignore_count = 0;
char *condition = NULL;
int pending = 0;
@@ -190,7 +191,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
enum opt
{
HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
- IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+ IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+ PENDING_OPT, DISABLE_OPT,
TRACEPOINT_OPT,
FORCE_CONDITION_OPT,
QUALIFIED_OPT,
@@ -204,6 +206,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
{"c", CONDITION_OPT, 1},
{"i", IGNORE_COUNT_OPT, 1},
{"p", THREAD_OPT, 1},
+ {"g", THREAD_GROUP_OPT, 1},
{"f", PENDING_OPT, 0},
{"d", DISABLE_OPT, 0},
{"a", TRACEPOINT_OPT, 0},
@@ -244,6 +247,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
case THREAD_OPT:
thread = atol (oarg);
break;
+ case THREAD_GROUP_OPT:
+ thread_group = mi_parse_thread_group_id (oarg);
+ break;
case PENDING_OPT:
pending = 1;
break;
@@ -357,7 +363,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
error (_("Garbage '%s' at end of location"), address);
}
- create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+ create_breakpoint (get_current_arch (), locspec.get (), condition,
+ thread, thread_group,
extra_string.c_str (),
force_condition,
0 /* condition and thread are valid. */,
@@ -1745,8 +1745,7 @@ mi_cmd_remove_inferior (const char *command, char **argv, int argc)
if (argc != 1)
error (_("-remove-inferior should be passed a single argument"));
- if (sscanf (argv[0], "i%d", &id) != 1)
- error (_("the thread group id is syntactically invalid"));
+ id = mi_parse_thread_group_id (argv[0]);
inf_to_remove = find_inferior_id (id);
if (inf_to_remove == NULL)
@@ -2757,6 +2756,21 @@ mi_cmd_complete (const char *command, char **argv, int argc)
result.number_matches == max_completions ? "1" : "0");
}
+/* See mi-main.h. */
+int
+mi_parse_thread_group_id (const char *id)
+{
+ if (*id != 'i')
+ error (_("thread group id should start with an 'i'"));
+
+ char *end;
+ long num = strtol (id + 1, &end, 10);
+
+ if (*end != '\0' || num > INT_MAX)
+ error (_("invalid thread group id '%s'"), id);
+
+ return (int) num;
+}
void _initialize_mi_main ();
void
@@ -76,4 +76,10 @@ extern void mi_cmd_fix_multi_location_breakpoint_output (const char *command,
extern void mi_cmd_fix_breakpoint_script_output (const char *command,
char **argv, int argc);
+/* Parse a thread-group-id from ID, and return the integer part of the
+ ID. A valid thread-group-id is the character 'i' followed by an
+ integer that is greater than zero. */
+
+extern int mi_parse_thread_group_id (const char *id);
+
#endif /* MI_MI_MAIN_H */
@@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
return -1;
}
+ if (self_bp->bp->inferior != -1 && id != -1)
+ {
+ PyErr_SetString (PyExc_RuntimeError,
+ _("Cannot have both 'thread' and 'inferior' "
+ "conditions on a breakpoint"));
+ return -1;
+ }
+
breakpoint_set_thread (self_bp->bp, id);
return 0;
}
+/* Python function to set the inferior of a breakpoint. */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+ gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+ long id;
+
+ BPPY_SET_REQUIRE_VALID (self_bp);
+
+ if (newvalue == NULL)
+ {
+ PyErr_SetString (PyExc_TypeError,
+ _("Cannot delete 'inferior' attribute."));
+ return -1;
+ }
+ else if (PyLong_Check (newvalue))
+ {
+ if (!gdb_py_int_as_long (newvalue, &id))
+ return -1;
+
+ if (!valid_global_inferior_id (id))
+ {
+ PyErr_SetString (PyExc_RuntimeError,
+ _("Invalid inferior ID."));
+ return -1;
+ }
+ }
+ else if (newvalue == Py_None)
+ id = -1;
+ else
+ {
+ PyErr_SetString (PyExc_TypeError,
+ _("The value of 'inferior' must be an integer or None."));
+ return -1;
+ }
+
+ if (self_bp->bp->type != bp_breakpoint
+ && self_bp->bp->type != bp_hardware_breakpoint)
+ {
+ PyErr_SetString (PyExc_RuntimeError,
+ _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+ "of this type"));
+ return -1;
+ }
+
+ if (self_bp->bp->thread != -1 && id != -1)
+ {
+ PyErr_SetString (PyExc_RuntimeError,
+ _("Cannot have both 'thread' and 'inferior' conditions "
+ "on a breakpoint"));
+ return -1;
+ }
+
+ if (self_bp->bp->task != -1 && id != -1)
+ {
+ PyErr_SetString (PyExc_RuntimeError,
+ _("Cannot have both 'task' and 'inferior' conditions "
+ "on a breakpoint"));
+ return -1;
+ }
+
+ breakpoint_set_inferior (self_bp->bp, id);
+
+ return 0;
+}
+
/* Python function to set the (Ada) task of a breakpoint. */
static int
bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure)
return gdb_py_object_from_longest (self_bp->bp->thread).release ();
}
+/* Python function to get the breakpoint's inferior ID. */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+ gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+ BPPY_REQUIRE_VALID (self_bp);
+
+ if (self_bp->bp->inferior == -1)
+ Py_RETURN_NONE;
+
+ return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
/* Python function to get the breakpoint's task ID (in Ada). */
static PyObject *
bppy_get_task (PyObject *self, void *closure)
@@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
= breakpoint_ops_for_location_spec (locspec.get (), false);
create_breakpoint (gdbpy_enter::get_gdbarch (),
- locspec.get (), NULL, -1, NULL, false,
+ locspec.get (), NULL, -1, -1, NULL, false,
0,
temporary_bp, type,
0,
@@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
If the value is None, then this breakpoint is not thread-specific.\n\
No other type of value can be used.", NULL },
+ { "inferior", bppy_get_inferior, bppy_set_inferior,
+ "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint. If the value is None, then this breakpoint is not\n\
+inferior-specific. No other type of value can be used.", NULL },
{ "task", bppy_get_task, bppy_set_task,
"Thread ID for the breakpoint.\n\
If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
location_spec_up locspec
= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
create_breakpoint (gdbpy_enter::get_gdbarch (),
- locspec.get (), NULL, thread, NULL, false,
+ locspec.get (), NULL, thread, -1, NULL, false,
0,
1 /*temp_flag*/,
bp_breakpoint,
@@ -58,6 +58,8 @@ 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 "break break_me inferior 1 task 1" \
+ "You can specify only one of inferior 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" \
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
}
# Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
proc_with_prefix keywords-after-function {} {
set explicit_list \
@@ -412,6 +412,7 @@ namespace eval $testfile {
"-qualified"
"-source"
"if"
+ "inferior"
"task"
"thread"
}
new file mode 100644
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2023 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int
+foo (void)
+{
+ return 0;
+}
+
+int
+main (void)
+{
+ int res = foo ();
+ return res;
+}
new file mode 100644
@@ -0,0 +1,108 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Check for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted. Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+ return -1
+}
+
+# Helper proc to create a breakpoint location regexp. NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+ return [mi_make_breakpoint_loc \
+ -number "$num" \
+ -enabled "y" \
+ -func "foo" \
+ -inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+ mi_gdb_exit
+
+ if {$mode eq "separate"} {
+ set start_ops "separate-mi-tty"
+ } else {
+ set start_ops ""
+ }
+
+ if [mi_gdb_start $start_ops] {
+ return
+ }
+
+ # Load a test binary into inferior 1.
+ mi_gdb_load ${binfile}
+
+ # Setup inferior 2, including loading an exec file.
+ mi_gdb_test "-add-inferior" \
+ [multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+ "~\"\\\[New inferior 2\\\]\\\\n\"" \
+ "\~\"Added inferior 2\\\\n\"" \
+ "\\^done,inferior=\"\[^\"\]+\"" ] \
+ "mi add inferior 2"
+ mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+ "\\^done" \
+ "set executable of inferior 2"
+
+ # Build regexp for the two locations.
+ set loc1 [make_bp_loc "$::decimal\\.1"]
+ set loc2 [make_bp_loc "$::decimal\\.2"]
+
+ # Create the inferior-specific breakpoint.
+ mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+ -inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+ set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+ if {$mode eq "separate"} {
+ # In 'separate' mode we delete the inferior from the CLI, and
+ # then look for the breakpoint-deleted notification on the MI.
+ with_spawn_id $gdb_main_spawn_id {
+ gdb_test "inferior 1" ".*"
+ gdb_test "remove-inferiors 2" \
+ "Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+ }
+
+ gdb_test_multiple "" "check for b/p deleted notification on MI" {
+ -re "=breakpoint-deleted,id=\"$bpnum\"" {
+ pass $gdb_test_name
+ }
+ }
+ } else {
+ # In the non-separate mode we delete the inferior from the MI
+ # and expect to immediately see a breakpoint-deleted
+ # notification.
+ mi_gdb_test "-remove-inferior i2" \
+ [multi_line \
+ "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+ "=breakpoint-deleted,id=\"$bpnum\"" \
+ "=thread-group-removed,id=\"i2\"" \
+ "\\^done"]
+ }
+}
new file mode 100644
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2022-2023 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+ /* Nothing. */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+ int i;
+
+ for (i = 0; i < 10; ++i)
+ global_var = 0;
+}
+
+static void
+bar (void)
+{
+ global_var = 0;
+
+ foo ();
+}
+
+
+int
+main (void)
+{
+ global_var = 0;
+ foo ();
+ bar ();
+ stop_breakpt ();
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2022-2023 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+ /* Nothing. */
+}
+
+int
+main (void)
+{
+ int ret = baz ();
+ stop_breakpt ();
+ return ret;
+}
+
+static int
+bar (void)
+{
+ return baz ();
+}
+
+static int
+foo (void)
+{
+ return 0;
+}
+
+static int
+baz (void)
+{
+ return foo ();
+}
new file mode 100644
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+ return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+ return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+ return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+ return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+ return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail. Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+ "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+ "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+ "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list. TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+ gdb_test_multiple "info breakpoints $bp_number" $testname {
+ -re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+ exp_continue
+ }
+
+ -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+ set saw_header true
+ exp_continue
+ }
+
+ -re "^\\s+stop only in inferior 1\r\n" {
+ set saw_inf_cond true
+ exp_continue
+ }
+
+ -re "^\\s+breakpoint already hit $::decimal times\r\n" {
+ exp_continue
+ }
+
+ -re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+ incr location_count
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ with_test_prefix $gdb_test_name {
+ gdb_assert { $saw_header \
+ && $location_count == $expected_loc_count \
+ && $saw_inf_cond } \
+ $gdb_test_name
+ }
+ }
+ }
+}
+
+# Create an inferior-specific breakpoint. Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+ "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+ "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+ $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+ "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+ "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+ "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+ "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+ "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+ $bp_number\.$decimal, foo \\(\\) .*" \
+ "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+ "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+ "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+ "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+ "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+ "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+ "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+ "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+ $bp_number 2
+
+# Now create another new inferior, then remove inferior 1. As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+ "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+ "No breakpoint or watchpoint matching '$bp_number'\\."
@@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } {
"Get Breakpoint List" 0
gdb_test "python print (blist\[1\].thread)" \
"None" "Check breakpoint thread"
+ gdb_test "python print (blist\[1\].inferior)" \
+ "None" "Check breakpoint inferior"
gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
"True" "Check breakpoint type"
gdb_test "python print (blist\[0\].number)" \
@@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
"check number of lines in commands"
}
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+ global srcfile testfile hex decimal
+
+ # Start with a fresh gdb.
+ clean_restart ${testfile}
+
+ if {![runto_main]} {
+ return 0
+ }
+
+ with_test_prefix "thread" {
+ delete_breakpoints
+ gdb_test "break multiply thread 1"
+ gdb_test "python bp = gdb.breakpoints ()\[0\]"
+ gdb_test "python print(bp.thread)" "1"
+ gdb_test "python print(bp.inferior)" "None"
+ gdb_test "python bp.inferior = 1" \
+ "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+ gdb_test_no_output "python bp.thread = None"
+ gdb_test_no_output "python bp.inferior = 1" \
+ "set the inferior now the thread has been cleared"
+ gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+ }
+
+ with_test_prefix "inferior" {
+ delete_breakpoints
+ gdb_test "break multiply inferior 1"
+ gdb_test "python bp = gdb.breakpoints ()\[0\]"
+ gdb_test "python print(bp.thread)" "None"
+ gdb_test "python print(bp.inferior)" "1"
+ gdb_test "python bp.thread = 1" \
+ "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+ gdb_test_no_output "python bp.inferior = None"
+ gdb_test_no_output "python bp.thread = 1" \
+ "set the thread now the inferior has been cleared"
+ gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+ }
+}
+
proc_with_prefix test_bkpt_invisible { } {
global srcfile testfile hex decimal
@@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
test_bkpt_basic
test_bkpt_deletion
test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
test_bkpt_invisible
test_hardware_breakpoints
test_catchpoints
@@ -27,7 +27,7 @@ namespace eval completion {
# List of all quote chars, including no-quote at all.
variable maybe_quoted_list {"" "'" "\""}
- variable keyword_list {"-force-condition" "if" "task" "thread"}
+ variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
variable explicit_opts_list \
{"-function" "-label" "-line" "-qualified" "-source"}
@@ -2528,7 +2528,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
# locations.
#
# All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
# thread-groups, and thread.
#
# For the option -thread the corresponding output field is only added
@@ -2542,12 +2542,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
proc mi_make_breakpoint_loc {args} {
parse_args {{number .*} {enabled .*} {addr .*}
{func .*} {file .*} {fullname .*} {line .*}
- {thread-groups \\\[.*\\\]} {thread ""}}
+ {thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
set attr_list {}
foreach attr [list number enabled addr func file \
- fullname line thread-groups] {
- lappend attr_list $attr [set $attr]
+ fullname line thread-groups inferior] {
+ if {$attr ne "inferior" || [set $attr] ne ""} {
+ lappend attr_list $attr [set $attr]
+ }
}
set result [mi_build_kv_pairs $attr_list]
@@ -2621,7 +2623,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
# locations.
#
# All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
# original-location, cond, evaluated-by, locations, and thread.
#
# Only if -script and -ignore are given will they appear in the output.
@@ -2642,7 +2644,7 @@ proc mi_make_breakpoint_multi {args} {
parse_args {{number .*} {type .*} {disp .*} {enabled .*}
{times .*} {ignore 0}
{script ""} {original-location .*} {cond ""} {evaluated-by ""}
- {locations .*} {thread ""}}
+ {locations .*} {thread ""} {inferior ""}}
set attr_list {}
foreach attr [list number type disp enabled] {
@@ -2651,6 +2653,12 @@ proc mi_make_breakpoint_multi {args} {
lappend attr_list "addr" "<MULTIPLE>"
+ # Only include the inferior field if it was set. This field is
+ # optional in the MI output.
+ if {$inferior ne ""} {
+ lappend attr_list "inferior" $inferior
+ }
+
set result [mi_make_breakpoint_1 \
$attr_list $thread $cond ${evaluated-by} $times \
$ignore $script ${original-location}]