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

Message ID 9b23163b1cf8013c5c132ea2f92d013f72cf273e.1685479504.git.aburgess@redhat.com
State New
Headers
Series thread-specific breakpoints in just some inferiors |

Commit Message

Andrew Burgess May 30, 2023, 8:46 p.m. UTC
  This commit updates GDB so that thread-specific breakpoints are only
inserted into the inferior that contains the thread we are interested
in.

Actually, as breakpoints are placed in program spaces, we insert the
breakpoint in any inferior that shares a program space with the
inferior containing the thread we are interested in.  But as far as
most users are concerned this really means the one inferior.

In terms of implementation, getting this basically working is easy
enough, now that a breakpoint's thread field is setup prior to GDB
looking for locations, we can easily use the thread 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, this is
called when the thread for a thread-specific 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 field to decide when we
should stop.  Now though, changing a breakpoint's thread 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 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 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-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

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                              | 236 ++++++++++++++----
 gdb/breakpoint.h                              |  26 +-
 .../gdb.mi/user-selected-context-sync.exp     |   2 +-
 .../gdb.multi/bp-thread-specific.exp          |   7 +-
 .../gdb.multi/multi-target-continue.exp       |   2 +-
 .../gdb.multi/multi-target-ping-pong-next.exp |   4 +-
 gdb/testsuite/gdb.multi/pending-bp.exp        | 206 +++++++++++++++
 gdb/testsuite/gdb.multi/tids.exp              |   6 +-
 11 files changed, 427 insertions(+), 81 deletions(-)
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index d50b01ffa4f..eb7a984af16 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -71,6 +71,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.
+
 * New commands
 
 maintenance print record-instruction [ N ]
diff --git a/gdb/ada-lang.c b/gdb/ada-lang.c
index 237ec09050c..8a56b78ee61 100644
--- a/gdb/ada-lang.c
+++ b/gdb/ada-lang.c
@@ -12176,7 +12176,7 @@  struct ada_catchpoint : public code_breakpoint
   }
 
   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;
@@ -12268,11 +12268,11 @@  ada_catchpoint::allocate_location ()
    catchpoint kinds.  */
 
 void
-ada_catchpoint::re_set ()
+ada_catchpoint::re_set (program_space *pspace)
 {
   /* Call the base class's method.  This updates the catchpoint's
      locations.  */
-  this->code_breakpoint::re_set ();
+  this->code_breakpoint::re_set (pspace);
 
   /* Reparse the exception conditional expressions.  One for each
      location.  */
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 4343016b6b2..9a7d487981c 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -90,9 +90,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 *,
@@ -220,11 +223,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,
 };
 
@@ -289,7 +293,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;
@@ -323,7 +327,7 @@  struct momentary_breakpoint : public code_breakpoint
     thread = thread_;
   }
 
-  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;
@@ -334,7 +338,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,
@@ -1461,7 +1465,36 @@  breakpoint_set_thread (struct breakpoint *b, int thread)
 
   b->thread = thread;
   if (old_thread != thread)
-    gdb::observers::breakpoint_modified.notify (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.  */
+      gdb::observers::breakpoint_modified.notify (b);
+    }
 }
 
 /* See breakpoint.h.  */
@@ -8626,7 +8659,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;
 
@@ -8690,7 +8724,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);
@@ -8698,7 +8732,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);
 }
 
@@ -8797,6 +8831,24 @@  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.  If THREAD is -1, meaning all
+   threads, 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.  */
+
+static struct program_space *
+find_program_space_for_global_thread_id (int thread)
+{
+  if (thread == -1)
+    return nullptr;
+
+  struct thread_info *thr = find_thread_global_id (thread);
+  gdb_assert (thr != nullptr);
+  gdb_assert (thr->inf != nullptr);
+  return thr->inf->pspace;
+}
+
 /* See breakpoint.h.  */
 
 const struct breakpoint_ops *
@@ -8890,7 +8942,10 @@  create_breakpoint (struct gdbarch *gdbarch,
 
   try
     {
-      ops->create_sals_from_location_spec (locspec, &canonical);
+      struct program_space *search_pspace
+	= find_program_space_for_global_thread_id (thread);
+      ops->create_sals_from_location_spec (locspec, &canonical,
+					   search_pspace);
     }
   catch (const gdb_exception_error &e)
     {
@@ -9361,7 +9416,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."));
@@ -9448,7 +9503,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.
@@ -11548,7 +11603,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))
@@ -11558,7 +11613,7 @@  code_breakpoint::re_set ()
       return;
     }
 
-  re_set_default ();
+  re_set_default (pspace);
 }
 
 int
@@ -11764,7 +11819,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)
     {
@@ -11857,7 +11912,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.
@@ -11898,12 +11953,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));
 }
@@ -11993,9 +12049,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);
@@ -12053,8 +12109,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;
@@ -12561,12 +12619,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;
@@ -12729,40 +12807,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 *thread_pspace
+    = find_program_space_for_global_thread_id (this->thread);
+
+  /* If this is not a thread-specific breakpoint, or it is a
+     thread-specific breakpoint but we are looking for new locations in the
+     program space that the specific thread is running, then look for new
+     locations for this breakpoint.  */
+  if (thread_pspace == nullptr || filter_pspace == thread_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.  */
 
@@ -12797,7 +12880,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)
 	  {
@@ -12818,6 +12901,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 8abb46b54ad..e5293448536 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -558,15 +558,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
@@ -698,8 +698,12 @@  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).
+
+     FILTER_PSPACE is the program space in which symbols may have changed,
+     or can be nullptr to indicate that all program spaces may have
+     changed.  */
+  virtual void re_set (program_space *filter_pspace)
   {
     /* Nothing to re-set.  */
   }
@@ -927,7 +931,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;
@@ -949,7 +953,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.  */
@@ -971,7 +975,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/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 4889c31aff3..8cc47537e68 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"
 		    }
 		}
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/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.exp b/gdb/testsuite/gdb.multi/pending-bp.exp
index 4a65f79cfc4..8e1d1234948 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.
@@ -111,5 +153,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"
     }
 }