[PATCHv6,10/10] gdb: only insert thread-specific breakpoints in the relevant inferior

Message ID 5c3e030571f7fef0a798ef25e43c078421855338.1701513410.git.aburgess@redhat.com
State New
Headers
Series [PATCHv6,01/10] gdb: create_breakpoint: add asserts and additional comments |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Testing passed

Commit Message

Andrew Burgess Dec. 2, 2023, 10:42 a.m. UTC
  Eli has already approved the NEWS changes in this commit.

---

This commit updates GDB so that thread or inferior specific
breakpoints are only inserted into the program space in which the
specific thread or inferior is running.

In terms of implementation, getting this basically working is easy
enough, now that a breakpoint's thread or inferior field is setup
prior to GDB looking for locations, we can easily use this information
to find a suitable program_space and pass this to as a filter when
creating the sals.

Or we could if breakpoint_ops::create_sals_from_location_spec allowed
us to pass in a filter program_space.

So, this commit extends breakpoint_ops::create_sals_from_location_spec
to take a program_space argument, and uses this to filter the set of
returned sals.  This accounts for about half the change in this patch.

The second set of changes starts from breakpoint_set_thread and
breakpoint_set_inferior, this is called when the thread or inferior
for a breakpoint changes, e.g. from the Python API.

Previously this call would never result in the locations of a
breakpoint changing, after all, locations were inserted in every
program space, and we just use the thread or inferior variable to
decide when we should stop.  Now though, changing a breakpoint's
thread or inferior can mean we need to figure out a new set of
breakpoint locations.

To support this I've added a new breakpoint_re_set_one function, which
is like breakpoint_re_set, but takes a single breakpoint, and just
updates the locations for that one breakpoint.  We only need to call
this function if the program_space in which a breakpoint's thread (or
inferior) is running actually changes.  If the program_space does
change then we call the new breakpoint_re_set_one function passing in
the program_space which should be used to filter the new locations (or
nullptr to indicate we should set locations in all program spaces).
This filter program_space needs to propagate down to all the re_set
methods, this accounts for the remaining half of the changes in this
patch.

There were a couple of existing tests that created thread or inferior
specific breakpoints and then checked the 'info breakpoints' output,
these needed updating.  These were:

  gdb.mi/user-selected-context-sync.exp
  gdb.multi/bp-thread-specific.exp
  gdb.multi/multi-target-continue.exp
  gdb.multi/multi-target-ping-pong-next.exp
  gdb.multi/tids.exp
  gdb.mi/new-ui-bp-deleted.exp
  gdb.multi/inferior-specific-bp.exp
  gdb.multi/pending-bp-del-inferior.exp

I've also added some additional tests to:

  gdb.multi/pending-bp.exp

I've updated the documentation and added a NEWS entry.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS                                      |   7 +
 gdb/ada-lang.c                                |   6 +-
 gdb/break-catch-throw.c                       |   6 +-
 gdb/breakpoint.c                              | 280 ++++++++++++++----
 gdb/breakpoint.h                              |  29 +-
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    |   8 +-
 .../gdb.mi/user-selected-context-sync.exp     |   4 +-
 .../gdb.multi/bp-thread-specific.exp          |   7 +-
 .../gdb.multi/inferior-specific-bp.exp        |  12 +-
 .../gdb.multi/multi-target-continue.exp       |   2 +-
 .../gdb.multi/multi-target-ping-pong-next.exp |   4 +-
 .../gdb.multi/pending-bp-del-inferior.exp     |   6 +-
 gdb/testsuite/gdb.multi/pending-bp.exp        | 206 +++++++++++++
 gdb/testsuite/gdb.multi/tids.exp              |   6 +-
 14 files changed, 484 insertions(+), 99 deletions(-)
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index a3cbd34f1d0..b2cf70b2c64 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -13,6 +13,13 @@ 
   'thread' or 'task' keywords are parsed at the time the breakpoint is
   created, rather than at the time the breakpoint becomes non-pending.
 
+* Thread-specific breakpoints are only inserted into the program space
+  in which the thread of interest is running.  In most cases program
+  spaces are unique for each inferior, so this means that
+  thread-specific breakpoints will usually only be inserted for the
+  inferior containing the thread of interest.  The breakpoint will
+  be hit no less than before.
+
 * Changed commands
 
 disassemble
diff --git a/gdb/ada-lang.c b/gdb/ada-lang.c
index ff7222c7eed..a2bf42a5703 100644
--- a/gdb/ada-lang.c
+++ b/gdb/ada-lang.c
@@ -12079,11 +12079,11 @@  struct ada_catchpoint : public code_breakpoint
     enable_state = enabled ? bp_enabled : bp_disabled;
     language = language_ada;
 
-    re_set ();
+    re_set (pspace);
   }
 
   struct bp_location *allocate_location () override;
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   void check_status (struct bpstat *bs) override;
   enum print_stop_action print_it (const bpstat *bs) const override;
   bool print_one (const bp_location **) const override;
@@ -12128,7 +12128,7 @@  static struct symtab_and_line ada_exception_sal
    catchpoint kinds.  */
 
 void
-ada_catchpoint::re_set ()
+ada_catchpoint::re_set (program_space *pspace)
 {
   std::vector<symtab_and_line> sals;
   try
diff --git a/gdb/break-catch-throw.c b/gdb/break-catch-throw.c
index 47d534c5ee8..e661d1624e8 100644
--- a/gdb/break-catch-throw.c
+++ b/gdb/break-catch-throw.c
@@ -82,10 +82,10 @@  struct exception_catchpoint : public code_breakpoint
 				     _("invalid type-matching regexp")))
   {
     pspace = current_program_space;
-    re_set ();
+    re_set (pspace);
   }
 
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   enum print_stop_action print_it (const bpstat *bs) const override;
   bool print_one (const bp_location **) const override;
   void print_mention () const override;
