From: Andrew Burgess Date: Tue, 8 Nov 2022 12:32:51 +0000 (+0000) Subject: gdb: add inferior-specific breakpoints X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=b080fe54fb3;p=binutils-gdb.git 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/ --- diff --git a/gdb/NEWS b/gdb/NEWS index ef2b3b3a4f5..c4b1f7a7e3b 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -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 e08f3444a65..f88ca1c9b65 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -99,7 +99,7 @@ static void create_breakpoints_sal (struct gdbarch *, gdb::unique_xmalloc_ptr, gdb::unique_xmalloc_ptr, 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); @@ -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); - 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 cond_string_, gdb::unique_xmalloc_ptr 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 cond_string, gdb::unique_xmalloc_ptr 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 cond_string, gdb::unique_xmalloc_ptr 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 *cond_string, - int *thread, int *task, + int *thread, int *inferior, int *task, gdb::unique_xmalloc_ptr *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 &sals, const char *input, gdb::unique_xmalloc_ptr *cond_string, - int *thread, int *task, + int *thread, int *inferior, int *task, gdb::unique_xmalloc_ptr *rest) { int num_failures = 0; @@ -8991,6 +9100,7 @@ find_condition_and_thread_for_sals (const std::vector &sals, { gdb::unique_xmalloc_ptr cond; int thread_id = -1; + int inferior_id = -1; int task_id = -1; gdb::unique_xmalloc_ptr remaining; @@ -9003,11 +9113,16 @@ find_condition_and_thread_for_sals (const std::vector &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 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 f4896293bb7..1a73d08a887 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -583,7 +583,7 @@ struct breakpoint_ops struct linespec_result *, gdb::unique_xmalloc_ptr, gdb::unique_xmalloc_ptr, - 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 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 cond_string, gdb::unique_xmalloc_ptr 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 814cc6d714a..8be9725d1a2 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 505d1102c20..7460d6c8e31 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 6c6dacb3883..59254646bcc 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 96c5feafb1a..fd85d27466a 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 8f300a5bcc5..74578353482 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 afa9eb4d3ac..fd9f54d4afd 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 0777fcbd35e..975bc1c5dde 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 0ac2c74153d..b76940e7403 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 cb17921c150..b35544baef1 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 fa1570e1a04..cb064515690 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 b71e5fafc46..fa2139ba5d2 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 eb7ee5c9951..603d43f7c36 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 fbe19b49d43..f005707b9da 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 b08d65953d2..668002d9038 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 00000000000..c171ef71b9d --- /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 . */ + +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 00000000000..57e69ef6240 --- /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 . + +# 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 00000000000..8f86d8cdcea --- /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 . */ + +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 00000000000..e5b20b6e7a8 --- /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 . */ + +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 00000000000..1f6573268da --- /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 . + +# 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+\\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 df17d646b28..b2c39d70c67 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 ea73c3bb367..fdc512838c3 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 49d5e2ef272..3cd94b03c15 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" "" + # 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}]