public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>
Subject: [PATCHv3 2/2] gdb: add inferior-specific breakpoints
Date: Thu, 16 Mar 2023 17:03:45 +0000	[thread overview]
Message-ID: <91e7acb067377f5e3b6484a5119e9fa7c05e27bd.1678985326.git.aburgess@redhat.com> (raw)
In-Reply-To: <cover.1678985326.git.aburgess@redhat.com>

Eli,

You already reviwed these doc changes here:

  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

I've made no significant changes to the docs part since then.

Thanks,
Andrew

---

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 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 started investigating implementing this.  And though the
initial implementation is trivial to get going, like so many things,
the devil is in the detail.  While my hope is that I will bring this
optimisation to the list at some point in the near future I can't
guarantee it.  That said, we already have the issue of placing
breakpoints into inferiors that will never be hit (with thread
specific breakpoints), so I don't believe I'm adding anything worse
than we already have.  Personally, I'd be happy to see this new
feature merged even if the optimisation never happens.
---
 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 938 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index cc262f1f8a6..c963c0dd29c 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -61,6 +61,13 @@
     break foo thread 1 task 1
     watch var thread 2 task 3
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
@@ -104,6 +111,14 @@ show always-read-ctf
    without a thread restriction.  The same is also true for the 'task'
    field of an Ada task-specific breakpoint.
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 *** 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 abee22cd162..5e2065e31b8 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -96,7 +96,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -320,6 +320,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1459,13 +1462,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
@@ -1473,16 +1478,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     gdb::observers::breakpoint_modified.notify (b);
@@ -3157,6 +3183,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3257,6 +3289,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint *b : all_breakpoints_safe ())
+    {
+      if (b->inferior == inf->num && user_breakpoint_p (b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b->disposition != disp_del
+	      && b->disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b->number, inf->num);
+	  delete_breakpoint (b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5466,6 +5521,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6482,6 +6538,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");
@@ -6544,6 +6602,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)
@@ -7522,7 +7587,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);
+	  }
       }
 }
 
@@ -7533,7 +7601,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;
+	  }
       }
 }
 
@@ -7586,6 +7657,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
     if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
       {
+	gdb_assert (b->inferior == -1);
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
 	/* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8420,7 +8492,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_)
@@ -8444,10 +8517,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_);
@@ -8549,7 +8626,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)
@@ -8563,7 +8640,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);
@@ -8592,7 +8669,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)
 {
@@ -8616,7 +8694,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);
     }
@@ -8746,21 +8824,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;
@@ -8777,7 +8860,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);
@@ -8817,6 +8900,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)
@@ -8824,6 +8910,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;
@@ -8834,6 +8940,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)
@@ -8845,7 +8954,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."));
@@ -8861,7 +8970,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;
@@ -8869,6 +8978,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;
 
@@ -8881,11 +8991,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;
@@ -8975,7 +9090,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,
@@ -8989,6 +9105,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.  */
@@ -9064,7 +9184,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);
 	}
@@ -9114,7 +9235,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
@@ -9183,7 +9304,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,
@@ -9295,7 +9418,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,
@@ -10051,6 +10175,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;
@@ -10105,12 +10230,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;
 	    }
@@ -10124,12 +10250,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
@@ -10302,6 +10436,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;
@@ -12211,7 +12346,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)
 {
@@ -12237,7 +12373,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));
 
@@ -12870,10 +13006,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)
@@ -13747,7 +13884,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 */,
@@ -13765,7 +13902,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 */,
@@ -13803,7 +13940,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 */,
@@ -13872,7 +14009,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 */,
@@ -14959,4 +15096,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 03aecd15eff..d88bf8f5f89 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -584,7 +584,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);
 };
 
@@ -802,6 +802,10 @@ struct breakpoint
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -857,7 +861,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);
@@ -1537,6 +1541,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,
@@ -1680,6 +1685,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 98b7c984aa7..83687bf6eaf 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3509,6 +3509,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4471,8 +4522,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7323,9 +7375,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31508,6 +31565,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32099,7 +32160,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32162,6 +32223,9 @@
 @item -p @var{thread-id}
 Restrict the breakpoint to the thread with the specified global
 @var{thread-id}.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 54d5660543a..59c229819b1 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3290,7 +3290,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6092,9 +6095,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 2931df265d7..4e343b85656 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 c369b795757..acd1eb081f1 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -425,17 +425,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 72034cc4ffb..55df57666f9 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -815,4 +815,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..695dc6515ee 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 75957b75bad..fc1c4205311 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -172,6 +172,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   char *condition = NULL;
   int pending = 0;