@@ -198,7 +198,7 @@  exception_catchpoint::check_status (struct bpstat *bs)
 /* Implement the 're_set' method.  */
 
 void
-exception_catchpoint::re_set ()
+exception_catchpoint::re_set (program_space *pspace)
 {
   std::vector<symtab_and_line> sals;
   struct program_space *filter_pspace = current_program_space;
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 7590cd7248c..0fd48b4cde9 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -91,9 +91,12 @@ 
 static void map_breakpoint_numbers (const char *,
 				    gdb::function_view<void (breakpoint *)>);
 
-static void
-  create_sals_from_location_spec_default (location_spec *locspec,
-					  linespec_result *canonical);
+static void parse_breakpoint_sals (location_spec *locspec,
+				   linespec_result *canonical,
+				   program_space *search_pspace);
+
+static void breakpoint_re_set_one (breakpoint *b,
+				   program_space *filter_pspace);
 
 static void create_breakpoints_sal (struct gdbarch *,
 				    struct linespec_result *,
@@ -283,11 +286,12 @@  static bool strace_marker_p (struct breakpoint *b);
 
 static void bkpt_probe_create_sals_from_location_spec
      (location_spec *locspec,
-      struct linespec_result *canonical);
+      struct linespec_result *canonical,
+      struct program_space *search_pspace);
 
 const struct breakpoint_ops code_breakpoint_ops =
 {
-  create_sals_from_location_spec_default,
+  parse_breakpoint_sals,
   create_breakpoints_sal,
 };
 
@@ -352,7 +356,7 @@  struct internal_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
   }
 
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   void check_status (struct bpstat *bs) override;
   enum print_stop_action print_it (const bpstat *bs) const override;
   void print_mention () const override;
@@ -389,7 +393,7 @@  struct momentary_breakpoint : public code_breakpoint
     gdb_assert (inferior == -1);
   }
 
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   void check_status (struct bpstat *bs) override;
   enum print_stop_action print_it (const bpstat *bs) const override;
   void print_mention () const override;
@@ -400,7 +404,7 @@  struct dprintf_breakpoint : public ordinary_breakpoint
 {
   using ordinary_breakpoint::ordinary_breakpoint;
 
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   int breakpoint_hit (const struct bp_location *bl,
 		      const address_space *aspace,
 		      CORE_ADDR bp_addr,
@@ -1554,7 +1558,36 @@  breakpoint_set_thread (struct breakpoint *b, int thread)
   int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
-    notify_breakpoint_modified (b);
+    {
+      /* If THREAD is in a different program_space than OLD_THREAD, or the
+	 breakpoint has switched to or from being thread-specific, then we
+	 need to re-set the locations of this breakpoint.  First, figure
+	 out the program_space for the old and new threads, use a value of
+	 nullptr to indicate the breakpoint is in all program spaces.  */
+      program_space *old_pspace = nullptr;
+      if (old_thread != -1)
+	{
+	  struct thread_info *thr = find_thread_global_id (old_thread);
+	  gdb_assert (thr != nullptr);
+	  old_pspace = thr->inf->pspace;
+	}
+
+      program_space *new_pspace = nullptr;
+      if (thread != -1)
+	{
+	  struct thread_info *thr = find_thread_global_id (thread);
+	  gdb_assert (thr != nullptr);
+	  new_pspace = thr->inf->pspace;
+	}
+
+      /* If the program space has changed for this breakpoint, then
+	 re-evaluate it's locations.  */
+      if (old_pspace != new_pspace)
+	breakpoint_re_set_one (b, new_pspace);
+
+      /* Let others know the breakpoint has changed.  */
+      notify_breakpoint_modified (b);
+    }
 }
 
 /* See breakpoint.h.  */
@@ -1573,7 +1606,34 @@  breakpoint_set_inferior (struct breakpoint *b, int inferior)
   int old_inferior = b->inferior;
   b->inferior = inferior;
   if (old_inferior != inferior)
-    notify_breakpoint_modified (b);
+    {
+      /* If INFERIOR is in a different program_space than OLD_INFERIOR, or
+	 the breakpoint has switch to or from inferior-specific, then we
+	 need to re-set the locations of this breakpoint.  First, figure
+	 out the program_space for the old and new inferiors, use a value
+	 of nullptr to indicate the breakpoint is in all program
+	 spaces.  */
+      program_space *old_pspace = nullptr;
+      if (old_inferior != -1)
+	{
+	  struct inferior *inf = find_inferior_id (old_inferior);
+	  gdb_assert (inf != nullptr);
+	  old_pspace = inf->pspace;
+	}
+
+      program_space *new_pspace = nullptr;
+      if (inferior != -1)
+	{
+	  struct inferior *inf = find_inferior_id (inferior);
+	  gdb_assert (inf != nullptr);
+	  new_pspace = inf->pspace;
+	}
+
+      if (old_pspace != new_pspace)
+	breakpoint_re_set_one (b, new_pspace);
+
+      notify_breakpoint_modified (b);
+    }
 }
 
 /* See breakpoint.h.  */
