gdb: add inferior-specific breakpoints
authorAndrew Burgess <aburgess@redhat.com>
Tue, 8 Nov 2022 12:32:51 +0000 (12:32 +0000)
committerAndrew Burgess <aburgess@redhat.com>
Thu, 17 Aug 2023 15:42:39 +0000 (16:42 +0100)
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/

25 files changed:
gdb/NEWS
gdb/breakpoint.c
gdb/breakpoint.h
gdb/doc/gdb.texinfo
gdb/doc/python.texi
gdb/guile/scm-breakpoint.c
gdb/infcmd.c
gdb/inferior.h
gdb/linespec.c
gdb/mi/mi-cmd-break.c
gdb/mi/mi-main.c
gdb/mi/mi-main.h
gdb/python/py-breakpoint.c
gdb/python/py-finishbreakpoint.c
gdb/testsuite/gdb.ada/tasks.exp
gdb/testsuite/gdb.linespec/cpcompletion.exp
gdb/testsuite/gdb.linespec/explicit.exp
gdb/testsuite/gdb.mi/new-ui-bp-deleted.c [new file with mode: 0644]
gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp [new file with mode: 0644]
gdb/testsuite/gdb.multi/inferior-specific-bp-1.c [new file with mode: 0644]
gdb/testsuite/gdb.multi/inferior-specific-bp-2.c [new file with mode: 0644]
gdb/testsuite/gdb.multi/inferior-specific-bp.exp [new file with mode: 0644]
gdb/testsuite/gdb.python/py-breakpoint.exp
gdb/testsuite/lib/completion-support.exp
gdb/testsuite/lib/mi-support.exp

index ef2b3b3a4f58480c4bb1f5ebd9650eb9abbea888..c4b1f7a7e3bbcb2a26358605da5b7de576852097 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
   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.
index e08f3444a65f85e91c5a124b8da43a41caff706e..f88ca1c9b65202a665da1f3ac819f0ab4c16921a 100644 (file)
@@ -99,7 +99,7 @@ static void create_breakpoints_sal (struct gdbarch *,
                                    gdb::unique_xmalloc_ptr<char>,
                                    gdb::unique_xmalloc_ptr<char>,
                                    enum bptype,
-                                   enum bpdisp, int, int,
+                                   enum bpdisp, int, int, int,
                                    int,
                                    int, int, int, unsigned);
 
@@ -385,6 +385,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1541,13 +1544,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  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<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");
 }
index f4896293bb7fd6021d1511b13c24bc977481a527..1a73d08a88723093706b816fd8a1058d4d9e6258 100644 (file)
@@ -583,7 +583,7 @@ struct breakpoint_ops
                                  struct linespec_result *,
                                  gdb::unique_xmalloc_ptr<char>,
                                  gdb::unique_xmalloc_ptr<char>,
-                                 enum bptype, enum bpdisp, int, int,
+                                 enum bptype, enum bpdisp, int, int, int,
                                  int, int, int, int, unsigned);
 };
 
@@ -863,6 +863,10 @@ struct breakpoint : public intrusive_list_node<breakpoint>
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -921,7 +925,7 @@ struct code_breakpoint : public breakpoint
                   gdb::unique_xmalloc_ptr<char> cond_string,
                   gdb::unique_xmalloc_ptr<char> extra_string,
                   enum bpdisp disposition,
-                  int thread, int task, int ignore_count,
+                  int thread, int task, int inferior, int ignore_count,
                   int from_tty,
                   int enabled, unsigned flags,
                   int display_canonical);
@@ -1601,6 +1605,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
                              struct location_spec *locspec,
                              const char *cond_string, int thread,
+                             int inferior,
                              const char *extra_string,
                              bool force_condition,
                              int parse_extra,
@@ -1744,6 +1749,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
index 814cc6d714a4d8280ea48acffb1788cd501aa3d5..8be9725d1a27820aebe955c2d7ead3f9421b750e 100644 (file)
@@ -3517,6 +3517,57 @@ Here, both inferior 2 and inferior 1 are running in the same program
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::      Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4480,8 +4531,9 @@ Expressions,,Ambiguous Expressions}, for a discussion of that
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7344,9 +7396,14 @@ thread exit, but also when you detach from the process with the
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31630,6 +31687,10 @@ Where this breakpoint's condition is evaluated, either @samp{host} or
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32221,7 +32282,7 @@ N.A.
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32287,6 +32348,9 @@ Restrict the breakpoint to the thread with the specified global
 time the breakpoint is requested.  Breakpoints created with a
 @var{thread-id} will automatically be deleted when the corresponding
 thread exits.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