@@ -190,7 +191,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -204,6 +206,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -244,6 +247,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	case THREAD_OPT:
 	  thread = atol (oarg);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -357,7 +363,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 0013e5dfafd..aaa13d1d65b 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1745,8 +1745,7 @@ mi_cmd_remove_inferior (const char *command, char **argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2757,6 +2756,21 @@ mi_cmd_complete (const char *command, char **argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index ff1d4ed84bc..0389a141cf5 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -76,4 +76,10 @@ extern void mi_cmd_fix_multi_location_breakpoint_output (const char *command,
 extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 char **argv, int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 880f1b5c1e2..2ac4d959a29 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 7122fa820f6..ba77b1d0a16 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 <http://www.gnu.org/licenses/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..93a0eb3de49
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Check for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "\\^done"]
+    }
+}
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 <http://www.gnu.org/licenses/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 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 <http://www.gnu.org/licenses/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 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 <http://www.gnu.org/licenses/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index ab81b7ade85..e0e76109eb3 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index 275f8874f15..85768deab94 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 3c2dd2fab2b..ee76062de42 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2524,7 +2524,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
@@ -2538,12 +2538,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]
@@ -2617,7 +2619,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.
@@ -2638,7 +2640,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] {
@@ -2647,6 +2649,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}]
-- 
2.25.4


  parent reply	other threads:[~2023-03-16 17:03 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
2022-11-28 11:25 ` [PATCH 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
2022-11-28 11:25 ` [PATCH 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
2022-11-28 11:25 ` [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
2022-12-23  8:37   ` Aktemur, Tankut Baris
2022-11-28 11:25 ` [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
2022-11-28 13:10   ` Eli Zaretskii
2022-11-28 11:25 ` [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
2022-11-28 13:18   ` Eli Zaretskii
2022-12-23 10:05   ` Aktemur, Tankut Baris
2023-01-19 19:13     ` Andrew Burgess
2023-01-20 13:12       ` Aktemur, Tankut Baris
2022-11-28 11:25 ` [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
2022-12-23 10:17   ` Aktemur, Tankut Baris
2022-12-23 10:55 ` [PATCH 0/6] Inferior specific breakpoints Aktemur, Tankut Baris
2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
2023-02-02 17:50     ` Pedro Alves
2023-02-04 15:46       ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
2023-02-04 16:22     ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
2023-02-02 18:13     ` Pedro Alves
2023-02-06 14:48       ` Andrew Burgess
2023-02-06 17:01         ` Pedro Alves
2023-02-07 14:42           ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
2023-01-20 13:22     ` Eli Zaretskii
2023-02-02 14:08       ` Andrew Burgess
2023-02-02 14:31         ` Eli Zaretskii
2023-02-02 18:21     ` Pedro Alves
2023-02-03 16:41       ` Andrew Burgess
2023-02-04  5:52         ` Joel Brobecker
2023-02-04 15:40           ` Andrew Burgess
2023-02-06 11:06       ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
2023-01-20 13:25     ` Eli Zaretskii
2023-02-02 19:17     ` Pedro Alves
2023-02-03 16:55       ` Andrew Burgess
2023-02-06 17:24         ` Pedro Alves
2023-02-16 12:56     ` Aktemur, Tankut Baris
2023-01-20  9:46   ` [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
2023-02-16 12:59     ` Aktemur, Tankut Baris
2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
2023-03-16 17:03     ` [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls Andrew Burgess
2023-04-03 14:12       ` Andrew Burgess
2023-03-16 17:03     ` Andrew Burgess [this message]
2023-04-03 14:14     ` [PATCHv4] gdb: add inferior-specific breakpoints Andrew Burgess
2023-05-15 19:15       ` [PATCHv5] " Andrew Burgess
2023-05-30 20:41         ` [PATCHv6] " Andrew Burgess
2023-07-07 10:23           ` [PATCHv7] " Andrew Burgess
2023-08-17 15:53             ` [PUSHEDv8] " Andrew Burgess
2023-08-23  8:06               ` [PUSHED] gdb: add missing notify_breakpoint_modified call Andrew Burgess
2023-08-23  8:19               ` [PUSHED] gdb/testsuite: improve MI support for inferior specific breakpoints Andrew Burgess

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=91e7acb067377f5e3b6484a5119e9fa7c05e27bd.1678985326.git.aburgess@redhat.com \
    --to=aburgess@redhat.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).