@@ -8814,7 +8874,8 @@  create_breakpoints_sal (struct gdbarch *gdbarch,
 
 static void
 parse_breakpoint_sals (location_spec *locspec,
-		       struct linespec_result *canonical)
+		       struct linespec_result *canonical,
+		       struct program_space *search_pspace)
 {
   struct symtab_and_line cursal;
 
@@ -8878,7 +8939,7 @@  parse_breakpoint_sals (location_spec *locspec,
 	      && strchr ("+-", spec[0]) != NULL
 	      && spec[1] != '['))
 	{
-	  decode_line_full (locspec, DECODE_LINE_FUNFIRSTLINE, NULL,
+	  decode_line_full (locspec, DECODE_LINE_FUNFIRSTLINE, search_pspace,
 			    get_last_displayed_symtab (),
 			    get_last_displayed_line (),
 			    canonical, NULL, NULL);
@@ -8886,7 +8947,7 @@  parse_breakpoint_sals (location_spec *locspec,
 	}
     }
 
-  decode_line_full (locspec, DECODE_LINE_FUNFIRSTLINE, NULL,
+  decode_line_full (locspec, DECODE_LINE_FUNFIRSTLINE, search_pspace,
 		    cursal.symtab, cursal.line, canonical, NULL, NULL);
 }
 
@@ -8985,6 +9046,39 @@  breakpoint_ops_for_location_spec_type (enum location_spec_type locspec_type,
     }
 }
 
+/* Return the program space to use as a filter when searching for locations
+   of a breakpoint specific to THREAD or INFERIOR.  If THREAD and INFERIOR
+   are both -1, meaning all threads/inferiors, then this function returns
+   nullptr, indicating no program space filtering should be performed.
+   Otherwise, this function returns the program space for the inferior that
+   contains THREAD (when THREAD is not -1), or the program space for
+   INFERIOR (when INFERIOR is not -1).  */
+
+static struct program_space *
+find_program_space_for_breakpoint (int thread, int inferior)
+{
+  if (thread != -1)
+    {
+      gdb_assert (inferior == -1);
+
+      struct thread_info *thr = find_thread_global_id (thread);
+      gdb_assert (thr != nullptr);
+      gdb_assert (thr->inf != nullptr);
+      return thr->inf->pspace;
+    }
+  else if (inferior != -1)
+    {
+      gdb_assert (thread == -1);
+
+      struct inferior *inf = find_inferior_id (inferior);
+      gdb_assert (inf != nullptr);
+
+      return inf->pspace;
+    }
+
+  return nullptr;
+}
+
 /* See breakpoint.h.  */
 
 const struct breakpoint_ops *