index 505d1102c20faeae28f2dff360b29623a2f5ef7c..7460d6c8e319c58979f80d2339d3e3207e2dc0e5 100644 (file)
@@ -3439,7 +3439,10 @@ Return an object representing the current inferior.
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6303,9 +6306,24 @@ read-only.
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
index 6c6dacb38836566f9d51af98e1bf8f28a271162a..59254646bcc4415f8f380e1d1ac256917c90e352 100644 (file)
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
            const breakpoint_ops *ops =
              breakpoint_ops_for_location_spec (locspec.get (), false);
            create_breakpoint (get_current_arch (),
-                              locspec.get (), NULL, -1, NULL, false,
+                              locspec.get (), NULL, -1, -1, NULL, false,
                               0,
                               temporary, bp_breakpoint,
                               0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+                   _("Cannot have both 'thread' and 'inferior' "
+                     "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
index 96c5feafb1aefbae03087894514548fba7107cc5..fd85d27466ac71987644e3ae81c1bce4a8a91699 100644 (file)
@@ -416,17 +416,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-        inferior-specific using a condition.  A better solution would be to
-        have proper inferior-specific breakpoint support, in the breakpoint
-        machinery.  We could then avoid inserting a breakpoint in the program
-        spaces unrelated to this inferior.  */
-      const char *op
-       = ((current_language->la_language == language_ada
-           || current_language->la_language == language_pascal
-           || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-       ("-qualified %s if $_inferior %s %d", main_name (), op,
-        current_inferior ()->num);
+        inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+                                      main_name (),
+                                      current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
index 8f300a5bcc5ca9ff9aa2280eade64c5b763cec16..74578353482a46aec5c337520749894a7f933db9 100644 (file)
@@ -853,4 +853,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
index afa9eb4d3ac4d98cf47c1db98f454302c8747d7a..fd9f54d4afd70bd444cea2a46df11ed4dffe2103 100644 (file)
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
index 0777fcbd35e6b26b56a695fecadd44b41d9de752..975bc1c5ddec3db37f5afa31c32e826b42cfbc0e 100644 (file)
@@ -173,6 +173,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   const char *condition = NULL;
   int pending = 0;
@@ -191,7 +192,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -205,6 +207,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -247,6 +250,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
          if (!valid_global_thread_id (thread))
            error (_("Unknown thread %d."), thread);
          break;
+       case THREAD_GROUP_OPT:
+         thread_group = mi_parse_thread_group_id (oarg);
+         break;
        case PENDING_OPT:
          pending = 1;
          break;
@@ -360,7 +366,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
        error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+                    thread, thread_group,
                     extra_string.c_str (),
                     force_condition,
                     0 /* condition and thread are valid.  */,
index 0ac2c74153de965ade3073ebc8cd2137dbe8b484..b76940e740324493de8cf28e599650de300be786 100644 (file)
@@ -1762,8 +1762,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2796,6 +2795,21 @@ mi_cmd_complete (const char *command, const char *const *argv, int argc)
                       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
index cb17921c150854ba0811630985238605ebd2deda..b35544baef10295861e88c02daa573af835ec49c 100644 (file)
@@ -75,4 +75,10 @@ extern void mi_cmd_fix_breakpoint_script_output (const char *command,
                                                 const char *const *argv,
                                                 int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
index fa1570e1a049cb3a2caeb87e7bf9a2d04cb0962b..cb0645156908f700c0936e5520e9e36d46f87985 100644 (file)
@@ -288,11 +288,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+                      _("Cannot have both 'thread' and 'inferior' "
+                        "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+                      _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+       return -1;
+
+      if (!valid_global_inferior_id (id))
+       {
+         PyErr_SetString (PyExc_RuntimeError,
+                          _("Invalid inferior ID."));
+         return -1;
+       }
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+                      _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+                      _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+                        "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+                      _("Cannot have both 'thread' and 'inferior' conditions "
+                        "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+                      _("Cannot have both 'task' and 'inferior' conditions "
+                        "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -704,6 +779,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -942,7 +1031,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
              = breakpoint_ops_for_location_spec (locspec.get (), false);
 
            create_breakpoint (gdbpy_enter::get_gdbarch (),
-                              locspec.get (), NULL, -1, NULL, false,
+                              locspec.get (), NULL, -1, -1, NULL, false,
                               0,
                               temporary_bp, type,
                               0,
@@ -1376,6 +1465,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
index b71e5fafc4614a0e5944b043cc4383094294c64a..fa2139ba5d2e4188a44e92a66f72f65240b8868f 100644 (file)
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
        = new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-                        locspec.get (), NULL, thread, NULL, false,
+                        locspec.get (), NULL, thread, -1, NULL, false,
                         0,
                         1 /*temp_flag*/,
                         bp_breakpoint,
index eb7ee5c995147fb8d39f19429421330c9a4b781c..603d43f7c36a03442ce791310c9df67a7b39ec87 100644 (file)
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
index fbe19b49d43cf7115ff9d0b71daf4e2cfa9346ad..f005707b9da7c9e3099ab9639eb161f567ca2cfd 100644 (file)
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
index b08d65953d22dba7bb89233396c83905bfbaef99..668002d903895be29eb12c44bb0602c261573d90 100644 (file)
@@ -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 (file)
index 0000000..c171ef7
--- /dev/null
@@ -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 (file)
index 0000000..57e69ef
--- /dev/null
@@ -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 (file)
index 0000000..8f86d8c
--- /dev/null
@@ -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 (file)
index 0000000..e5b20b6
--- /dev/null
@@ -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 (file)
index 0000000..1f65732
--- /dev/null
@@ -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'\\."
index df17d646b28c6e67c2010c1f4c21427278f60895..b2c39d70c67d7d6180aae7c3b58a0b84493ef2cb 100644 (file)
@@ -153,6 +153,8 @@ proc_with_prefix test_bkpt_basic { } {
        "Check repr for a thread breakpoint"
     gdb_py_test_silent_cmd "python blist\[1\].thread = None" \
        "clear breakpoint thread" 0
+    gdb_test "python print (blist\[1\].inferior)" \
+       "None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
        "True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -255,6 +257,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
        "check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+       return 0
+    }
+
+    with_test_prefix "thread" {
+       delete_breakpoints
+       gdb_test "break multiply thread 1"
+       gdb_test "python bp = gdb.breakpoints ()\[0\]"
+       gdb_test "python print(bp.thread)" "1"
+       gdb_test "python print(bp.inferior)" "None"
+       gdb_test "python bp.inferior = 1" \
+           "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+       gdb_test_no_output "python bp.thread = None"
+       gdb_test_no_output "python bp.inferior = 1" \
+           "set the inferior now the thread has been cleared"
+       gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+       delete_breakpoints
+       gdb_test "break multiply inferior 1"
+       gdb_test "python bp = gdb.breakpoints ()\[0\]"
+       gdb_test "python print(bp.thread)" "None"
+       gdb_test "python print(bp.inferior)" "1"
+       gdb_test "python bp.thread = 1" \
+           "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+       gdb_test_no_output "python bp.inferior = None"
+       gdb_test_no_output "python bp.thread = 1" \
+           "set the thread now the inferior has been cleared"
+       gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -900,6 +942,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
index ea73c3bb367c58bb6d5210367201f0811c983746..fdc512838c3428181db69dec392408291c07797a 100644 (file)
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
        {"-function" "-label" "-line" "-qualified" "-source"}
index 49d5e2ef272756b7223cfe199deb91870dfd8724..3cd94b03c15f1df21397b8abf37f02a5535fa492 100644 (file)
@@ -2542,7 +2542,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2556,12 +2556,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
        {func .*} {file .*} {fullname .*} {line .*}
-       {thread-groups \\\[.*\\\]} {thread ""}}
+       {thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-                     fullname line thread-groups] {
-       lappend attr_list $attr [set $attr]
+                     fullname line thread-groups inferior] {
+       if {$attr ne "inferior" || [set $attr] ne ""} {
+           lappend attr_list $attr [set $attr]
+       }
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2635,7 +2637,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2656,7 +2658,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
        {times .*} {ignore 0}
        {script ""} {original-location .*} {cond ""} {evaluated-by ""}
-       {locations .*} {thread ""}}
+       {locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2665,6 +2667,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+       lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
                    $attr_list $thread $cond ${evaluated-by} $times \
                    $ignore $script ${original-location}]