diff options
25 files changed, 944 insertions, 88 deletions
@@ -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. diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index e08f344..f88ca1c 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -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); - 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) notify_breakpoint_modified (b); @@ -1556,15 +1561,36 @@ 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) 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"); } diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index f489629..1a73d08 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -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 diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 814cc6d..8be9725 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -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. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 505d110..7460d6c 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -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 diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c index 6c6dacb..5925464 100644 --- a/gdb/guile/scm-breakpoint.c +++ b/gdb/guile/scm-breakpoint.c @@ -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; diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 96c5fea..fd85d27 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -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); } diff --git a/gdb/inferior.h b/gdb/inferior.h index 8f300a5..7457835 100644 --- a/gdb/inferior.h +++ b/gdb/inferior.h @@ -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) */ diff --git a/gdb/linespec.c b/gdb/linespec.c index afa9eb4..fd9f54d 100644 --- a/gdb/linespec.c +++ b/gdb/linespec.c @@ -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 */ diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c index 0777fcb..975bc1c 100644 --- a/gdb/mi/mi-cmd-break.c +++ b/gdb/mi/mi-cmd-break.c @@ -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. */, diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 0ac2c74..b76940e 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -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 diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h index cb17921..b35544b 100644 --- a/gdb/mi/mi-main.h +++ b/gdb/mi/mi-main.h @@ -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 */ diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index fa1570e..cb06451 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -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\ diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c index b71e5fa..fa2139b 100644 --- a/gdb/python/py-finishbreakpoint.c +++ b/gdb/python/py-finishbreakpoint.c @@ -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, diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp index eb7ee5c..603d43f 100644 --- a/gdb/testsuite/gdb.ada/tasks.exp +++ b/gdb/testsuite/gdb.ada/tasks.exp @@ -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" \ diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp index fbe19b4..f005707 100644 --- a/gdb/testsuite/gdb.linespec/cpcompletion.exp +++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp @@ -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 \ diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp index b08d659..668002d 100644 --- a/gdb/testsuite/gdb.linespec/explicit.exp +++ b/gdb/testsuite/gdb.linespec/explicit.exp @@ -412,6 +412,7 @@ namespace eval $testfile { "-qualified" "-source" "if" + "inferior" "task" "thread" } diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c new file mode 100644 index 0000000..c171ef7 --- /dev/null +++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c @@ -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; +} diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp new file mode 100644 index 0000000..57e69ef --- /dev/null +++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp @@ -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"] + } +} diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c new file mode 100644 index 0000000..8f86d8c --- /dev/null +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c @@ -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; +} diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c new file mode 100644 index 0000000..e5b20b6 --- /dev/null +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c @@ -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 (); +} diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp new file mode 100644 index 0000000..1f65732 --- /dev/null +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp @@ -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'\\." diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp index df17d64..b2c39d7 100644 --- a/gdb/testsuite/gdb.python/py-breakpoint.exp +++ b/gdb/testsuite/gdb.python/py-breakpoint.exp @@ -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 diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp index ea73c3b..fdc5128 100644 --- a/gdb/testsuite/lib/completion-support.exp +++ b/gdb/testsuite/lib/completion-support.exp @@ -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"} diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp index 49d5e2e..3cd94b0 100644 --- a/gdb/testsuite/lib/mi-support.exp +++ b/gdb/testsuite/lib/mi-support.exp @@ -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}] |