[Lldb-commits] [lldb] Add a scripted way to re-present a stop location (PR #158128)

via lldb-commits lldb-commits at lists.llvm.org
Thu Sep 11 11:08:11 PDT 2025


https://github.com/jimingham created https://github.com/llvm/llvm-project/pull/158128

This patch adds the notion of "Facade" locations which can be reported from a ScriptedResolver instead of the actual underlying breakpoint location for the breakpoint.  Also add a "was_hit" method to the scripted resolver that allows the breakpoint to say which of these "Facade" locations was hit, and "get_location_description" to provide a description for the facade locations.

I apologize in advance for the size of the patch.  Almost all of what's here was necessary to (a) make the feature testable and (b) not break any of the current behavior.

The motivation for this feature is given in the "Providing Facade Locations" section that I added to the python-reference.rst so I won't repeat it here.

rdar://152112327

>From b5a0c082685a2d76d3eafcfa2b470bed8218aec6 Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Thu, 14 Aug 2025 17:21:04 -0700
Subject: [PATCH] Add a way for scripted breakpoint resolvers to present
 "Facade" locations instead of the actual underlying breakpoint location for
 the breakpoint.  Also add a "was_hit" method to the scripted resolver that
 allows the breakpoint to say which of these "Facade" locations was hit, and
 "get_location_description" to provide a description for the facade locations.

rdar://152112327
---
 lldb/bindings/python/python-swigsafecast.swig |   4 +
 lldb/bindings/python/python-wrapper.swig      |  24 +++
 lldb/docs/use/python-reference.rst            |  61 ++++++
 lldb/include/lldb/API/SBBreakpoint.h          |   9 +-
 lldb/include/lldb/API/SBBreakpointLocation.h  |   1 +
 lldb/include/lldb/API/SBFrame.h               |   3 +-
 lldb/include/lldb/Breakpoint/Breakpoint.h     |  58 +++++-
 .../lldb/Breakpoint/BreakpointLocation.h      |  68 ++++++-
 .../Breakpoint/BreakpointLocationCollection.h |   2 +-
 .../lldb/Breakpoint/BreakpointLocationList.h  |   3 +-
 .../Breakpoint/BreakpointResolverScripted.h   |   6 +
 lldb/include/lldb/Breakpoint/BreakpointSite.h |   3 +-
 .../lldb/Breakpoint/StopPointSiteList.h       |  24 ---
 lldb/include/lldb/Breakpoint/StoppointSite.h  |   5 +-
 .../Interfaces/ScriptedBreakpointInterface.h  |  10 +
 .../lldb/Interpreter/ScriptInterpreter.h      |   6 +
 lldb/source/API/SBBreakpoint.cpp              |  10 +
 lldb/source/Breakpoint/Breakpoint.cpp         |  88 +++++++--
 lldb/source/Breakpoint/BreakpointLocation.cpp | 177 ++++++++++++++----
 .../BreakpointLocationCollection.cpp          |  16 +-
 .../Breakpoint/BreakpointLocationList.cpp     |  11 +-
 .../Breakpoint/BreakpointResolverScripted.cpp |  24 ++-
 lldb/source/Breakpoint/BreakpointSite.cpp     |   5 +-
 lldb/source/Interpreter/ScriptInterpreter.cpp |  13 ++
 .../ScriptedBreakpointPythonInterface.cpp     |  26 +++
 .../ScriptedBreakpointPythonInterface.h       |   6 +
 .../Interfaces/ScriptedPythonInterface.cpp    |  47 +++++
 .../Interfaces/ScriptedPythonInterface.h      |  27 +++
 .../Python/SWIGPythonBridge.h                 |   3 +
 lldb/source/Target/StopInfo.cpp               |  72 ++++---
 .../breakpoint/scripted_bkpt/resolver.py      |   1 -
 .../breakpoint/scripted_bkpt/was_hit/Makefile |   4 +
 .../scripted_bkpt/was_hit/TestWasHit.py       |  85 +++++++++
 .../scripted_bkpt/was_hit/bkpt_resolver.py    |  45 +++++
 .../breakpoint/scripted_bkpt/was_hit/main.c   |  17 ++
 .../Python/PythonTestSuite.cpp                |  10 +
 36 files changed, 840 insertions(+), 134 deletions(-)
 create mode 100644 lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/Makefile
 create mode 100644 lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/TestWasHit.py
 create mode 100644 lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/bkpt_resolver.py
 create mode 100644 lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/main.c

diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig
index 4721dfdc17e6a..3ea24f1a31414 100644
--- a/lldb/bindings/python/python-swigsafecast.swig
+++ b/lldb/bindings/python/python-swigsafecast.swig
@@ -142,5 +142,9 @@ PythonObject SWIGBridge::ToSWIGWrapper(
   return ToSWIGHelper(module_spec_sb.release(), SWIGTYPE_p_lldb__SBModuleSpec);
 }
 
+PythonObject SWIGBridge::ToSWIGWrapper(lldb::DescriptionLevel level) {
+  return PythonInteger((int64_t) level);
+}
+
 } // namespace python
 } // namespace lldb_private
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 2c30d536a753d..64b7dc8381073 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -422,6 +422,30 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *
   return sb_ptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject * data) {
+  lldb::SBFrame *sb_ptr = nullptr;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBFrame, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(PyObject * data) {
+  lldb::SBBreakpointLocation *sb_ptr = nullptr;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBBreakpointLocation, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject * data) {
   lldb::SBAttachInfo *sb_ptr = nullptr;
 
diff --git a/lldb/docs/use/python-reference.rst b/lldb/docs/use/python-reference.rst
index 4292714c9c208..5f40526a7308e 100644
--- a/lldb/docs/use/python-reference.rst
+++ b/lldb/docs/use/python-reference.rst
@@ -420,6 +420,67 @@ of Modules and the list of CompileUnits that will make up the SearchFilter. If
 you pass in empty lists, the breakpoint will use the default "search
 everywhere,accept everything" filter.
 
+Providing Facade Locations:
+
+The breakpoint resolver interface also allows you to present a separate set
+of locations for the breakpoint than the ones that actually implement the
+breakpoint in the target.
+
+An example use case for this is if you are providing a debugging interface for a
+library that implements an interpreter for a language lldb can't debug.  But
+while debugging that library at the level of the implementation language (e.g. C/C++, etc)
+you would like to offer the ability to "stop when a line in a source language
+file is executed".
+
+You can do this if you know where new lines of code are dispatched in the
+interpreter.  You would set a breakpoint there, and then look at the state
+when that breakpoint is hit to see if it is dispatching the source file and
+line that were requested, and stop appropriately.
+
+Facade breakpoint locations are intended to make a more natural presentation
+of that sort of feature.  The idea is that you would make a custom breakpoint
+resolver that sets actual locations in the places of interest in the interpreter.
+
+Then your resolver would add "facade locations" that represent the places in the
+interpreted code that you want the breakpoint to stop at, using SBBreakpoint::AddFacadeLocation.
+When lldb describes the breakpoint, it will only show the Facade locations.
+Since facade breakpoint location's description is customizable, you can make these
+locations more descriptive.  And when the "real" location is hit, lldb will call the
+"was_hit" method of your resolver.  That will return the facade location you
+consider to have been hit this time around, or if you return None, the breakpoint
+will be considered not to have been hit.
+
+Note, this feature is also useful if you don't intend to present facade
+locations since it essentially provides a scripted breakpoint condition.  Every
+time one of the locations in your breakpoint is hit, you can run the code in
+your "was_hit" to determine whether to consider the breakpoint hit or not, and
+return the location you were passed in if you want it to be a hit, and None if not.
+
+The Facade location adds these optional affordances to the Resolver class:
+
++------------------------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
+| Name                         | Arguments                              | Description                                                                                                      |
++------------------------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
+| ``was_hit``                  | ``frame``:`lldb.SBFrame`               | This will get called when one of the "real" locations set by your resolver is hit                                |
+|                              | ``bp_loc``:`lldb.SBBreakpointLocation` |                                                                                                                  |
+|                              |                                        |                                                                                                                  |
+|                              |                                        | ``frame`` is the stack frame that hit this location.                                                             |
+|                              |                                        |                                                                                                                  |
+|                              |                                        |                                                                                                                  |
+|                              |                                        | ``bp_loc`` is the real location that was hit.                                                                    |
+|                              |                                        |                                                                                                                  |
+|                              |                                        | Return either the facade location that you want to consider hit on this stop, or None if you don't consider      |
+|                              |                                        | any of your facade locations to have been hit.                                                                   |
++------------------------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
+| ``get_location_description`` | ``bp_loc``:`lldb.SBBreakpointLocation` | Use this to provide a helpful description of each facade location.                                               |
+|                              | ``desc_level``:`lldb.DescriptionLevel` |                                                                                                                  |
+|                              |                                        | ``bp_loc`` is the facade location to describe.                                                                   |
+|                              |                                        |                                                                                                                  |
+|                              |                                        |                                                                                                                  |
+|                              |                                        | ``desc_level`` is the level of description requested.  The Brief description is printed when the location is     |
+|                              |                                        | hit.  Full is printed for `break list` and Verbose for `break list -v`.                                          |
++------------------------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
+
 Using the python API' to create custom stepping logic
 -----------------------------------------------------
 
diff --git a/lldb/include/lldb/API/SBBreakpoint.h b/lldb/include/lldb/API/SBBreakpoint.h
index 18ed3e7226d3b..3bf87a1b7cc92 100644
--- a/lldb/include/lldb/API/SBBreakpoint.h
+++ b/lldb/include/lldb/API/SBBreakpoint.h
@@ -153,9 +153,16 @@ class LLDB_API SBBreakpoint {
   /// fails, e.g. when there aren't enough hardware resources available.
   lldb::SBError SetIsHardware(bool is_hardware);
 
-  // Can only be called from a ScriptedBreakpointResolver...
+  /// Adds a location to the breakpoint at the address passed in.
+  /// Can only be called from a ScriptedBreakpointResolver...
   SBError
   AddLocation(SBAddress &address);
+  /// Add a "Facade location" to the breakpoint.  This returns the Facade 
+  /// Location that was added, which you can then use in 
+  /// get_location_description and was_hit in your breakpoint resolver.
+  /// Can only be called from a ScriptedBreakpointResolver.
+  SBBreakpointLocation
+  AddFacadeLocation();
 
   SBStructuredData SerializeToStructuredData();
 
diff --git a/lldb/include/lldb/API/SBBreakpointLocation.h b/lldb/include/lldb/API/SBBreakpointLocation.h
index fa823e2b518ac..deda4970cd0ed 100644
--- a/lldb/include/lldb/API/SBBreakpointLocation.h
+++ b/lldb/include/lldb/API/SBBreakpointLocation.h
@@ -24,6 +24,7 @@ class SWIGBridge;
 namespace lldb {
 
 class LLDB_API SBBreakpointLocation {
+  friend class lldb_private::ScriptInterpreter;
 public:
   SBBreakpointLocation();
 
diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h
index e4bbcd5ddcd9c..4abb44b4bb0e5 100644
--- a/lldb/include/lldb/API/SBFrame.h
+++ b/lldb/include/lldb/API/SBFrame.h
@@ -225,7 +225,8 @@ class LLDB_API SBFrame {
   friend class SBInstruction;
   friend class SBThread;
   friend class SBValue;
-
+  
+  friend class lldb_private::ScriptInterpreter;
   friend class lldb_private::python::SWIGBridge;
   friend class lldb_private::lua::SWIGBridge;
 
diff --git a/lldb/include/lldb/Breakpoint/Breakpoint.h b/lldb/include/lldb/Breakpoint/Breakpoint.h
index 26a5e901a0d7e..2bef6a919d878 100644
--- a/lldb/include/lldb/Breakpoint/Breakpoint.h
+++ b/lldb/include/lldb/Breakpoint/Breakpoint.h
@@ -248,6 +248,23 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
   ///    Returns a pointer to the new location.
   lldb::BreakpointLocationSP AddLocation(const Address &addr,
                                          bool *new_location = nullptr);
+  /// Add a `facade` location to the breakpoint's collection of facade locations.  
+  /// This is only meant to be called by the breakpoint's resolver.
+  /// Facade locations are placeholders that a scripted breakpoint can use to
+  /// represent the stop locations provided by the breakpoint.  The scripted
+  /// breakpoint should record the id of the facade location, and provide
+  /// the description of the location in the GetDescription method
+  /// To emulate hitting a facade location, the breakpoint's WasHit should
+  /// return the ID of the facade that was "hit".
+  ///
+  /// \param[out] new_location
+  ///    Set to \b true if a new location was created, to \b false if there
+  ///    already was a location at this Address.
+  /// \return
+  ///    Returns a pointer to the new location.
+  lldb::BreakpointLocationSP AddFacadeLocation();
+  
+  lldb::BreakpointLocationSP GetFacadeLocationByID(lldb::break_id_t);
 
   /// Find a breakpoint location by Address.
   ///
@@ -268,27 +285,36 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
   ///    there is no breakpoint location at that address.
   lldb::break_id_t FindLocationIDByAddress(const Address &addr);
 
-  /// Find a breakpoint location for a given breakpoint location ID.
+  /// Find a breakpoint location for a given breakpoint location ID.  If there
+  /// are Facade Locations in the breakpoint, the facade locations will be
+  /// searched instead of the "real" ones.
   ///
   /// \param[in] bp_loc_id
   ///    The ID specifying the location.
+  ///
+  /// \param[in] use_facade
+  /// If \b true, then prefer facade locations over "real" ones if they exist.  
+  ///
   /// \return
   ///    Returns a shared pointer to the location with ID \a bp_loc_id.  The
   ///    pointer
   ///    in the shared pointer will be nullptr if there is no location with that
   ///    ID.
-  lldb::BreakpointLocationSP FindLocationByID(lldb::break_id_t bp_loc_id);
+  lldb::BreakpointLocationSP FindLocationByID(lldb::break_id_t bp_loc_id, bool use_facade = true);
 
   /// Get breakpoint locations by index.
   ///
   /// \param[in] index
   ///    The location index.
   ///
+  /// \param[in] use_facade
+  /// If \b true, then prefer facade locations over "real" ones if they exist.  
+  ///
   /// \return
   ///     Returns a shared pointer to the location with index \a
   ///     index. The shared pointer might contain nullptr if \a index is
   ///     greater than then number of actual locations.
-  lldb::BreakpointLocationSP GetLocationAtIndex(size_t index);
+  lldb::BreakpointLocationSP GetLocationAtIndex(size_t index, bool use_facade = true);
 
   /// Removes all invalid breakpoint locations.
   ///
@@ -409,9 +435,12 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
   /// Return the number of breakpoint locations that have resolved to actual
   /// breakpoint sites.
   ///
+  /// \param[in] use_facade
+  /// If \b true, then prefer facade locations over "real" ones if they exist.  
+  ///
   /// \return
   ///     The number locations resolved breakpoint sites.
-  size_t GetNumResolvedLocations() const;
+  size_t GetNumResolvedLocations(bool use_facade = true) const;
 
   /// Return whether this breakpoint has any resolved locations.
   ///
@@ -421,9 +450,12 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
 
   /// Return the number of breakpoint locations.
   ///
+  /// \param[in] use_facade
+  /// If \b true, then prefer facade locations over "real" ones if they exist.  
+  ///
   /// \return
   ///     The number breakpoint locations.
-  size_t GetNumLocations() const;
+  size_t GetNumLocations(bool use_facade = true) const;
 
   /// Put a description of this breakpoint into the stream \a s.
   ///
@@ -529,6 +561,20 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
       m_name_list.erase(name_to_remove);
   }
 
+  enum TypeDisplay {
+    eDisplayFacade = 1,
+    eDisplayReal   = 1 << 1,
+    eDisplayHeader = 1 << 2
+  };
+
+  void GetDescriptionForType(Stream *s, lldb::DescriptionLevel level,
+                      uint8_t display_type, bool show_locations);
+                      
+  bool HasFacadeLocations() {
+    return m_facade_locations.GetSize() != 0;
+  }
+
+
 public:
   bool MatchesName(const char *name) {
     return m_name_list.find(name) != m_name_list.end();
@@ -657,6 +703,8 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
   BreakpointOptions m_options; // Settable breakpoint options
   BreakpointLocationList
       m_locations; // The list of locations currently found for this breakpoint.
+  BreakpointLocationCollection m_facade_locations;
+
   std::string m_kind_description;
   bool m_resolve_indirect_symbols;
 
diff --git a/lldb/include/lldb/Breakpoint/BreakpointLocation.h b/lldb/include/lldb/Breakpoint/BreakpointLocation.h
index ab2e5e170559d..c4fa9bec51aae 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointLocation.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointLocation.h
@@ -38,6 +38,12 @@ namespace lldb_private {
 
 class BreakpointLocation
     : public std::enable_shared_from_this<BreakpointLocation> {
+  friend class BreakpointSite;
+  friend class BreakpointLocationList;
+  friend class Breakpoint;
+  friend class Process;
+  friend class StopInfoBreakpoint;
+
 public:
   ~BreakpointLocation();
 
@@ -55,16 +61,39 @@ class BreakpointLocation
 
   Target &GetTarget();
 
+  /// This is a programmatic version of a breakpoint "condition".  When a
+  /// breakpoint is hit, WasHit will get called before the synchronous ShouldStop
+  /// callback is run, and if it returns an empty BreakpointLocationSP, lldb will
+  /// act as if that breakpoint wasn't hit.
+  ///
+  /// \param[in] context
+  ///   The context at the stop point
+  ///    
+  /// \return
+  ///    This will return the breakpoint location that was hit on this stop.
+  ///    If there was no facade location this will be the original location.
+  ///    If the shared pointer is empty, then we'll treat it as if the 
+  ///    breakpoint was not hit.
+  lldb::BreakpointLocationSP WasHit(StoppointCallbackContext *context);
+
   /// Determines whether we should stop due to a hit at this breakpoint
   /// location.
   ///
   /// Side Effects: This may evaluate the breakpoint condition, and run the
   /// callback.  So this command may do a considerable amount of work.
   ///
+  /// \param[in] context
+  ///   The context at the stop point
+  ///    
+  /// \param[out] facade_loc_sp
+  ///   If this stop should be attributed not to the location that was hit, but
+  ///   to a facade location, it will be returned in this facade_loc_sp.
+  ///    
   /// \return
   ///     \b true if this breakpoint location thinks we should stop,
   ///     \b false otherwise.
-  bool ShouldStop(StoppointCallbackContext *context);
+  bool ShouldStop(StoppointCallbackContext *context, 
+          lldb::BreakpointLocationSP &facade_loc_sp);
 
   // The next section deals with various breakpoint options.
 
@@ -292,11 +321,6 @@ class BreakpointLocation
   }
 
 protected:
-  friend class BreakpointSite;
-  friend class BreakpointLocationList;
-  friend class Process;
-  friend class StopInfoBreakpoint;
-
   /// Set the breakpoint site for this location to \a bp_site_sp.
   ///
   /// \param[in] bp_site_sp
@@ -346,9 +370,11 @@ class BreakpointLocation
   // Constructors and Destructors
   //
   // Only the Breakpoint can make breakpoint locations, and it owns them.
-
   /// Constructor.
   ///
+  /// \param[in] loc_id
+  ///     The location id of the new location. 
+  ///
   /// \param[in] owner
   ///     A back pointer to the breakpoint that owns this location.
   ///
@@ -359,10 +385,25 @@ class BreakpointLocation
   ///     The thread for which this breakpoint location is valid, or
   ///     LLDB_INVALID_THREAD_ID if it is valid for all threads.
   ///
-  BreakpointLocation(lldb::break_id_t bid, Breakpoint &owner,
+  BreakpointLocation(lldb::break_id_t loc_id, Breakpoint &owner,
                      const Address &addr, lldb::tid_t tid,
                      bool check_for_resolver = true);
 
+  /// This is the constructor for locations with no address.  Currently this is
+  /// just used for Facade locations. 
+  ///
+  /// \param[in] loc_id
+  ///     The location id of the new location. 
+  ///
+  /// \param[in] owner
+  ///     A back pointer to the breakpoint that owns this location.
+  ///
+  ///
+public:
+  BreakpointLocation(lldb::break_id_t loc_id, Breakpoint &owner);
+  bool IsValid() const { return m_is_valid; }
+  bool IsFacade() const { return m_is_facade; }
+private:
   // Data members:
   bool m_should_resolve_indirect_functions;
   bool m_is_reexported;
@@ -390,6 +431,17 @@ class BreakpointLocation
   /// location was given somewhere in the virtual inlined call stack since the
   /// Address always resolves to the lowest entry in the stack.
   std::optional<LineEntry> m_preferred_line_entry;
+  bool m_is_valid = true;  /// Because Facade locations don't have sites
+                           /// we can't use the presence of the site to mean
+                           /// this breakpoint is valid, but must manage
+                           /// the state directly.
+  bool m_is_facade = false; /// Facade locations aren't directly triggered
+                            /// and don't have a breakpoint site.  They are
+                            /// a useful fiction when you want to represent
+                            /// the stop location as something lldb can't
+                            /// naturally stop at.
+
+  void SetInvalid() { m_is_valid = false; }
 
   void SetShouldResolveIndirectFunctions(bool do_resolve) {
     m_should_resolve_indirect_functions = do_resolve;
diff --git a/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h b/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h
index 3aef1d658c0e5..8ef13d304d044 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h
@@ -111,7 +111,7 @@ class BreakpointLocationCollection {
   ///
   /// \return
   ///    \b true if we should stop, \b false otherwise.
-  bool ShouldStop(StoppointCallbackContext *context);
+  bool ShouldStop(StoppointCallbackContext *context, BreakpointLocationCollection &stopped_bp_locs);
 
   /// Print a description of the breakpoint locations in this list
   /// to the stream \a s.
diff --git a/lldb/include/lldb/Breakpoint/BreakpointLocationList.h b/lldb/include/lldb/Breakpoint/BreakpointLocationList.h
index 17dc0bfe03148..8c8309b731b6b 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointLocationList.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointLocationList.h
@@ -140,7 +140,8 @@ class BreakpointLocationList {
   ///
   /// \return
   ///     \b true if we should stop, \b false otherwise.
-  bool ShouldStop(StoppointCallbackContext *context, lldb::break_id_t breakID);
+  bool ShouldStop(StoppointCallbackContext *context, lldb::break_id_t breakID, 
+      lldb::BreakpointLocationSP &bp_loc_sp);
 
   /// Returns the number of elements in this breakpoint location list.
   ///
diff --git a/lldb/include/lldb/Breakpoint/BreakpointResolverScripted.h b/lldb/include/lldb/Breakpoint/BreakpointResolverScripted.h
index 0322fd9f46ede..7e747ab0bdc1f 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointResolverScripted.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointResolverScripted.h
@@ -45,6 +45,12 @@ class BreakpointResolverScripted : public BreakpointResolver {
 
   void GetDescription(Stream *s) override;
 
+  lldb::BreakpointLocationSP WasHit(lldb::StackFrameSP frame_sp, 
+                                    lldb::BreakpointLocationSP bp_loc_sp);
+  
+  std::optional<std::string> GetLocationDescription(lldb::BreakpointLocationSP bp_loc_sp,
+      lldb::DescriptionLevel level);
+
   void Dump(Stream *s) const override;
 
   /// Methods for support type inquiry through isa, cast, and dyn_cast:
diff --git a/lldb/include/lldb/Breakpoint/BreakpointSite.h b/lldb/include/lldb/Breakpoint/BreakpointSite.h
index 7b3f7be23639f..75b5ed9f4f531 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointSite.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointSite.h
@@ -99,7 +99,8 @@ class BreakpointSite : public std::enable_shared_from_this<BreakpointSite>,
   ///
   /// \return
   ///    \b true if we should stop, \b false otherwise.
-  bool ShouldStop(StoppointCallbackContext *context) override;
+  bool ShouldStop(StoppointCallbackContext *context, 
+          BreakpointLocationCollection &stopping_bp_loc) override;
 
   /// Standard Dump method
   void Dump(Stream *s) const override;
diff --git a/lldb/include/lldb/Breakpoint/StopPointSiteList.h b/lldb/include/lldb/Breakpoint/StopPointSiteList.h
index 7ed53e952dc8d..101eccda4616b 100644
--- a/lldb/include/lldb/Breakpoint/StopPointSiteList.h
+++ b/lldb/include/lldb/Breakpoint/StopPointSiteList.h
@@ -213,30 +213,6 @@ template <typename StopPointSite> class StopPointSiteList {
 
   typedef void (*StopPointSiteSPMapFunc)(StopPointSite &site, void *baton);
 
-  /// Enquires of the site on in this list with ID \a site_id
-  /// whether we should stop for the constituent or not.
-  ///
-  /// \param[in] context
-  ///    This contains the information about this stop.
-  ///
-  /// \param[in] site_id
-  ///    This site ID that we hit.
-  ///
-  /// \return
-  ///    \b true if we should stop, \b false otherwise.
-  bool ShouldStop(StoppointCallbackContext *context,
-                  typename StopPointSite::SiteID site_id) {
-    if (StopPointSiteSP site_sp = FindByID(site_id)) {
-      // Let the site decide if it should stop here (could not have
-      // reached it's target hit count yet, or it could have a callback that
-      // decided it shouldn't stop (shared library loads/unloads).
-      return site_sp->ShouldStop(context);
-    }
-    // We should stop here since this site isn't valid anymore or it
-    // doesn't exist.
-    return true;
-  }
-
   /// Returns the number of elements in the list.
   ///
   /// \result
diff --git a/lldb/include/lldb/Breakpoint/StoppointSite.h b/lldb/include/lldb/Breakpoint/StoppointSite.h
index bef19f37908c6..a398aa838596c 100644
--- a/lldb/include/lldb/Breakpoint/StoppointSite.h
+++ b/lldb/include/lldb/Breakpoint/StoppointSite.h
@@ -38,7 +38,10 @@ class StoppointSite {
 
   virtual bool IsHardware() const = 0;
 
-  virtual bool ShouldStop(StoppointCallbackContext* context) = 0;
+  virtual bool ShouldStop(StoppointCallbackContext* context) { return false; };
+
+  virtual bool ShouldStop(StoppointCallbackContext* context,
+          BreakpointLocationCollection &stopping_bp_locs) { return false; };
 
   virtual void Dump(Stream* stream) const = 0;
 
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h
index 28d6ed992b141..c203e7f15d279 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h
@@ -26,6 +26,16 @@ class ScriptedBreakpointInterface : public ScriptedInterface {
   virtual bool ResolverCallback(SymbolContext sym_ctx) { return true; }
   virtual lldb::SearchDepth GetDepth() { return lldb::eSearchDepthModule; }
   virtual std::optional<std::string> GetShortHelp() { return nullptr; }
+  /// WasHit returns the breakpoint location SP for the location that was "hit".
+  virtual lldb::BreakpointLocationSP WasHit(lldb::StackFrameSP frame_sp, 
+                          lldb::BreakpointLocationSP bp_loc_sp) { 
+      return LLDB_INVALID_BREAK_ID;
+  }
+  virtual std::optional<std::string> 
+      GetLocationDescription(lldb::BreakpointLocationSP bp_loc_sp, 
+                             lldb::DescriptionLevel level) { 
+      return {};
+  }
 };
 } // namespace lldb_private
 
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 024bbc90a9a39..554f8128344d9 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -11,6 +11,7 @@
 
 #include "lldb/API/SBAttachInfo.h"
 #include "lldb/API/SBBreakpoint.h"
+#include "lldb/API/SBBreakpointLocation.h"
 #include "lldb/API/SBData.h"
 #include "lldb/API/SBError.h"
 #include "lldb/API/SBEvent.h"
@@ -572,12 +573,17 @@ class ScriptInterpreter : public PluginInterface {
 
   lldb::StreamSP GetOpaqueTypeFromSBStream(const lldb::SBStream &stream) const;
 
+  lldb::StackFrameSP GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const;
+
   SymbolContext
   GetOpaqueTypeFromSBSymbolContext(const lldb::SBSymbolContext &sym_ctx) const;
 
   lldb::BreakpointSP
   GetOpaqueTypeFromSBBreakpoint(const lldb::SBBreakpoint &breakpoint) const;
 
+  lldb::BreakpointLocationSP
+  GetOpaqueTypeFromSBBreakpointLocation(const lldb::SBBreakpointLocation &break_loc) const;
+
   lldb::ProcessAttachInfoSP
   GetOpaqueTypeFromSBAttachInfo(const lldb::SBAttachInfo &attach_info) const;
 
diff --git a/lldb/source/API/SBBreakpoint.cpp b/lldb/source/API/SBBreakpoint.cpp
index 07c0a2ea907ba..93748dd46f69c 100644
--- a/lldb/source/API/SBBreakpoint.cpp
+++ b/lldb/source/API/SBBreakpoint.cpp
@@ -567,6 +567,16 @@ SBError SBBreakpoint::AddLocation(SBAddress &address) {
   return error;
 }
 
+SBBreakpointLocation
+SBBreakpoint::AddFacadeLocation() {
+  BreakpointSP bkpt_sp = GetSP();
+  if (!bkpt_sp) {
+    return {};
+  }
+  BreakpointLocationSP loc_sp = bkpt_sp->AddFacadeLocation();
+  return SBBreakpointLocation(loc_sp);
+}
+
 SBStructuredData SBBreakpoint::SerializeToStructuredData() {
   LLDB_INSTRUMENT_VA(this);
 
diff --git a/lldb/source/Breakpoint/Breakpoint.cpp b/lldb/source/Breakpoint/Breakpoint.cpp
index 1544bf85fb859..d6d2f8aa17c19 100644
--- a/lldb/source/Breakpoint/Breakpoint.cpp
+++ b/lldb/source/Breakpoint/Breakpoint.cpp
@@ -58,7 +58,12 @@ Breakpoint::Breakpoint(Target &new_target, const Breakpoint &source_bp)
       m_hit_counter() {}
 
 // Destructor
-Breakpoint::~Breakpoint() = default;
+Breakpoint::~Breakpoint() {
+  for (BreakpointLocationSP location_sp : m_locations.BreakpointLocations())
+    location_sp->SetInvalid();
+  for (BreakpointLocationSP location_sp : m_facade_locations.BreakpointLocations())
+    location_sp->SetInvalid();
+}
 
 BreakpointSP Breakpoint::CopyFromBreakpoint(TargetSP new_target,
                                             const Breakpoint &bp_to_copy_from) {
@@ -302,6 +307,19 @@ BreakpointLocationSP Breakpoint::AddLocation(const Address &addr,
                                  new_location);
 }
 
+BreakpointLocationSP Breakpoint::AddFacadeLocation() {
+  size_t next_id = m_facade_locations.GetSize() + 1;
+  BreakpointLocationSP break_loc_sp 
+      = std::make_shared<BreakpointLocation>(next_id, *this);
+  break_loc_sp->m_is_facade = true;
+  m_facade_locations.Add(break_loc_sp);
+  return break_loc_sp;
+}
+
+BreakpointLocationSP Breakpoint::GetFacadeLocationByID(lldb::break_id_t loc_id) {
+  return m_facade_locations.GetByIndex(loc_id - 1);
+}
+
 BreakpointLocationSP Breakpoint::FindLocationByAddress(const Address &addr) {
   return m_locations.FindByAddress(addr);
 }
@@ -310,15 +328,21 @@ break_id_t Breakpoint::FindLocationIDByAddress(const Address &addr) {
   return m_locations.FindIDByAddress(addr);
 }
 
-BreakpointLocationSP Breakpoint::FindLocationByID(break_id_t bp_loc_id) {
+BreakpointLocationSP Breakpoint::FindLocationByID(break_id_t bp_loc_id, bool use_facade) {
+  if (use_facade && m_facade_locations.GetSize())
+    return GetFacadeLocationByID(bp_loc_id);
   return m_locations.FindByID(bp_loc_id);
 }
 
-BreakpointLocationSP Breakpoint::GetLocationAtIndex(size_t index) {
+BreakpointLocationSP Breakpoint::GetLocationAtIndex(size_t index, bool use_facade) {
+  if (use_facade && m_facade_locations.GetSize() > 0)
+    return m_facade_locations.GetByIndex(index);
   return m_locations.GetByIndex(index);
 }
 
 void Breakpoint::RemoveInvalidLocations(const ArchSpec &arch) {
+  // FIXME: Should we ask the scripted resolver whether any of its facade
+  // locations are invalid?
   m_locations.RemoveInvalidLocations(arch);
 }
 
@@ -864,9 +888,15 @@ void Breakpoint::ModuleReplaced(ModuleSP old_module_sp,
 
 void Breakpoint::Dump(Stream *) {}
 
-size_t Breakpoint::GetNumResolvedLocations() const {
+size_t Breakpoint::GetNumResolvedLocations(bool use_facade) const {
   // Return the number of breakpoints that are actually resolved and set down
   // in the inferior process.
+  // All facade locations are considered to be resolved:
+  if (use_facade) {
+    size_t num_facade_locs = m_facade_locations.GetSize();
+    if (num_facade_locs)
+      return num_facade_locs;
+  }
   return m_locations.GetNumResolvedLocations();
 }
 
@@ -874,7 +904,14 @@ bool Breakpoint::HasResolvedLocations() const {
   return GetNumResolvedLocations() > 0;
 }
 
-size_t Breakpoint::GetNumLocations() const { return m_locations.GetSize(); }
+size_t Breakpoint::GetNumLocations(bool use_facade) const { 
+  if (use_facade) {
+    size_t num_facade_locs = m_facade_locations.GetSize();
+    if (num_facade_locs > 0)
+      return num_facade_locs;
+  }
+  return m_locations.GetSize(); 
+}
 
 void Breakpoint::AddName(llvm::StringRef new_name) {
   m_name_list.insert(new_name.str());
@@ -899,8 +936,30 @@ void Breakpoint::GetDescription(Stream *s, lldb::DescriptionLevel level,
     s->Printf("Kind: %s\n", GetBreakpointKind());
   }
 
-  const size_t num_locations = GetNumLocations();
-  const size_t num_resolved_locations = GetNumResolvedLocations();
+  bool show_both_types = level == eDescriptionLevelVerbose 
+    && HasFacadeLocations() && show_locations;
+  uint8_t display_mask = eDisplayFacade;
+  if (show_both_types)
+    display_mask |= eDisplayHeader;
+    
+  GetDescriptionForType(s, level, display_mask, show_locations);
+  
+  if (show_both_types) {
+    display_mask = eDisplayReal | eDisplayHeader;
+    GetDescriptionForType(s, level, display_mask, show_locations);
+  }
+  // Reset the colors back to normal if they were previously greyed out.
+  if (dim_breakpoint_description)
+    s->Printf("%s", ansi::FormatAnsiTerminalCodes(
+                        GetTarget().GetDebugger().GetDisabledAnsiSuffix())
+                        .c_str());
+}
+  
+void Breakpoint::GetDescriptionForType(Stream *s, lldb::DescriptionLevel level,
+                                       uint8_t display_type, bool show_locations) {
+  bool use_facade = (display_type & eDisplayFacade) != 0;
+  const size_t num_locations = GetNumLocations(use_facade);
+  const size_t num_resolved_locations = GetNumResolvedLocations(use_facade);
 
   // They just made the breakpoint, they don't need to be told HOW they made
   // it... Also, we'll print the breakpoint number differently depending on
@@ -957,7 +1016,7 @@ void Breakpoint::GetDescription(Stream *s, lldb::DescriptionLevel level,
     } else if (num_locations == 1 && !show_locations) {
       // There is only one location, so we'll just print that location
       // information.
-      GetLocationAtIndex(0)->GetDescription(s, level);
+      GetLocationAtIndex(0, use_facade)->GetDescription(s, level);
     } else {
       s->Printf("%" PRIu64 " locations.", static_cast<uint64_t>(num_locations));
     }
@@ -979,20 +1038,21 @@ void Breakpoint::GetDescription(Stream *s, lldb::DescriptionLevel level,
   // The brief description is just the location name (1.2 or whatever).  That's
   // pointless to show in the breakpoint's description, so suppress it.
   if (show_locations && level != lldb::eDescriptionLevelBrief) {
+    if ((display_type & eDisplayHeader) != 0)
+      if ((display_type & eDisplayFacade) != 0)
+        s->Printf("Facade locations:\n");
+      else
+        s->Printf("Implementation Locations\n");
+
     s->IndentMore();
     for (size_t i = 0; i < num_locations; ++i) {
-      BreakpointLocation *loc = GetLocationAtIndex(i).get();
+      BreakpointLocation *loc = GetLocationAtIndex(i, use_facade).get();
       loc->GetDescription(s, level);
       s->EOL();
     }
     s->IndentLess();
   }
 
-  // Reset the colors back to normal if they were previously greyed out.
-  if (dim_breakpoint_description)
-    s->Printf("%s", ansi::FormatAnsiTerminalCodes(
-                        GetTarget().GetDebugger().GetDisabledAnsiSuffix())
-                        .c_str());
 }
 
 void Breakpoint::GetResolverDescription(Stream *s) {
diff --git a/lldb/source/Breakpoint/BreakpointLocation.cpp b/lldb/source/Breakpoint/BreakpointLocation.cpp
index 443d4f50833d3..829ed03392999 100644
--- a/lldb/source/Breakpoint/BreakpointLocation.cpp
+++ b/lldb/source/Breakpoint/BreakpointLocation.cpp
@@ -8,6 +8,8 @@
 
 #include "lldb/Breakpoint/BreakpointLocation.h"
 #include "lldb/Breakpoint/BreakpointID.h"
+#include "lldb/Breakpoint/BreakpointResolver.h"
+#include "lldb/Breakpoint/BreakpointResolverScripted.h"
 #include "lldb/Breakpoint/StoppointCallbackContext.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Module.h"
@@ -45,6 +47,13 @@ BreakpointLocation::BreakpointLocation(break_id_t loc_id, Breakpoint &owner,
   SetThreadIDInternal(tid);
 }
 
+BreakpointLocation::BreakpointLocation(break_id_t loc_id, Breakpoint &owner)
+    : m_should_resolve_indirect_functions(false), m_is_reexported(false),
+      m_is_indirect(false), m_address(LLDB_INVALID_ADDRESS), m_owner(owner),
+      m_condition_hash(0), m_loc_id(loc_id), m_hit_counter() {
+  SetThreadIDInternal(LLDB_INVALID_THREAD_ID);
+}
+
 BreakpointLocation::~BreakpointLocation() {
   llvm::consumeError(ClearBreakpointSite());
 }
@@ -372,12 +381,41 @@ bool BreakpointLocation::ValidForThisThread(Thread &thread) {
           .GetThreadSpecNoCreate());
 }
 
+BreakpointLocationSP BreakpointLocation::WasHit(StoppointCallbackContext *context)
+{
+  // Only the BreakpointResolverScripted provides WasHit.
+  BreakpointResolverSP resolver_sp = GetBreakpoint().GetResolver();
+  BreakpointResolverScripted *scripted = llvm::dyn_cast<BreakpointResolverScripted>(resolver_sp.get());
+  if (!scripted)
+    return shared_from_this();
+
+  StackFrameSP frame_sp = context->exe_ctx_ref.GetFrameSP();
+  if (!frame_sp)
+    return shared_from_this();
+
+  BreakpointLocationSP return_loc_sp = scripted->WasHit(frame_sp, shared_from_this());
+  // If this is a facade location, then we won't have bumped its hit count
+  // while processing the original location hit.  Do so here.  We don't need
+  // to bump the breakpoint's hit count, however, since hitting the real
+  // location would have already done that.
+  // Also we have to check the enabled state here, since we would never have
+  // gotten here with a real location...
+  if (return_loc_sp && return_loc_sp->IsFacade()) {
+     if (return_loc_sp->IsEnabled())
+       return_loc_sp->m_hit_counter.Increment();
+     else
+       return {};
+  }
+  return return_loc_sp;
+}
+
 // RETURNS - true if we should stop at this breakpoint, false if we
 // should continue.  Note, we don't check the thread spec for the breakpoint
 // here, since if the breakpoint is not for this thread, then the event won't
 // even get reported, so the check is redundant.
 
-bool BreakpointLocation::ShouldStop(StoppointCallbackContext *context) {
+bool BreakpointLocation::ShouldStop(StoppointCallbackContext *context, 
+        lldb::BreakpointLocationSP &facade_loc_sp) {
   bool should_stop = true;
   Log *log = GetLog(LLDBLog::Breakpoints);
 
@@ -386,6 +424,27 @@ bool BreakpointLocation::ShouldStop(StoppointCallbackContext *context) {
   if (!IsEnabled())
     return false;
 
+  // Next check WasHit:
+  BreakpointLocationSP loc_hit_sp = WasHit(context);
+  
+  if (!loc_hit_sp) {
+    // We bump the hit counts in StopInfoBreakpoint::ShouldStopSynchronous,
+    // before we call into each location's ShouldStop.  So we need to undo
+    // that here.
+    UndoBumpHitCount();
+    return false;
+  }
+  
+  // If the location hit was not us, it was a facade location, in which case
+  // we should use the facade location's callbacks, etc.  Those will all be
+  // run in the asynchronous phase, so for now we just have to record the fact
+  // that we should treat this as a facade hit.  This is strictly an out
+  // parameter, so clear it if this isn't a facade hit.
+  if (loc_hit_sp.get() != this)
+    facade_loc_sp = loc_hit_sp;
+  else
+    facade_loc_sp.reset();
+
   // We only run synchronous callbacks in ShouldStop:
   context->is_synchronous = true;
   should_stop = InvokeCallback(context);
@@ -395,6 +454,11 @@ bool BreakpointLocation::ShouldStop(StoppointCallbackContext *context) {
     GetDescription(&s, lldb::eDescriptionLevelVerbose);
     LLDB_LOGF(log, "Hit breakpoint location: %s, %s.\n", s.GetData(),
               should_stop ? "stopping" : "continuing");
+    if (facade_loc_sp) {
+      s.Clear();
+      facade_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose);
+      LLDB_LOGF(log, "Attributing to facade location: %s.\n", s.GetData());
+    }
   }
 
   return should_stop;
@@ -417,7 +481,10 @@ void BreakpointLocation::UndoBumpHitCount() {
 }
 
 bool BreakpointLocation::IsResolved() const {
-  return m_bp_site_sp.get() != nullptr;
+
+  bool has_site = m_bp_site_sp.get() != nullptr;
+  // Facade locations are currently always considered resolved.
+  return has_site || IsFacade();
 }
 
 lldb::BreakpointSiteSP BreakpointLocation::GetBreakpointSite() const {
@@ -425,7 +492,9 @@ lldb::BreakpointSiteSP BreakpointLocation::GetBreakpointSite() const {
 }
 
 llvm::Error BreakpointLocation::ResolveBreakpointSite() {
-  if (m_bp_site_sp)
+  // This might be a facade location, which doesn't have an address.
+  // In that case, don't attempt to make a site.
+  if (m_bp_site_sp  || IsFacade())
     return llvm::Error::success();
 
   Process *process = m_owner.GetTarget().GetProcessSP().get();
@@ -454,8 +523,13 @@ bool BreakpointLocation::SetBreakpointSite(BreakpointSiteSP &bp_site_sp) {
 }
 
 llvm::Error BreakpointLocation::ClearBreakpointSite() {
-  if (!m_bp_site_sp)
-    return llvm::createStringError("no breakpoint site to clear");
+  if (!m_bp_site_sp) {
+    // This might be a Facade Location, which don't have sites or addresses
+    if (IsFacade())
+      return llvm::Error::success();
+    else
+      return llvm::createStringError("no breakpoint site to clear");
+  }
 
   // If the process exists, get it to remove the owner, it will remove the
   // physical implementation of the breakpoint as well if there are no more
@@ -473,6 +547,17 @@ llvm::Error BreakpointLocation::ClearBreakpointSite() {
 void BreakpointLocation::GetDescription(Stream *s,
                                         lldb::DescriptionLevel level) {
   SymbolContext sc;
+  
+  // If this is a scripted breakpoint, give it a chance to describe its
+  // locations:
+  std::optional<std::string> scripted_opt;
+  BreakpointResolverSP resolver_sp = GetBreakpoint().GetResolver();
+  BreakpointResolverScripted *scripted = 
+      llvm::dyn_cast<BreakpointResolverScripted>(resolver_sp.get());
+  if (scripted)
+        scripted_opt = scripted->GetLocationDescription(shared_from_this(), level);
+
+  bool is_scripted_desc = scripted_opt.has_value();
 
   // If the description level is "initial" then the breakpoint is printing out
   // our initial state, and we should let it decide how it wants to print our
@@ -491,7 +576,9 @@ void BreakpointLocation::GetDescription(Stream *s,
   if (level == lldb::eDescriptionLevelVerbose)
     s->IndentMore();
 
-  if (m_address.IsSectionOffset()) {
+  if (is_scripted_desc) {
+    s->PutCString(scripted_opt->c_str());
+  } else if (m_address.IsSectionOffset()) {
     m_address.CalculateSymbolContext(&sc);
 
     if (level == lldb::eDescriptionLevelFull ||
@@ -566,43 +653,51 @@ void BreakpointLocation::GetDescription(Stream *s,
     s->Indent();
   }
 
-  if (m_address.IsSectionOffset() &&
-      (level == eDescriptionLevelFull || level == eDescriptionLevelInitial))
-    s->Printf(", ");
-  s->Printf("address = ");
-
-  ExecutionContextScope *exe_scope = nullptr;
-  Target *target = &m_owner.GetTarget();
-  if (target)
-    exe_scope = target->GetProcessSP().get();
-  if (exe_scope == nullptr)
-    exe_scope = target;
-
-  if (level == eDescriptionLevelInitial)
-    m_address.Dump(s, exe_scope, Address::DumpStyleLoadAddress,
-                   Address::DumpStyleFileAddress);
-  else
-    m_address.Dump(s, exe_scope, Address::DumpStyleLoadAddress,
-                   Address::DumpStyleModuleWithFileAddress);
-
-  if (IsIndirect() && m_bp_site_sp) {
-    Address resolved_address;
-    resolved_address.SetLoadAddress(m_bp_site_sp->GetLoadAddress(), target);
-    Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
-    if (resolved_symbol) {
-      if (level == eDescriptionLevelFull || level == eDescriptionLevelInitial)
-        s->Printf(", ");
-      else if (level == lldb::eDescriptionLevelVerbose) {
-        s->EOL();
-        s->Indent();
+  if (!is_scripted_desc) {
+    if (m_address.IsSectionOffset() &&
+        (level == eDescriptionLevelFull || level == eDescriptionLevelInitial))
+      s->Printf(", ");
+    s->Printf("address = ");
+
+    ExecutionContextScope *exe_scope = nullptr;
+    Target *target = &m_owner.GetTarget();
+    if (target)
+      exe_scope = target->GetProcessSP().get();
+    if (exe_scope == nullptr)
+      exe_scope = target;
+
+    if (level == eDescriptionLevelInitial)
+      m_address.Dump(s, exe_scope, Address::DumpStyleLoadAddress,
+                     Address::DumpStyleFileAddress);
+    else
+      m_address.Dump(s, exe_scope, Address::DumpStyleLoadAddress,
+                     Address::DumpStyleModuleWithFileAddress);
+
+    if (IsIndirect() && m_bp_site_sp) {
+      Address resolved_address;
+      resolved_address.SetLoadAddress(m_bp_site_sp->GetLoadAddress(), target);
+      Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
+      if (resolved_symbol) {
+        if (level == eDescriptionLevelFull || level == eDescriptionLevelInitial)
+          s->Printf(", ");
+        else if (level == lldb::eDescriptionLevelVerbose) {
+          s->EOL();
+          s->Indent();
+        }
+        s->Printf("indirect target = %s",
+                  resolved_symbol->GetName().GetCString());
       }
-      s->Printf("indirect target = %s",
-                resolved_symbol->GetName().GetCString());
     }
   }
 
-  bool is_resolved = IsResolved();
-  bool is_hardware = is_resolved && m_bp_site_sp->IsHardware();
+  // Scripted breakpoint are currently always resolved.  Does this seem right?
+  // If they don't add any scripted locations, we shouldn't consider them
+  // resolved.
+  bool is_resolved = is_scripted_desc || IsResolved();
+  // A scripted breakpoint might be resolved but not have a site.  Be sure to 
+  // check for that.
+  bool is_hardware = !is_scripted_desc && IsResolved() && m_bp_site_sp 
+      && m_bp_site_sp->IsHardware();
 
   if (level == lldb::eDescriptionLevelVerbose) {
     s->EOL();
@@ -717,9 +812,9 @@ void BreakpointLocation::SwapLocation(BreakpointLocationSP swap_from) {
 }
 
 void BreakpointLocation::SetThreadIDInternal(lldb::tid_t thread_id) {
-  if (thread_id != LLDB_INVALID_THREAD_ID)
+  if (thread_id != LLDB_INVALID_THREAD_ID) {
     GetLocationOptions().SetThreadID(thread_id);
-  else {
+  } else {
     // If we're resetting this to an invalid thread id, then don't make an
     // options pointer just to do that.
     if (m_options_up != nullptr)
diff --git a/lldb/source/Breakpoint/BreakpointLocationCollection.cpp b/lldb/source/Breakpoint/BreakpointLocationCollection.cpp
index 81bec0bd7583d..1d052c5fc9bb6 100644
--- a/lldb/source/Breakpoint/BreakpointLocationCollection.cpp
+++ b/lldb/source/Breakpoint/BreakpointLocationCollection.cpp
@@ -115,7 +115,8 @@ BreakpointLocationCollection::GetByIndex(size_t i) const {
 }
 
 bool BreakpointLocationCollection::ShouldStop(
-    StoppointCallbackContext *context) {
+    StoppointCallbackContext *context,
+    BreakpointLocationCollection &stopped_bp_locs) {
   bool shouldStop = false;
   size_t i = 0;
   size_t prev_size = GetSize();
@@ -123,9 +124,20 @@ bool BreakpointLocationCollection::ShouldStop(
     // ShouldStop can remove the breakpoint from the list, or even delete
     // it, so we should
     BreakpointLocationSP cur_loc_sp = GetByIndex(i);
+    BreakpointLocationSP reported_loc_sp;
     BreakpointSP keep_bkpt_alive_sp = cur_loc_sp->GetBreakpoint().shared_from_this();
-    if (cur_loc_sp->ShouldStop(context))
+    // We're building up the list or which locations claim responsibility for
+    // this stop.  If the location's ShouldStop defers to a facade location by
+    // returning a non-null reported location, we want to use that.  Otherwise
+    // use the original location.
+    if (cur_loc_sp->ShouldStop(context, reported_loc_sp)) {
+      if (reported_loc_sp)
+        stopped_bp_locs.Add(reported_loc_sp);
+      else
+        stopped_bp_locs.Add(cur_loc_sp);
+
       shouldStop = true;
+    }
 
     if (prev_size == GetSize())
       i++;
diff --git a/lldb/source/Breakpoint/BreakpointLocationList.cpp b/lldb/source/Breakpoint/BreakpointLocationList.cpp
index 44d1eb5bf7140..5a791d4364641 100644
--- a/lldb/source/Breakpoint/BreakpointLocationList.cpp
+++ b/lldb/source/Breakpoint/BreakpointLocationList.cpp
@@ -32,22 +32,23 @@ BreakpointLocationList::Create(const Address &addr,
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
   // The location ID is just the size of the location list + 1
   lldb::break_id_t bp_loc_id = ++m_next_id;
-  BreakpointLocationSP bp_loc_sp(
-      new BreakpointLocation(bp_loc_id, m_owner, addr, LLDB_INVALID_THREAD_ID,
-                             resolve_indirect_symbols));
+  BreakpointLocationSP bp_loc_sp(new 
+      BreakpointLocation(bp_loc_id, m_owner, addr, LLDB_INVALID_THREAD_ID, 
+      resolve_indirect_symbols));
   m_locations.push_back(bp_loc_sp);
   m_address_to_location[addr] = bp_loc_sp;
   return bp_loc_sp;
 }
 
 bool BreakpointLocationList::ShouldStop(StoppointCallbackContext *context,
-                                        lldb::break_id_t break_id) {
+                                        lldb::break_id_t break_id,
+                                        lldb::BreakpointLocationSP &bp_loc_sp) {
   BreakpointLocationSP bp = FindByID(break_id);
   if (bp) {
     // Let the BreakpointLocation decide if it should stop here (could not have
     // reached it's target hit count yet, or it could have a callback that
     // decided it shouldn't stop (shared library loads/unloads).
-    return bp->ShouldStop(context);
+    return bp->ShouldStop(context, bp_loc_sp);
   }
   // We should stop here since this BreakpointLocation isn't valid anymore or
   // it doesn't exist.
diff --git a/lldb/source/Breakpoint/BreakpointResolverScripted.cpp b/lldb/source/Breakpoint/BreakpointResolverScripted.cpp
index 701cabae3ee97..b1e45d761735b 100644
--- a/lldb/source/Breakpoint/BreakpointResolverScripted.cpp
+++ b/lldb/source/Breakpoint/BreakpointResolverScripted.cpp
@@ -50,7 +50,9 @@ void BreakpointResolverScripted::CreateImplementationIfNeeded(
   if (!script_interp)
     return;
 
-  m_interface_sp = script_interp->CreateScriptedBreakpointInterface();
+  if (!m_interface_sp)
+    m_interface_sp = script_interp->CreateScriptedBreakpointInterface();
+
   if (!m_interface_sp) {
     m_error = Status::FromErrorStringWithFormat(
         "BreakpointResolverScripted::%s () - ERROR: %s", __FUNCTION__,
@@ -61,6 +63,7 @@ void BreakpointResolverScripted::CreateImplementationIfNeeded(
   auto obj_or_err =
       m_interface_sp->CreatePluginObject(m_class_name, breakpoint_sp, m_args);
   if (!obj_or_err) {
+    m_interface_sp.reset();
     m_error = Status::FromError(obj_or_err.takeError());
     return;
   }
@@ -146,6 +149,8 @@ void BreakpointResolverScripted::GetDescription(Stream *s) {
   StructuredData::GenericSP generic_sp;
   std::optional<std::string> short_help;
 
+  CreateImplementationIfNeeded(GetBreakpoint());
+
   if (m_interface_sp) {
     short_help = m_interface_sp->GetShortHelp();
   }
@@ -155,6 +160,23 @@ void BreakpointResolverScripted::GetDescription(Stream *s) {
     s->Printf("python class = %s", m_class_name.c_str());
 }
 
+std::optional<std::string> 
+BreakpointResolverScripted::GetLocationDescription(
+    lldb::BreakpointLocationSP bp_loc_sp, lldb::DescriptionLevel level) {
+  CreateImplementationIfNeeded(GetBreakpoint());
+  if (m_interface_sp) 
+    return m_interface_sp->GetLocationDescription(bp_loc_sp, level);
+  return {};
+}
+
+lldb::BreakpointLocationSP
+BreakpointResolverScripted::WasHit(lldb::StackFrameSP frame_sp, lldb::BreakpointLocationSP bp_loc_sp) {
+  if (m_interface_sp)
+    return m_interface_sp->WasHit(frame_sp, bp_loc_sp);
+
+  return {};
+}
+
 void BreakpointResolverScripted::Dump(Stream *s) const {}
 
 lldb::BreakpointResolverSP
diff --git a/lldb/source/Breakpoint/BreakpointSite.cpp b/lldb/source/Breakpoint/BreakpointSite.cpp
index d430e3de788f0..52100b91de09c 100644
--- a/lldb/source/Breakpoint/BreakpointSite.cpp
+++ b/lldb/source/Breakpoint/BreakpointSite.cpp
@@ -45,7 +45,8 @@ break_id_t BreakpointSite::GetNextID() {
 // RETURNS - true if we should stop at this breakpoint, false if we
 // should continue.
 
-bool BreakpointSite::ShouldStop(StoppointCallbackContext *context) {
+bool BreakpointSite::ShouldStop(StoppointCallbackContext *context,
+        BreakpointLocationCollection &stopping_bp_locs) {
   m_hit_counter.Increment();
   // ShouldStop can do a lot of work, and might even come back and hit
   // this breakpoint site again.  So don't hold the m_constituents_mutex the
@@ -56,7 +57,7 @@ bool BreakpointSite::ShouldStop(StoppointCallbackContext *context) {
     std::lock_guard<std::recursive_mutex> guard(m_constituents_mutex);
     constituents_copy = m_constituents;
   }
-  return constituents_copy.ShouldStop(context);
+  return constituents_copy.ShouldStop(context, stopping_bp_locs);
 }
 
 bool BreakpointSite::IsBreakpointAtThisSite(lldb::break_id_t bp_id) {
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 6a654a0dafe5b..51475db27269b 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -81,6 +81,11 @@ lldb::BreakpointSP ScriptInterpreter::GetOpaqueTypeFromSBBreakpoint(
   return breakpoint.m_opaque_wp.lock();
 }
 
+lldb::BreakpointLocationSP ScriptInterpreter::GetOpaqueTypeFromSBBreakpointLocation(
+    const lldb::SBBreakpointLocation &break_loc) const {
+  return break_loc.m_opaque_wp.lock();
+}
+
 lldb::ProcessAttachInfoSP ScriptInterpreter::GetOpaqueTypeFromSBAttachInfo(
     const lldb::SBAttachInfo &attach_info) const {
   return attach_info.m_opaque_sp;
@@ -100,6 +105,14 @@ ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const {
   return Status();
 }
 
+lldb::StackFrameSP
+ScriptInterpreter::GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const {
+  if (frame.m_opaque_sp) {
+    return frame.m_opaque_sp->GetFrameSP();
+  }
+  return nullptr;
+}
+
 Event *
 ScriptInterpreter::GetOpaqueTypeFromSBEvent(const lldb::SBEvent &event) const {
   return event.m_opaque_ptr;
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp
index 660edaa0191f0..0c6d23b108fa1 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp
@@ -81,6 +81,32 @@ std::optional<std::string> ScriptedBreakpointPythonInterface::GetShortHelp() {
   return obj->GetAsString()->GetValue().str();
 }
 
+lldb::BreakpointLocationSP
+ScriptedBreakpointPythonInterface::WasHit(lldb::StackFrameSP frame_sp, lldb::BreakpointLocationSP bp_loc_sp) {
+  Status py_error;
+  lldb::BreakpointLocationSP loc_sp 
+    = Dispatch<lldb::BreakpointLocationSP>("was_hit", py_error, frame_sp, bp_loc_sp);
+
+  if (py_error.Fail())
+    return bp_loc_sp;
+
+  return loc_sp;
+}
+
+std::optional<std::string> 
+ScriptedBreakpointPythonInterface::GetLocationDescription(
+    lldb::BreakpointLocationSP bp_loc_sp, 
+    lldb::DescriptionLevel level) {
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("get_location_description", error, bp_loc_sp, level);
+
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return {};
+
+  return obj->GetAsString()->GetValue().str();
+}
+
 void ScriptedBreakpointPythonInterface::Initialize() {
   const std::vector<llvm::StringRef> ci_usages = {
       "breakpoint set -P classname [-k key -v value ...]"};
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.h
index 27bdd8718ac0c..b8c1219279876 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.h
@@ -36,6 +36,12 @@ class ScriptedBreakpointPythonInterface : public ScriptedBreakpointInterface,
   bool ResolverCallback(SymbolContext sym_ctx) override;
   lldb::SearchDepth GetDepth() override;
   std::optional<std::string> GetShortHelp() override;
+  lldb::BreakpointLocationSP WasHit(lldb::StackFrameSP frame_sp, 
+                  lldb::BreakpointLocationSP bp_loc_sp) override;
+  virtual std::optional<std::string> 
+      GetLocationDescription(lldb::BreakpointLocationSP bp_loc_sp, 
+                             lldb::DescriptionLevel level) override;
+
 
   static void Initialize();
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index 8083ccae04026..cda8185ef0a17 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -80,6 +80,19 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
   return nullptr;
 }
 
+template <>
+lldb::StackFrameSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
+    python::PythonObject &p, Status &error) {
+  if (lldb::SBFrame *sb_frame = reinterpret_cast<lldb::SBFrame *>(
+          python::LLDBSWIGPython_CastPyObjectToSBFrame(p.get())))
+    return m_interpreter.GetOpaqueTypeFromSBFrame(*sb_frame);
+  error = Status::FromErrorString(
+      "Couldn't cast lldb::SBFrame to lldb_private::StackFrame.");
+
+  return nullptr;
+}
+
 template <>
 SymbolContext
 ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>(
@@ -126,6 +139,22 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
   return m_interpreter.GetOpaqueTypeFromSBBreakpoint(*sb_breakpoint);
 }
 
+template <>
+lldb::BreakpointLocationSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointLocationSP>(
+    python::PythonObject &p, Status &error) {
+  lldb::SBBreakpointLocation *sb_break_loc = reinterpret_cast<lldb::SBBreakpointLocation *>(
+      python::LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(p.get()));
+
+  if (!sb_break_loc) {
+    error = Status::FromErrorStringWithFormat(
+        "Couldn't cast lldb::SBBreakpointLocation to lldb::BreakpointLocationSP.");
+    return nullptr;
+  }
+
+  return m_interpreter.GetOpaqueTypeFromSBBreakpointLocation(*sb_break_loc);
+}
+
 template <>
 lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
     lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error) {
@@ -194,4 +223,22 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<
   return m_interpreter.GetOpaqueTypeFromSBExecutionContext(*sb_exe_ctx);
 }
 
+template <>
+lldb::DescriptionLevel
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    lldb::DescriptionLevel>(python::PythonObject &p, Status &error) {
+    lldb::DescriptionLevel ret_val = lldb::eDescriptionLevelBrief;
+    llvm::Expected<unsigned long long> unsigned_or_err = p.AsUnsignedLongLong();
+    if (!unsigned_or_err) {
+      error = (Status::FromError(unsigned_or_err.takeError()));
+      return ret_val;
+    }
+    unsigned long long unsigned_val = *unsigned_or_err;
+    if (unsigned_val >= lldb::DescriptionLevel::kNumDescriptionLevels) {
+      error = Status("value too large for lldb::DescriptionLevel.");
+      return ret_val;
+    }
+    return static_cast<lldb::DescriptionLevel>(unsigned_val);
+}
+
 #endif
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index f769d3d29add7..57d22d44e565e 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -436,6 +436,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     return python::SWIGBridge::ToSWIGWrapper(arg);
   }
 
+  python::PythonObject Transform(lldb::BreakpointLocationSP arg) {
+    return python::SWIGBridge::ToSWIGWrapper(arg);
+  }
+
   python::PythonObject Transform(lldb::ProcessSP arg) {
     return python::SWIGBridge::ToSWIGWrapper(arg);
   }
@@ -464,10 +468,18 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     return python::SWIGBridge::ToSWIGWrapper(arg.get());
   }
 
+  python::PythonObject Transform(lldb::StackFrameSP arg) {
+    return python::SWIGBridge::ToSWIGWrapper(arg);
+  }
+
   python::PythonObject Transform(lldb::DataExtractorSP arg) {
     return python::SWIGBridge::ToSWIGWrapper(arg);
   }
 
+  python::PythonObject Transform(lldb::DescriptionLevel arg) {
+    return python::SWIGBridge::ToSWIGWrapper(arg);
+  }
+
   template <typename T, typename U>
   void ReverseTransform(T &original_arg, U transformed_arg, Status &error) {
     // If U is not a PythonObject, don't touch it!
@@ -573,11 +585,21 @@ lldb::StreamSP
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
     python::PythonObject &p, Status &error);
 
+template <>
+lldb::StackFrameSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
+    python::PythonObject &p, Status &error);
+
 template <>
 lldb::BreakpointSP
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
     python::PythonObject &p, Status &error);
 
+template <>
+lldb::BreakpointLocationSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointLocationSP>(
+    python::PythonObject &p, Status &error);
+
 template <>
 lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
     lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error);
@@ -601,6 +623,11 @@ lldb::ExecutionContextRefSP
 ScriptedPythonInterface::ExtractValueFromPythonObject<
     lldb::ExecutionContextRefSP>(python::PythonObject &p, Status &error);
 
+template <>
+lldb::DescriptionLevel
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    lldb::DescriptionLevel>(python::PythonObject &p, Status &error);
+
 } // namespace lldb_private
 
 #endif // LLDB_ENABLE_PYTHON
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 413778639c297..7b39d29ba2b20 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -107,6 +107,7 @@ class SWIGBridge {
   static PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp);
   static PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp);
   static PythonObject ToSWIGWrapper(lldb::DataExtractorSP data_extractor_sp);
+  static PythonObject ToSWIGWrapper(lldb::DescriptionLevel level);
 
   static PythonObject
   ToSWIGWrapper(std::unique_ptr<lldb::SBStructuredData> data_sb);
@@ -256,11 +257,13 @@ class SWIGBridge {
 
 void *LLDBSWIGPython_CastPyObjectToSBData(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data);
diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp
index f47dae2b2465d..b864159ba7471 100644
--- a/lldb/source/Target/StopInfo.cpp
+++ b/lldb/source/Target/StopInfo.cpp
@@ -157,7 +157,7 @@ class StopInfoBreakpoint : public StopInfo {
           ExecutionContext exe_ctx(thread_sp->GetStackFrameAtIndex(0));
           StoppointCallbackContext context(event_ptr, exe_ctx, true);
           bp_site_sp->BumpHitCounts();
-          m_should_stop = bp_site_sp->ShouldStop(&context);
+          m_should_stop = bp_site_sp->ShouldStop(&context, m_async_stopped_locs);
         } else {
           Log *log = GetLog(LLDBLog::Process);
 
@@ -180,6 +180,7 @@ class StopInfoBreakpoint : public StopInfo {
   }
 
   const char *GetDescription() override {
+    // FIXME: only print m_async_stopped_locs.
     if (m_description.empty()) {
       ThreadSP thread_sp(m_thread_wp.lock());
       if (thread_sp) {
@@ -202,7 +203,7 @@ class StopInfoBreakpoint : public StopInfo {
           }
 
           strm.Printf("breakpoint ");
-          bp_site_sp->GetDescription(&strm, eDescriptionLevelBrief);
+          m_async_stopped_locs.GetDescription(&strm, eDescriptionLevelBrief);
           m_description = std::string(strm.GetString());
         } else {
           StreamString strm;
@@ -244,6 +245,12 @@ class StopInfoBreakpoint : public StopInfo {
   }
 
   uint32_t GetStopReasonDataCount() const override {
+    size_t num_async_locs = m_async_stopped_locs.GetSize();
+    // If we have async locations, they are the ones we should report:
+    if (num_async_locs > 0)
+      return num_async_locs * 2;
+
+    // Otherwise report the number of locations at this breakpoint's site.
     lldb::BreakpointSiteSP bp_site_sp = GetBreakpointSiteSP();
     if (bp_site_sp)
       return bp_site_sp->GetNumberOfConstituents() * 2;
@@ -251,22 +258,25 @@ class StopInfoBreakpoint : public StopInfo {
   }
 
   uint64_t GetStopReasonDataAtIndex(uint32_t idx) override {
-    lldb::BreakpointSiteSP bp_site_sp = GetBreakpointSiteSP();
-    if (bp_site_sp) {
-      uint32_t bp_index = idx / 2;
-      BreakpointLocationSP bp_loc_sp(
-          bp_site_sp->GetConstituentAtIndex(bp_index));
-      if (bp_loc_sp) {
-        if (idx & 1) {
-          // FIXME: This might be a Facade breakpoint, so we need to fetch
-          // the one that the thread actually hit, not the native loc ID.
-
-          // Odd idx, return the breakpoint location ID
-          return bp_loc_sp->GetID();
-        } else {
-          // Even idx, return the breakpoint ID
-          return bp_loc_sp->GetBreakpoint().GetID();
-        }
+    uint32_t bp_index = idx / 2;
+    BreakpointLocationSP loc_to_report_sp;
+
+    size_t num_async_locs = m_async_stopped_locs.GetSize();
+    if (num_async_locs > 0) {
+      // GetByIndex returns an empty SP if we ask past its contents:
+      loc_to_report_sp = m_async_stopped_locs.GetByIndex(bp_index);
+    } else {
+      lldb::BreakpointSiteSP bp_site_sp = GetBreakpointSiteSP();
+      if (bp_site_sp)
+        loc_to_report_sp = bp_site_sp->GetConstituentAtIndex(bp_index);
+    }
+    if (loc_to_report_sp) {
+      if (idx & 1) {
+        // Odd idx, return the breakpoint location ID
+        return loc_to_report_sp->GetID();
+      } else {
+        // Even idx, return the breakpoint ID
+        return loc_to_report_sp->GetBreakpoint().GetID();
       }
     }
     return LLDB_INVALID_BREAK_ID;
@@ -336,7 +346,7 @@ class StopInfoBreakpoint : public StopInfo {
         // site, then we won't be operating on a bad list.
         BreakpointLocationCollection site_locations;
         size_t num_constituents =
-            bp_site_sp->CopyConstituentsList(site_locations);
+            m_async_stopped_locs.GetSize();
 
         if (num_constituents == 0) {
           m_should_stop = true;
@@ -435,17 +445,28 @@ class StopInfoBreakpoint : public StopInfo {
           // breakpoints, and the locations don't keep their constituents alive.
           // I'm just sticking the BreakpointSP's in a vector since I'm only
           // using it to locally increment their retain counts.
-
+          
+          // We are holding onto the breakpoint locations that were hit
+          // by this stop info between the "synchonous" ShouldStop and now.
+          // But an intervening action might have deleted one of the breakpoints
+          // we hit before we get here.  So at the same time let's build a list
+          // of the still valid locations:
           std::vector<lldb::BreakpointSP> location_constituents;
 
+          BreakpointLocationCollection valid_locs;
           for (size_t j = 0; j < num_constituents; j++) {
-            BreakpointLocationSP loc(site_locations.GetByIndex(j));
-            location_constituents.push_back(
-                loc->GetBreakpoint().shared_from_this());
+            BreakpointLocationSP loc_sp(m_async_stopped_locs.GetByIndex(j));
+            if (loc_sp->IsValid()) {
+              location_constituents.push_back(
+                  loc_sp->GetBreakpoint().shared_from_this());
+              valid_locs.Add(loc_sp);
+            }
           }
 
-          for (size_t j = 0; j < num_constituents; j++) {
-            lldb::BreakpointLocationSP bp_loc_sp = site_locations.GetByIndex(j);
+          size_t num_valid_locs = valid_locs.GetSize();
+          for (size_t j = 0; j < num_valid_locs; j++) {
+            lldb::BreakpointLocationSP bp_loc_sp 
+                = valid_locs.GetByIndex(j);
             StreamString loc_desc;
             if (log) {
               bp_loc_sp->GetDescription(&loc_desc, eDescriptionLevelBrief);
@@ -679,6 +700,7 @@ class StopInfoBreakpoint : public StopInfo {
   lldb::break_id_t m_break_id;
   bool m_was_all_internal;
   bool m_was_one_shot;
+  BreakpointLocationCollection m_async_stopped_locs;
 };
 
 // StopInfoWatchpoint
diff --git a/lldb/test/API/functionalities/breakpoint/scripted_bkpt/resolver.py b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/resolver.py
index 85c7340127615..95868486b8cba 100644
--- a/lldb/test/API/functionalities/breakpoint/scripted_bkpt/resolver.py
+++ b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/resolver.py
@@ -51,7 +51,6 @@ def __callback__(self, sym_ctx):
     def get_short_help(self):
         return "I am a python breakpoint resolver"
 
-
 class ResolverModuleDepth(Resolver):
     def __get_depth__(self):
         return lldb.eSearchDepthModule
diff --git a/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/Makefile b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/Makefile
new file mode 100644
index 0000000000000..695335e068c0c
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -std=c99
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/TestWasHit.py b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/TestWasHit.py
new file mode 100644
index 0000000000000..de178f5b04fc7
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/TestWasHit.py
@@ -0,0 +1,85 @@
+"""
+Test the WasHit feature of scripted breakpoints
+"""
+
+import os
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestWasHit(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
+    def test_was_hit_resolver(self):
+        """Use facade breakpoints to emulate hitting some locations"""
+        self.build()
+        self.do_test()
+
+    def make_target_and_import(self):
+        target = lldbutil.run_to_breakpoint_make_target(self)
+        self.import_resolver_script()
+        return target
+
+    def import_resolver_script(self):
+        interp = self.dbg.GetCommandInterpreter()
+        error = lldb.SBError()
+
+        script_name = os.path.join(self.getSourceDir(), "bkpt_resolver.py")
+
+        command = "command script import " + script_name
+        self.runCmd(command)
+
+    def make_extra_args(self, sym_name, num_locs, loc_to_miss):
+        return f' -k symbol -v {sym_name} -k num_locs -v {num_locs} -k loc_to_miss -v {loc_to_miss} '
+
+    def do_test(self):
+        """This reads in a python file and sets a breakpoint using it."""
+
+        target = self.make_target_and_import()
+        extra_args = self.make_extra_args("stop_symbol", 4, 2)
+
+        bkpt_no = lldbutil.run_break_set_by_script(self, "bkpt_resolver.FacadeExample", extra_args, 4)
+
+        # Make sure the help text shows up in the "break list" output:
+        self.expect(
+            "break list",
+            substrs=["I am a facade resolver - sym: stop_symbol - num_locs: 4"],
+            msg="Help is listed in break list",
+        )
+
+        bkpt = target.FindBreakpointByID(bkpt_no)
+        self.assertTrue(bkpt.IsValid(), "Found the right breakpoint")
+        
+        # Now continue.  We should hit locations 1, 3 and 4:
+        (target, process, thread, bkpt) = lldbutil.run_to_breakpoint_do_run(self, target, bkpt)
+        # This location should be bkpt_no.1:
+        self.assertEqual(thread.stop_reason_data[0], bkpt_no, "Hit the right breakpoint")
+        self.assertEqual(thread.stop_reason_data[1], 1, "First location hit is 1")
+
+        for loc in [3, 4]:
+            process.Continue()
+            self.assertEqual(thread.stop_reason, lldb.eStopReasonBreakpoint, "Hit breakpoint")
+            self.assertEqual(thread.stop_reason_data[0], bkpt_no, "Hit the right breakpoint")
+            self.assertEqual(thread.stop_reason_data[1], loc, f"Hit the right location: {loc}")
+
+        # At this point we should have hit three of the four locations, and not location 1.2.
+        # Check that that is true, and that the descriptions for the location are the ones
+        # the resolver provided.
+        self.assertEqual(bkpt.hit_count, 3, "Hit three locations")
+        for loc_id in range(1,4):
+            bkpt_loc = bkpt.FindLocationByID(loc_id)
+            self.assertTrue(bkpt_loc.IsValid(), f"{loc_id} was invalid.")
+            if loc_id != 2:
+                self.assertEqual(bkpt_loc.hit_count, 1, f"Loc {loc_id} hit count was wrong")
+            else:
+                self.assertEqual(bkpt_loc.hit_count, 0, "We didn't skip loc 2")
+            stream = lldb.SBStream()
+            self.assertTrue(bkpt_loc.GetDescription(stream, lldb.eDescriptionLevelFull),
+                            f"Didn't get description for {loc_id}")
+            self.assertIn(f"Location index: {loc_id}", stream.GetData(),
+                             f"Wrong desciption for {loc_id}")
+
+                            
diff --git a/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/bkpt_resolver.py b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/bkpt_resolver.py
new file mode 100644
index 0000000000000..004ee18513f2f
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/bkpt_resolver.py
@@ -0,0 +1,45 @@
+import lldb
+
+class FacadeExample:
+    def __init__(self, bkpt, extra_args, dict):
+        self.bkpt = bkpt
+        self.extra_args = extra_args
+        self.base_sym = None
+        self.facade_locs = []
+        self.facade_locs_desc = []
+        self.cur_facade_loc = 1
+
+        self.sym_name = extra_args.GetValueForKey("symbol").GetStringValue(100)
+        self.num_locs = extra_args.GetValueForKey("num_locs").GetIntegerValue(5)
+        self.loc_to_miss = extra_args.GetValueForKey("loc_to_miss").GetIntegerValue(10000)
+
+    def __callback__(self, sym_ctx):
+        self.base_sym = sym_ctx.module.FindSymbol(self.sym_name, lldb.eSymbolTypeCode)
+        if self.base_sym.IsValid():
+            self.bkpt.AddLocation(self.base_sym.GetStartAddress())
+            # Locations are 1 based, so to keep things simple, I'm making
+            # the array holding locations 1 based as well:
+            self.facade_locs_desc.append("This is the zero index, you shouldn't see this")
+            self.facade_locs.append(None)
+            for i in range(1, self.num_locs + 1):
+                self.facade_locs_desc.append(f"Location index: {i}")
+                self.facade_locs.append(self.bkpt.AddFacadeLocation())
+                
+    def get_short_help(self):
+        return f"I am a facade resolver - sym: {self.sym_name} - num_locs: {self.num_locs} - locs_to_miss: {self.loc_to_miss}"
+
+    def was_hit(self, frame, bp_loc):
+        tmp_loc = self.cur_facade_loc
+
+        self.cur_facade_loc = (self.cur_facade_loc + 1)
+        if self.cur_facade_loc == 5:
+          self.cur_facade_loc = 1
+
+        if tmp_loc == self.loc_to_miss:
+            return None
+
+        return self.facade_locs[tmp_loc]
+
+    def get_location_description(self, bp_loc, desc_level):
+        return self.facade_locs_desc[bp_loc.id]
+
diff --git a/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/main.c b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/main.c
new file mode 100644
index 0000000000000..762553109f7f7
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/scripted_bkpt/was_hit/main.c
@@ -0,0 +1,17 @@
+#include <stdio.h>
+
+int
+stop_symbol() {
+  static int s_cnt = 0;
+  printf("I am in the stop symbol: %d\n", s_cnt++);
+  return s_cnt;
+}
+
+int
+main()
+{
+  for (int i = 0; i < 100; i++) {
+    stop_symbol();
+  }
+  return 0;
+}
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index 068860ebc20f1..6f5d9fd97ee28 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -105,6 +105,11 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(
   return nullptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(
+    PyObject *data) {
+  return nullptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBAttachInfo(
     PyObject *data) {
   return nullptr;
@@ -130,6 +135,11 @@ lldb_private::python::LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data) {
   return nullptr;
 }
 
+void *
+lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data) {
+  return nullptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBSymbolContext(
     PyObject *data) {
   return nullptr;



More information about the lldb-commits mailing list