@@ -9087,7 +9181,10 @@  create_breakpoint (struct gdbarch *gdbarch,
 
   try
     {
-      ops->create_sals_from_location_spec (locspec, &canonical);
+      struct program_space *search_pspace
+	= find_program_space_for_breakpoint (thread, inferior);
+      ops->create_sals_from_location_spec (locspec, &canonical,
+					   search_pspace);
     }
   catch (const gdb_exception_error &e)
     {
@@ -9560,7 +9657,7 @@  break_range_command (const char *arg, int from_tty)
   arg_start = arg;
   location_spec_up start_locspec
     = string_to_location_spec (&arg, current_language);
-  parse_breakpoint_sals (start_locspec.get (), &canonical_start);
+  parse_breakpoint_sals (start_locspec.get (), &canonical_start, nullptr);
 
   if (arg[0] != ',')
     error (_("Too few arguments."));
@@ -9661,7 +9758,7 @@  watchpoint_exp_is_const (const struct expression *exp)
 /* Implement the "re_set" method for watchpoints.  */
 
 void
-watchpoint::re_set ()
+watchpoint::re_set (struct program_space *pspace)
 {
   /* Watchpoint can be either on expression using entirely global
      variables, or it can be on local variables.
@@ -11772,7 +11869,7 @@  breakpoint::print_recreate (struct ui_file *fp) const
 /* Default breakpoint_ops methods.  */
 
 void
-code_breakpoint::re_set ()
+code_breakpoint::re_set (struct program_space *pspace)
 {
   /* FIXME: is this still reachable?  */
   if (breakpoint_location_spec_empty_p (this))
@@ -11782,7 +11879,7 @@  code_breakpoint::re_set ()
       return;
     }
 
-  re_set_default ();
+  re_set_default (pspace);
 }
 
 int
@@ -11988,7 +12085,7 @@  code_breakpoint::decode_location_spec (location_spec *locspec,
 /* Virtual table for internal breakpoints.  */
 
 void
-internal_breakpoint::re_set ()
+internal_breakpoint::re_set (struct program_space *pspace)
 {
   switch (type)
     {
@@ -12081,7 +12178,7 @@  internal_breakpoint::print_mention () const
 /* Virtual table for momentary breakpoints  */
 
 void
-momentary_breakpoint::re_set ()
+momentary_breakpoint::re_set (struct program_space *pspace)
 {
   /* Keep temporary breakpoints, which can be encountered when we step
      over a dlopen call and solib_add is resetting the breakpoints.
@@ -12122,12 +12219,13 @@  longjmp_breakpoint::~longjmp_breakpoint ()
 
 static void
 bkpt_probe_create_sals_from_location_spec (location_spec *locspec,
-					   struct linespec_result *canonical)
+					   struct linespec_result *canonical,
+					   struct program_space *search_pspace)
 
 {
   struct linespec_sals lsal;
 
-  lsal.sals = parse_probes (locspec, NULL, canonical);
+  lsal.sals = parse_probes (locspec, search_pspace, canonical);
   lsal.canonical = xstrdup (canonical->locspec->to_string ());
   canonical->lsals.push_back (std::move (lsal));
 }
@@ -12217,9 +12315,9 @@  tracepoint::print_recreate (struct ui_file *fp) const
 }
 
 void
-dprintf_breakpoint::re_set ()
+dprintf_breakpoint::re_set (struct program_space *pspace)
 {
-  re_set_default ();
+  re_set_default (pspace);
 
   /* extra_string should never be non-NULL for dprintf.  */
   gdb_assert (extra_string != NULL);
@@ -12277,8 +12375,10 @@  dprintf_breakpoint::after_condition_true (struct bpstat *bs)
    markers (`-m').  */
 
 static void
-strace_marker_create_sals_from_location_spec (location_spec *locspec,
-					      struct linespec_result *canonical)
+strace_marker_create_sals_from_location_spec
+	(location_spec *locspec,
+	 struct linespec_result *canonical,
+	 struct program_space *search_pspace)
 {
   struct linespec_sals lsal;
   const char *arg_start, *arg;
@@ -12794,12 +12894,32 @@  update_breakpoint_locations (code_breakpoint *b,
      all locations are in the same shared library, that was unloaded.
      We'd like to retain the location, so that when the library is
      loaded again, we don't loose the enabled/disabled status of the
-     individual locations.  */
+     individual locations.
+
+     Thread specific breakpoints will also trigger this case if the thread
+     is changed to a different program space, and all of the old locations
+     go out of scope.  In this case we do (currently) discard the old
+     locations -- we assume the change in thread is permanent and the old
+     locations will never come back into scope.  */
   if (all_locations_are_pending (b, filter_pspace) && sals.empty ())
-    return;
+    {
+      if (b->thread != -1)
+	b->clear_locations ();
+      return;
+    }
 
   bp_location_list existing_locations = b->steal_locations (filter_pspace);
 
+  /* If this is a thread-specific breakpoint then any locations left on the
+     breakpoint are for a program space in which the thread of interest
+     does not operate.  This can happen when the user changes the thread of
+     a thread-specific breakpoint.
+
+     We assume that the change in thread is permanent, and that the old
+     locations will never be used again, so discard them now.  */
+  if (b->thread != -1)
+    b->clear_locations ();
+
   for (const auto &sal : sals)
     {
       struct bp_location *new_loc;
@@ -12965,40 +13085,45 @@  code_breakpoint::location_spec_to_sals (location_spec *locspec,
    locations.  */
 
 void
-code_breakpoint::re_set_default ()
+code_breakpoint::re_set_default (struct program_space *filter_pspace)
 {
-  struct program_space *filter_pspace = current_program_space;
   std::vector<symtab_and_line> expanded, expanded_end;
 
-  int found;
-  std::vector<symtab_and_line> sals = location_spec_to_sals (locspec.get (),
-							     filter_pspace,
-							     &found);
-  if (found)
-    expanded = std::move (sals);
-
-  if (locspec_range_end != nullptr)
-    {
-      std::vector<symtab_and_line> sals_end
-	= location_spec_to_sals (locspec_range_end.get (),
-				 filter_pspace, &found);
+  /* If this breakpoint is thread-specific then find the program space in
+     which the specific thread exists.  Otherwise, for breakpoints that are
+     not thread-specific THREAD_PSPACE will be nullptr.  */
+  program_space *bp_pspace
+    = find_program_space_for_breakpoint (this->thread, this->inferior);
+
+  /* If this is not a thread or inferior specific breakpoint, or it is a
+     thread or inferior specific breakpoint but we are looking for new
+     locations in the program space that the specific thread or inferior is
+     running, then look for new locations for this breakpoint.  */
+  if (bp_pspace == nullptr || filter_pspace == bp_pspace)
+    {
+      int found;
+      std::vector<symtab_and_line> sals
+	= location_spec_to_sals (locspec.get (), filter_pspace, &found);
       if (found)
-	expanded_end = std::move (sals_end);
+	expanded = std::move (sals);
+
+      if (locspec_range_end != nullptr)
+	{
+	  std::vector<symtab_and_line> sals_end
+	    = location_spec_to_sals (locspec_range_end.get (),
+				     filter_pspace, &found);
+	  if (found)
+	    expanded_end = std::move (sals_end);
+	}
     }
 
+  /* Update the locations for this breakpoint.  For thread-specific
+     breakpoints this will remove any old locations that are for the wrong
+     program space -- this can happen if the user changes the thread of a
+     thread-specific breakpoint.  */
   update_breakpoint_locations (this, filter_pspace, expanded, expanded_end);
 }
 
-/* Default method for creating SALs from an address string.  It basically
-   calls parse_breakpoint_sals.  Return 1 for success, zero for failure.  */
-
-static void
-create_sals_from_location_spec_default (location_spec *locspec,
-					struct linespec_result *canonical)
-{
-  parse_breakpoint_sals (locspec, canonical);
-}
-
 /* Re-set breakpoint locations for the current program space.
    Locations bound to other program spaces are left untouched.  */
 
@@ -13033,7 +13158,7 @@  breakpoint_re_set (void)
 	  {
 	    input_radix = b.input_radix;
 	    set_language (b.language);
-	    b.re_set ();
+	    b.re_set (current_program_space);
 	  }
 	catch (const gdb_exception &ex)
 	  {
@@ -13054,6 +13179,53 @@  breakpoint_re_set (void)
   /* Now we can insert.  */
   update_global_location_list (UGLL_MAY_INSERT);
 }
+
+/* Re-set locations for breakpoint B in FILTER_PSPACE.  If FILTER_PSPACE is
+   nullptr then re-set locations for B in all program spaces.  Locations
+   bound to program spaces other than FILTER_PSPACE are left untouched.  */
+
+static void
+breakpoint_re_set_one (breakpoint *b, program_space *filter_pspace)
+{
+  {
+    scoped_restore_current_language save_language;
+    scoped_restore save_input_radix = make_scoped_restore (&input_radix);
+    scoped_restore_current_pspace_and_thread restore_pspace_thread;
+
+    /* To ::re_set each breakpoint we set the current_language to the
+       language of the breakpoint before re-evaluating the breakpoint's
+       location.  This change can unfortunately get undone by accident if
+       the language_mode is set to auto, and we either switch frames, or
+       more likely in this context, we select the current frame.
+
+       We prevent this by temporarily turning the language_mode to
+       language_mode_manual.  We restore it once all breakpoints
+       have been reset.  */
+    scoped_restore save_language_mode = make_scoped_restore (&language_mode);
+    language_mode = language_mode_manual;
+
+    /* Note: we must not try to insert locations until after all
+       breakpoints have been re-set.  Otherwise, e.g., when re-setting
+       breakpoint 1, we'd insert the locations of breakpoint 2, which
+       hadn't been re-set yet, and thus may have stale locations.  */
+
+    try
+      {
+	input_radix = b->input_radix;
+	set_language (b->language);
+	b->re_set (filter_pspace);
+      }
+    catch (const gdb_exception &ex)
+      {
+	exception_fprintf (gdb_stderr, ex,
+			   "Error in re-setting breakpoint %d: ",
+			   b->number);
+      }
+  }
+
+  /* Now we can insert.  */
+  update_global_location_list (UGLL_MAY_INSERT);
+}
 
 /* Reset the thread number of this breakpoint:
 
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 2bf63b90072..a48a9fe81fd 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -562,15 +562,15 @@  enum print_stop_action
 
 struct breakpoint_ops
 {
-  /* Create SALs from location spec, storing the result in
-     linespec_result.
-
-     For an explanation about the arguments, see the function
-     `create_sals_from_location_spec_default'.
+  /* Create SALs from LOCSPEC, storing the result in linespec_result
+     CANONICAL.  If SEARCH_PSPACE is not nullptr then only results in the
+     corresponding program space are returned.  If SEARCH_PSPACE is nullptr
+     then results for all program spaces are returned.
 
      This function is called inside `create_breakpoint'.  */
   void (*create_sals_from_location_spec) (location_spec *locspec,
-					  struct linespec_result *canonical);
+					  linespec_result *canonical,
+					  program_space *search_pspace);
 
   /* This method will be responsible for creating a breakpoint given its SALs.
      Usually, it just calls `create_breakpoints_sal' (for ordinary
@@ -702,8 +702,15 @@  struct breakpoint : public intrusive_list_node<breakpoint>
 
   /* Reevaluate a breakpoint.  This is necessary after symbols change
      (e.g., an executable or DSO was loaded, or the inferior just
-     started).  */
-  virtual void re_set ()
+     started).
+
+     If not nullptr, then FILTER_PSPACE is the program space in which
+     symbols may have changed, we only need to add new locations in
+     FILTER_PSPACE.
+
+     If FILTER_PSPACE is nullptr then all program spaces may have changed,
+     new locations need to be searched for in every program space.  */
+  virtual void re_set (program_space *filter_pspace)
   {
     /* Nothing to re-set.  */
   }
@@ -947,7 +954,7 @@  struct code_breakpoint : public breakpoint
   /* Add a location for SAL to this breakpoint.  */
   bp_location *add_location (const symtab_and_line &sal);
 
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   int insert_location (struct bp_location *) override;
   int remove_location (struct bp_location *,
 		       enum remove_bp_reason reason) override;
@@ -969,7 +976,7 @@  struct code_breakpoint : public breakpoint
      struct program_space *search_pspace);
 
   /* Helper method that does the basic work of re_set.  */
-  void re_set_default ();
+  void re_set_default (program_space *pspace);
 
   /* Find the SaL locations corresponding to the given LOCATION.
      On return, FOUND will be 1 if any SaL was found, zero otherwise.  */
@@ -991,7 +998,7 @@  struct watchpoint : public breakpoint
 {
   using breakpoint::breakpoint;
 
-  void re_set () override;
+  void re_set (program_space *pspace) override;
   int insert_location (struct bp_location *) override;
   int remove_location (struct bp_location *,
 		       enum remove_bp_reason reason) override;
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
index 57e69ef6240..3afc1787026 100644
--- a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -76,8 +76,12 @@  foreach_mi_ui_mode mode {
     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\\\]"
+    mi_create_breakpoint "-g i2 foo" "create breakpoint in inferior 2" \
+	-number "$decimal" \
+	-type "breakpoint" \
+	-enabled "y" \
+	-func "foo" \
+	-inferior "2"
     set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
 
     if {$mode eq "separate"} {
diff --git a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 4889c31aff3..b39745bdf17 100644
--- a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
+++ b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
@@ -370,7 +370,7 @@  proc test_continue_to_start { mode inf } {
 			    "thread $inf.2 stops MI"
 		    } else {
 			mi_expect_stop "breakpoint-hit" "child_sub_function" \
-			    "" "$srcfile" "$decimal" {"" "disp=\"del\"" "locno=\"[0-9]+\""} \
+			    "" "$srcfile" "$decimal" {"" "disp=\"del\""} \
 			    "thread $inf.2 stops MI"
 		    }
 		}
@@ -439,7 +439,7 @@  proc_with_prefix test_setup { mode } {
 
 	with_spawn_id $mi_spawn_id {
 	    mi_expect_stop "breakpoint-hit" "main" "" "$srcfile" "$decimal" \
-		{"" "disp=\"del\"" "locno=\"[0-9]+\""} "main stop"
+		{"" "disp=\"del\""} "main stop"
 	}
 
 	# Consume CLI output.
diff --git a/gdb/testsuite/gdb.multi/bp-thread-specific.exp b/gdb/testsuite/gdb.multi/bp-thread-specific.exp
index 85c08f44a2c..68001e92044 100644
--- a/gdb/testsuite/gdb.multi/bp-thread-specific.exp
+++ b/gdb/testsuite/gdb.multi/bp-thread-specific.exp
@@ -50,7 +50,7 @@  gdb_test "info threads" \
 # locations ('foo' in both inferiors) even though only one of those
 # locations will ever trigger ('foo' in inferior 2).
 gdb_test "break foo thread 2.1" \
-    "Breakpoint $decimal at $hex: foo\\. \\(2 locations\\)"
+    "Breakpoint $decimal at $hex: file \[^\r\n\]+$srcfile, line $decimal\\."
 
 set bpnum [get_integer_valueof "\$bpnum" "INVALID"]
 
@@ -58,10 +58,7 @@  set bpnum [get_integer_valueof "\$bpnum" "INVALID"]
 # earlier breakpoint.  Check that the thread-id used when describing
 # the earlier breakpoints is correct.
 gdb_test "break foo thread 1.1" \
-    [multi_line \
-	 "Note: breakpoint $bpnum \\(thread 2.1\\) also set at pc $hex\\." \
-	 "Note: breakpoint $bpnum \\(thread 2.1\\) also set at pc $hex\\." \
-	 "Breakpoint $decimal at $hex: foo\\. \\(2 locations\\)"]
+    "Breakpoint $decimal at $hex: file \[^\r\n\]+$srcfile, line $decimal\\."
 
 # Save the breakpoints into a file.
 if {[is_remote host]} {
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
index b8aceabcad6..dda167dd39f 100644
--- a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -105,16 +105,8 @@  proc check_info_breakpoints { testname bp_number expected_loc_count } {
 # 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\\)"
+    "Breakpoint $decimal at $hex: foo\\. \\(2 locations\\)"
 set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
 		  "get b/p number for inferior specific breakpoint"]
 
@@ -123,7 +115,7 @@  set location_count 0
 set saw_inf_cond false
 
 check_info_breakpoints "first check for inferior specific breakpoint" \
-    $bp_number 3
+    $bp_number 2
 
 # Create a multi-inferior breakpoint to stop at.
 gdb_breakpoint "stop_breakpt" message
diff --git a/gdb/testsuite/gdb.multi/multi-target-continue.exp b/gdb/testsuite/gdb.multi/multi-target-continue.exp
index bf19cc00968..a1bffb7b613 100644
--- a/gdb/testsuite/gdb.multi/multi-target-continue.exp
+++ b/gdb/testsuite/gdb.multi/multi-target-continue.exp
@@ -30,7 +30,7 @@  proc test_continue {non-stop} {
 
     proc set_break {inf} {
 	gdb_test "break function${inf} thread ${inf}.1" \
-	"Breakpoint .* function${inf}\\..*"
+	    "Breakpoint ${::decimal} at ${::hex}: file .*, line ${::decimal}\\."
     }
 
     # Select inferior INF, and then run to a breakpoint on inferior
diff --git a/gdb/testsuite/gdb.multi/multi-target-ping-pong-next.exp b/gdb/testsuite/gdb.multi/multi-target-ping-pong-next.exp
index 64645b8ec8a..51fd70b018a 100644
--- a/gdb/testsuite/gdb.multi/multi-target-ping-pong-next.exp
+++ b/gdb/testsuite/gdb.multi/multi-target-ping-pong-next.exp
@@ -52,12 +52,12 @@  proc test_ping_pong_next {} {
     gdb_test "thread 1.1" "Switching to thread 1.1 .*"
 
     gdb_test "break $srcfile:$line1 thread 1.1" \
-	"Breakpoint .*$srcfile:$line1\\..*"
+	"Breakpoint .*$srcfile, line $line1\\."
 
     gdb_test "continue" "hit Breakpoint .*"
 
     gdb_test "break $srcfile:$line2 thread 2.1" \
-	"Breakpoint .*$srcfile:$line2\\..*"
+	"Breakpoint .*$srcfile, line $line2\\."
 
     # Now block inferior 1 and issue "next".  We should stop at the
     # breakpoint for inferior 2, given schedlock off.
diff --git a/gdb/testsuite/gdb.multi/pending-bp-del-inferior.exp b/gdb/testsuite/gdb.multi/pending-bp-del-inferior.exp
index cb1f119ff94..897d4a1cf52 100644
--- a/gdb/testsuite/gdb.multi/pending-bp-del-inferior.exp
+++ b/gdb/testsuite/gdb.multi/pending-bp-del-inferior.exp
@@ -129,10 +129,8 @@  proc do_bp_test { bp_type bp_pending } {
     } else {
 	set bp_pattern_before \
 	    [multi_line \
-		 "$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*" \
-		 "\\s+stop only in [string_to_regexp $bp_restriction]" \
-		 "$bp_number\\.1\\s+y\\s+$::hex in $bp_func at \[^\r\n\]+ inf 1" \
-		 "$bp_number\\.2\\s+y\\s+$::hex in $bp_func at \[^\r\n\]+ inf 2"]
+		 "$bp_number\\s+breakpoint\\s+keep\\s+y\\s+$::hex in $bp_func at \[^\r\n\]+ inf 1" \
+		 "\\s+stop only in [string_to_regexp $bp_restriction]"]
 
 	set bp_pattern_after \
 	    [multi_line \
diff --git a/gdb/testsuite/gdb.multi/pending-bp.exp b/gdb/testsuite/gdb.multi/pending-bp.exp
index e4f0aa2e38a..16803488a7f 100644
--- a/gdb/testsuite/gdb.multi/pending-bp.exp
+++ b/gdb/testsuite/gdb.multi/pending-bp.exp
@@ -78,6 +78,48 @@  proc do_test_setup { inf_1_stop inf_2_stop } {
     return true
 }
 
+# Create a breakpoint on the function 'foo' in THREAD.  It is expected
+# that the breakpoint created will be pending, this is checked by
+# running the 'info breakpoints' command.
+#
+# Returns the number for the newly created breakpoint.
+proc do_create_pending_foo_breakpoint { {thread "1.1"} } {
+    gdb_test "break foo thread $thread" \
+	[multi_line \
+	     "Function \"foo\" not defined\\." \
+	     "Breakpoint $::decimal \\(foo\\) pending\."] \
+	"set pending thread-specific breakpoint"
+    set bpnum [get_integer_valueof "\$bpnum" "*INVALID*" \
+		   "get number for thread-specific breakpoint on foo"]
+    gdb_test "info breakpoints $bpnum" \
+	[multi_line \
+	     "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+foo" \
+	     "\\s+stop only in thread [string_to_regexp $thread]"] \
+	"check thread-specific breakpoint is initially pending"
+
+    return $bpnum
+}
+
+# Create a breakpoint on the function 'foo' in THREAD.  It is expected
+# that the breakpoint created will not be pending, this is checked by
+# running the 'info breakpoints' command.
+#
+# Returns the number for the newly created breakpoint.
+proc do_create_foo_breakpoint { {thread "1.1"} } {
+    gdb_test "break foo thread $thread" \
+	"Breakpoint $::decimal at $::hex" \
+	"set thread-specific breakpoint"
+    set bpnum [get_integer_valueof "\$bpnum" "*INVALID*" \
+		   "get number for thread-specific breakpoint on foo"]
+    gdb_test "info breakpoints $bpnum" \
+	[multi_line \
+	     "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+$::hex\\s+<foo\[^>\]*> inf $::decimal" \
+	     "\\s+stop only in thread [string_to_regexp $thread]"] \
+	"check thread-specific breakpoint is initially pending"
+
+    return $bpnum
+}
+
 # Check that when a breakpoint is in the pending state, but that breakpoint
 # does have some locations (those locations themselves are pending), GDB
 # doesn't display the inferior list in the 'info breakpoints' output.
@@ -126,5 +168,169 @@  proc_with_prefix test_no_inf_display {} {
 	"check info breakpoints while breakpoint is pending"
 }
 
+# Setup two inferiors.  In #1 the symbol 'foo' has not yet been
+# loaded, while in #2 the symbol 'foo' has been loaded.
+#
+# Create a thread-specific breakpoint on 'foo' tied to a thread in
+# inferior #1, the breakpoint should be pending -- 'foo' is not yet
+# loaded in #1.
+#
+# Now move inferior #1 forward until 'foo' is loaded, check the
+# breakpoint is no longer pending.
+#
+# Move inferior #1 forward more until 'foo' is unloaded, check that
+# the breakpoint returns to the pending state.
+proc_with_prefix test_pending_toggle { } {
+
+    do_test_setup "Break before open" "Break before close"
+
+    set bpnum [do_create_pending_foo_breakpoint]
+
+    # Now return to inferior 1 and continue until the shared library is
+    # loaded, the breakpoint should become non-pending.
+    gdb_test "inferior 1" "Switching to inferior 1 .*" \
+	"switch back to inferior 1"
+    gdb_continue_to_breakpoint "stop in foo in inferior 1" "foo \\(\\) .*"
+
+    gdb_test "info breakpoint $bpnum" \
+	[multi_line \
+	     "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+$::hex <foo\[^>\]*> inf 1" \
+	     "\\s+stop only in thread 1\\.1" \
+	     "\\s+breakpoint already hit 1 time"] \
+	"check thread-specific breakpoint is no longer pending"
+
+    gdb_breakpoint [gdb_get_line_number "Break after close"] temporary
+    gdb_continue_to_breakpoint "close library"
+    gdb_test "info breakpoints $bpnum" \
+	[multi_line \
+	     "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+foo" \
+	     "\\s+stop only in thread 1\\.1" \
+	     "\\s+breakpoint already hit 1 time"] \
+	"check thread-specific breakpoint is pending again"
+}
+
+# Create a Python variable VAR and set it to the gdb.Breakpoint object
+# corresponding to the breakpoint numbered BPNUM.  If THREAD is not
+# the empty string then THREAD should be an integer, check that
+# gdb.Breakpoint.thread is set to the value of THREAD.  Otherwise, if
+# THREAD is the empty string, check that gdb.Breakpoint.thread is set
+# to None.
+proc py_find_breakpoint { var bpnum {thread ""} } {
+    gdb_test_no_output \
+	"python ${var}=\[b for b in gdb.breakpoints() if b.number == $bpnum\]\[0\]" \
+	"find Python gdb.Breakpoint object"
+    if { $thread ne "" } {
+	gdb_test_no_output "python assert(${var}.thread == ${thread})" \
+	    "check thread attribute is currently correct"
+    } else {
+	gdb_test_no_output "python assert(${var}.thread is None)" \
+	    "check thread attribute is currently correct"
+    }
+}
+
+# Setup two inferiors.  In #1 the symbol 'foo' has not yet been
+# loaded, while in #2 the symbol 'foo' has been loaded.
+#
+# Create a thread-specific breakpoint on 'foo' tied to a thread in
+# inferior #1, the breakpoint should be pending -- 'foo' is not yet
+# loaded in #1.
+#
+# Use Python to change the thread of the thread-specific breakpoint to
+# a thread in inferior #2, at this point the thread should gain a
+# location and become non-pending.
+#
+# Set the thread back to a thread in inferior #1, the breakpoint
+# should return to the pending state.
+proc_with_prefix py_test_toggle_thread {} {
+    do_test_setup "Break before open" "Break after open"
+
+    set bpnum [do_create_pending_foo_breakpoint]
+
+    py_find_breakpoint "bp" $bpnum 1
+
+    gdb_test_no_output "python bp.thread = 2" \
+	"change thread on thread-specific breakpoint"
+    gdb_test "info breakpoint $bpnum" \
+	[multi_line \
+	     "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+$::hex <foo\[^>\]*> inf 2" \
+	     "\\s+stop only in thread 2\\.1"] \
+	"check thread-specific breakpoint now has a location"
+
+    gdb_test_no_output "set call_count = 2" "set call_count in inferior 2"
+    gdb_continue_to_breakpoint "stop at foo in inferior 2" "foo \\(\\) .*"
+
+    gdb_test_no_output "python bp.thread = 1" \
+	"restore thread on thread-specific breakpoint"
+    gdb_test "info breakpoints $bpnum" \
+	[multi_line \
+	     "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+foo" \
+	     "\\s+stop only in thread 1\\.1" \
+	     "\\s+breakpoint already hit 1 time"] \
+	"check thread-specific breakpoint has returned to pending"
+
+    gdb_breakpoint [gdb_get_line_number "Break after close"] temporary
+    gdb_continue_to_breakpoint "stop after close in inferior 2" \
+	".* Break after close\\. .*"
+
+    gdb_test "inferior 1" "Switching to inferior 1 .*" \
+	"switch to inferior 1"
+    gdb_continue_to_breakpoint "stop at foo in inferior 1" "foo \\(\\) .*"
+}
+
+# Setup two inferiors.  Both inferiors have the symbol 'foo'
+# available.
+#
+# Create a thread-specific breakpoint on 'foo' tied to a thread in
+# inferior #1, the breakpoint should not be pending, but will only
+# have a single location, the location in inferior #1.
+#
+# Use Python to change the thread of the thread-specific breakpoint to
+# None.  At this point the breakpoint should gain a second location, a
+# location in inferior #2.
+proc_with_prefix py_test_clear_thread {} {
+    do_test_setup "Break after open" "Break after open"
+
+    set bpnum [do_create_foo_breakpoint]
+
+    py_find_breakpoint "bp" $bpnum 1
+
+    gdb_test_no_output "python bp.thread = None" \
+	"clear thread on thread-specific breakpoint"
+    gdb_test "info breakpoints $bpnum" \
+	[multi_line \
+	     "${bpnum}\\s+breakpoint\\s+keep y\\s+<MULTIPLE>\\s*" \
+	     "${bpnum}\\.1\\s+y\\s+${::hex}\\s+<foo\[^>\]*> inf $::decimal" \
+	     "${bpnum}\\.2\\s+y\\s+${::hex}\\s+<foo\[^>\]*> inf $::decimal"] \
+	"check for a location in both inferiors"
+
+    gdb_continue_to_breakpoint "stop at foo in inferior 2" "foo \\(\\) .*"
+    gdb_test_no_output "set call_count = 2" "set call_count in inferior 2"
+
+    gdb_test "inferior 1" "Switching to inferior 1 .*" \
+	"switch to inferior 1"
+    gdb_continue_to_breakpoint "stop at foo in inferior 1" "foo \\(\\) .*"
+    gdb_test_no_output "set call_count = 2" "set call_count in inferior 1"
+
+    gdb_test_no_output "python bp.thread = 2"
+    gdb_test "info breakpoints $bpnum" \
+	[multi_line \
+	     "${bpnum}\\s+breakpoint\\s+keep y\\s+${::hex}\\s+<foo\[^>\]*> inf 2" \
+	     "\\s+stop only in thread 2\\.1" \
+	     "\\s+breakpoint already hit 2 times"] \
+	"check for a location only in inferior 2"
+
+    gdb_breakpoint [gdb_get_line_number "Break after close"] temporary
+    gdb_continue_to_breakpoint "stop after close in inferior 1" \
+	".* Break after close\\. .*"
+
+    gdb_test "inferior 2" "Switching to inferior 2 .*" \
+	"switch back to inferior 2"
+    gdb_continue_to_breakpoint "stop at foo again in inferior 2" \
+	"foo \\(\\) .*"
+}
+
 # Run all the tests.
 test_no_inf_display
+test_pending_toggle
+py_test_toggle_thread
+py_test_clear_thread
diff --git a/gdb/testsuite/gdb.multi/tids.exp b/gdb/testsuite/gdb.multi/tids.exp
index 18fc4970fe7..34c270309de 100644
--- a/gdb/testsuite/gdb.multi/tids.exp
+++ b/gdb/testsuite/gdb.multi/tids.exp
@@ -433,11 +433,13 @@  if { [allow_python_tests] } {
 
 	gdb_py_test_silent_cmd "python bp = gdb.breakpoints()\[0\]" \
 	    "get python breakpoint" 0
-	gdb_test "python bp.thread = 6" "thread = 6" \
+	gdb_test_no_output "python bp.thread = 6" \
 	    "make breakpoint thread-specific with python"
 	# Check that the inferior-qualified ID is correct.
 	gdb_test "info breakpoint" \
-	    "stop only in thread 1.3\r\n.*" \
+	    [multi_line \
+		 "$decimal\\s+\[^\r\n\]+ in thread_function1 at \[^\r\n\]+" \
+		 "\\s+stop only in thread 1\\.3"] \
 	    "thread specific breakpoint right thread"
     }
 }