gdb: add inferior-specific breakpoints

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints are deleted when the associated thread is
deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation: currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have a series on the mailing list[1] that implements this
optimisation for thread-specific breakpoints.  Once this series has
landed I'll update that series to also handle inferior specific
breakpoints in the same way.  For now, inferior specific breakpoints
are just slightly less optimal, but this is no different to
thread-specific breakpoints in a multi-inferior debug session, so I
don't see this as a huge problem.

[1] https://inbox.sourceware.org/gdb-patches/cover.1685479504.git.aburgess@redhat.com/
This commit is contained in:
Andrew Burgess 2022-11-08 12:32:51 +00:00
parent 0c9546b152
commit b080fe54fb
25 changed files with 944 additions and 88 deletions

View File

@ -98,6 +98,13 @@
user that the end of file has been reached, refers the user to the
newly added '.' argument
* 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
set debug breakpoint on|off
@ -162,6 +169,14 @@ info main
considered simple.) Support for this feature can be verified by using the
'-list-features' command, which should contain "simple-values-ref-types".
** 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
** gdb.ThreadExitedEvent added. Emits a ThreadEvent.
@ -257,6 +272,12 @@ info main
** gdb.Progspace now has the new method "objfile_for_address". This
returns the gdb.Objfile, if any, that covers a given address.
** gdb.Breakpoint now has an "inferior" attribute. If the
Breakpoint object is inferior specific then this attribute holds
the inferior-id (an integer). If the Breakpoint object is not
inferior specific, then this field contains None. This field can
be written too.
*** Changes in GDB 13
* MI version 1 is deprecated, and will be removed in GDB 14.

View File

@ -99,7 +99,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);
@ -385,6 +385,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;
@ -1541,13 +1544,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);
/* 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)
notify_breakpoint_modified (b);
@ -1555,16 +1560,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);
/* 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)
notify_breakpoint_modified (b);
@ -3244,6 +3270,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
@ -3344,6 +3376,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
@ -5554,6 +5609,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");
@ -6581,6 +6637,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");
@ -6643,6 +6701,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)
@ -7629,7 +7694,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);
}
}
}
@ -7640,7 +7708,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;
}
}
}
@ -7697,6 +7768,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
{
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
@ -8542,7 +8614,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_)
@ -8566,10 +8639,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_);
@ -8671,7 +8748,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)
@ -8685,7 +8762,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);
@ -8714,7 +8791,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)
{
@ -8738,7 +8816,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);
}
@ -8868,21 +8946,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;
@ -8899,7 +8982,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);
@ -8939,6 +9022,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)
@ -8946,6 +9032,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;
@ -8956,6 +9062,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)
@ -8967,7 +9076,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."));
@ -8983,7 +9092,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;
@ -8991,6 +9100,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;
@ -9003,11 +9113,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;
@ -9097,7 +9212,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,
@ -9111,6 +9227,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. */
@ -9186,7 +9306,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);
}
@ -9236,7 +9357,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
@ -9305,7 +9426,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,
@ -9417,7 +9540,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,
@ -10162,6 +10286,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;
@ -10216,12 +10341,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;
}
@ -10235,12 +10361,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
@ -10413,6 +10547,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;
@ -12350,7 +12485,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)
{
@ -12376,7 +12512,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));
@ -12995,10 +13131,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)
@ -13872,7 +14009,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 */,
@ -13890,7 +14027,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 */,
@ -13928,7 +14065,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 */,
@ -13997,7 +14134,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 */,
@ -15094,4 +15231,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");
}

View File

@ -583,7 +583,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);
};
@ -863,6 +863,10 @@ struct breakpoint : public intrusive_list_node<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;
@ -921,7 +925,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);
@ -1601,6 +1605,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,
@ -1744,6 +1749,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

View File

@ -3517,6 +3517,57 @@ Here, both inferior 2 and inferior 1 are running in the same program
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
@ -4480,8 +4531,9 @@ Expressions,,Ambiguous Expressions}, for a discussion of that
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
@ -7344,9 +7396,14 @@ thread exit, but also when you detach from the process with the
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
@ -31630,6 +31687,10 @@ Where this breakpoint's condition is evaluated, either @samp{host} or
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.
@ -32221,7 +32282,7 @@ N.A.
@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
@ -32287,6 +32348,9 @@ Restrict the breakpoint to the thread with the specified global
time the breakpoint is requested. Breakpoints created with a
@var{thread-id} will automatically be deleted when the corresponding
thread exits.
@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.

View File

@ -3439,7 +3439,10 @@ Return an object representing the current inferior.
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}
@ -6303,9 +6306,24 @@ read-only.
@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

View File

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

View File

@ -416,17 +416,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);
}

View File

@ -853,4 +853,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) */

View File

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

View File

@ -173,6 +173,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
int hardware = 0;
int temp_p = 0;
int thread = -1;
int thread_group = -1;
int ignore_count = 0;
const char *condition = NULL;
int pending = 0;
@ -191,7 +192,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
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,
@ -205,6 +207,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
{"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},
@ -247,6 +250,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
if (!valid_global_thread_id (thread))
error (_("Unknown thread %d."), thread);
break;
case THREAD_GROUP_OPT:
thread_group = mi_parse_thread_group_id (oarg);
break;
case PENDING_OPT:
pending = 1;
break;
@ -360,7 +366,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
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. */,

View File

@ -1762,8 +1762,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *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)
@ -2796,6 +2795,21 @@ mi_cmd_complete (const char *command, const char *const *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

View File

@ -75,4 +75,10 @@ extern void mi_cmd_fix_breakpoint_script_output (const char *command,
const char *const *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 */

View File

@ -288,11 +288,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)
@ -704,6 +779,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)
@ -942,7 +1031,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,
@ -1376,6 +1465,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\

View File

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

View File

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

View File

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

View File

@ -412,6 +412,7 @@ namespace eval $testfile {
"-qualified"
"-source"
"if"
"inferior"
"task"
"thread"
}

View File

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

View File

@ -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\[^\r\n\]*\\\\n\"" \
"\\^done,inferior=\"\[^\"\]+\"(?:,connection={.*})?" ] \
"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 \
"=thread-group-removed,id=\"i2\"" \
"~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
"=breakpoint-deleted,id=\"$bpnum\"" \
"\\^done"]
}
}

View File

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

View File

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

View File

@ -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'\\."

View File

@ -153,6 +153,8 @@ proc_with_prefix test_bkpt_basic { } {
"Check repr for a thread breakpoint"
gdb_py_test_silent_cmd "python blist\[1\].thread = None" \
"clear breakpoint thread" 0
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)" \
@ -255,6 +257,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
@ -900,6 +942,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

View File

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

View File

@ -2542,7 +2542,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
@ -2556,12 +2556,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]
@ -2635,7 +2637,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.
@ -2656,7 +2658,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] {
@ -2665,6 +2667,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}]