Skip to content

release/22.x: [lldb] Backport FrameProviders fixes & improvements#180071

Merged
c-rhodes merged 7 commits intollvm:release/22.xfrom
llvmbot:issue172849
Feb 9, 2026
Merged

release/22.x: [lldb] Backport FrameProviders fixes & improvements#180071
c-rhodes merged 7 commits intollvm:release/22.xfrom
llvmbot:issue172849

Conversation

@llvmbot
Copy link
Member

@llvmbot llvmbot commented Feb 5, 2026

@llvmbot
Copy link
Member Author

llvmbot commented Feb 5, 2026

@JDevlieghere @medismailben @bzcheeseman @medismailben @medismailben @jimingham @vogelsgesang What do you think about merging this PR to the release branch?

@llvmbot
Copy link
Member Author

llvmbot commented Feb 5, 2026

@llvm/pr-subscribers-lldb

Author: None (llvmbot)

Changes

Backport 17b01bb cd70e2d 58f623c 8122d0e 10f2611 c373d76 943782b

Requested by: @medismailben


Patch is 101.10 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/180071.diff

32 Files Affected:

  • (modified) lldb/bindings/python/python-wrapper.swig (+12)
  • (modified) lldb/include/lldb/API/SBValue.h (+6-4)
  • (modified) lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h (+9)
  • (modified) lldb/include/lldb/Interpreter/ScriptInterpreter.h (+3)
  • (modified) lldb/include/lldb/Target/ExecutionContext.h (+11-8)
  • (modified) lldb/include/lldb/Target/StackFrame.h (+6-6)
  • (modified) lldb/include/lldb/Target/StackFrameList.h (+14-2)
  • (modified) lldb/include/lldb/Target/Target.h (+6)
  • (modified) lldb/include/lldb/Target/Thread.h (+30-4)
  • (modified) lldb/include/lldb/ValueObject/ValueObject.h (+78)
  • (modified) lldb/include/lldb/lldb-defines.h (+1)
  • (modified) lldb/include/lldb/lldb-types.h (+1)
  • (modified) lldb/source/API/SBValue.cpp (-166)
  • (modified) lldb/source/Interpreter/ScriptInterpreter.cpp (+10)
  • (modified) lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp (+66)
  • (modified) lldb/source/Plugins/Process/scripted/ScriptedFrame.h (+21)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp (+28)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h (+6)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp (+38)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h (+14)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h (+1)
  • (modified) lldb/source/Target/ExecutionContext.cpp (+56-18)
  • (modified) lldb/source/Target/StackFrameList.cpp (+35-16)
  • (modified) lldb/source/Target/Target.cpp (+36-23)
  • (modified) lldb/source/Target/Thread.cpp (+117-34)
  • (modified) lldb/source/ValueObject/ValueObject.cpp (+91)
  • (modified) lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py (+549)
  • (modified) lldb/test/API/functionalities/scripted_frame_provider/main.cpp (+34)
  • (modified) lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py (+160)
  • (added) lldb/test/API/macosx/extended-backtrace-api/Makefile (+3)
  • (added) lldb/test/API/macosx/extended-backtrace-api/TestExtendedBacktraceAPI.py (+144)
  • (added) lldb/test/API/macosx/extended-backtrace-api/main.m (+53)
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 0ba152166522b..bf59569920470 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -545,6 +545,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBValue(PyObject * data
   return sb_ptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBValueList(PyObject * data) {
+  lldb::SBValueList *sb_ptr = NULL;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBValueList, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *
                                                                     data) {
   lldb::SBMemoryRegionInfo *sb_ptr = NULL;
diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h
index dead11fba19fe..d4cc2f05c39e3 100644
--- a/lldb/include/lldb/API/SBValue.h
+++ b/lldb/include/lldb/API/SBValue.h
@@ -13,10 +13,9 @@
 #include "lldb/API/SBDefines.h"
 #include "lldb/API/SBType.h"
 
+namespace lldb_private {
 class ValueImpl;
 class ValueLocker;
-
-namespace lldb_private {
 namespace python {
 class SWIGBridge;
 }
@@ -490,7 +489,7 @@ class LLDB_API SBValue {
   /// \return
   ///     A ValueObjectSP of the best kind (static, dynamic or synthetic) we
   ///     can cons up, in accordance with the SBValue's settings.
-  lldb::ValueObjectSP GetSP(ValueLocker &value_locker) const;
+  lldb::ValueObjectSP GetSP(lldb_private::ValueLocker &value_locker) const;
 
   // these calls do the right thing WRT adjusting their settings according to
   // the target's preferences
@@ -506,8 +505,11 @@ class LLDB_API SBValue {
   void SetSP(const lldb::ValueObjectSP &sp, lldb::DynamicValueType use_dynamic,
              bool use_synthetic, const char *name);
 
+protected:
+  friend class lldb_private::ScriptInterpreter;
+
 private:
-  typedef std::shared_ptr<ValueImpl> ValueImplSP;
+  typedef std::shared_ptr<lldb_private::ValueImpl> ValueImplSP;
   ValueImplSP m_opaque_sp;
 
   void SetSP(ValueImplSP impl_sp);
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
index 8ef4b37d6ba12..00994d65fd601 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
@@ -10,6 +10,7 @@
 #define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEINTERFACE_H
 
 #include "ScriptedInterface.h"
+#include "lldb/API/SBValueList.h"
 #include "lldb/Core/StructuredDataImpl.h"
 #include "lldb/Symbol/SymbolContext.h"
 #include "lldb/lldb-private.h"
@@ -49,6 +50,14 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
   virtual std::optional<std::string> GetRegisterContext() {
     return std::nullopt;
   }
+
+  virtual lldb::ValueObjectListSP GetVariables() { return nullptr; }
+
+  virtual lldb::ValueObjectSP
+  GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
+                                      Status &error) {
+    return nullptr;
+  }
 };
 } // namespace lldb_private
 
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 0b91d6756552d..557d73a415452 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -609,6 +609,9 @@ class ScriptInterpreter : public PluginInterface {
   lldb::StackFrameListSP
   GetOpaqueTypeFromSBFrameList(const lldb::SBFrameList &exe_ctx) const;
 
+  lldb::ValueObjectSP
+  GetOpaqueTypeFromSBValue(const lldb::SBValue &value) const;
+
 protected:
   Debugger &m_debugger;
   lldb::ScriptLanguage m_script_lang;
diff --git a/lldb/include/lldb/Target/ExecutionContext.h b/lldb/include/lldb/Target/ExecutionContext.h
index 8637234c4fb95..47bcd729abcdd 100644
--- a/lldb/include/lldb/Target/ExecutionContext.h
+++ b/lldb/include/lldb/Target/ExecutionContext.h
@@ -13,10 +13,13 @@
 
 #include "lldb/Host/ProcessRunLock.h"
 #include "lldb/Target/StackID.h"
+#include "lldb/Target/SyntheticFrameProvider.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
 
+struct StoppedExecutionContext;
+
 //===----------------------------------------------------------------------===//
 /// Execution context objects refer to objects in the execution of the program
 /// that is being debugged. The consist of one or more of the following
@@ -270,9 +273,12 @@ class ExecutionContextRef {
 
   void ClearFrame() {
     m_stack_id.Clear();
-    m_frame_list_wp.reset();
+    m_frame_list_id.reset();
   }
 
+  friend llvm::Expected<StoppedExecutionContext>
+  GetStoppedExecutionContext(const ExecutionContextRef *exe_ctx_ref_ptr);
+
 protected:
   // Member variables
   lldb::TargetWP m_target_wp;         ///< A weak reference to a target
@@ -283,13 +289,10 @@ class ExecutionContextRef {
                                               /// backing object changes
   StackID m_stack_id; ///< The stack ID that this object refers to in case the
                       ///< backing object changes
-  mutable lldb::StackFrameListWP
-      m_frame_list_wp; ///< Weak reference to the
-                       ///< frame list that contains
-                       ///< this frame. If we can create a valid
-                       ///< StackFrameListSP from it, we must use it to resolve
-                       ///< the StackID, otherwise, we should ask the Thread's
-                       ///< StackFrameList.
+  /// A map of identifiers to scripted frame providers used in this thread.
+  mutable std::optional<
+      std::pair<ScriptedFrameProviderDescriptor, lldb::frame_list_id_t>>
+      m_frame_list_id;
 };
 
 /// \class ExecutionContext ExecutionContext.h
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 46922448d6e59..5cba9afe2a7e8 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -542,17 +542,17 @@ class StackFrame : public ExecutionContextScope,
 
   virtual lldb::RecognizedStackFrameSP GetRecognizedFrame();
 
-  /// Get the StackFrameList that contains this frame.
+  /// Get the identifier of the StackFrameList that contains this frame.
   ///
-  /// Returns the StackFrameList that contains this frame, allowing
+  /// Returns the StackFrameList identifier that contains this frame, allowing
   /// frames to resolve execution contexts without calling
   /// Thread::GetStackFrameList(), which can cause circular dependencies
   /// during frame provider initialization.
   ///
   /// \return
-  ///   The StackFrameList that contains this frame, or nullptr if not set.
-  virtual lldb::StackFrameListSP GetContainingStackFrameList() const {
-    return m_frame_list_wp.lock();
+  ///   The identifier of the containing StackFrameList
+  lldb::frame_list_id_t GetContainingStackFrameListIdentifier() const {
+    return m_frame_list_id;
   }
 
 protected:
@@ -598,8 +598,8 @@ class StackFrame : public ExecutionContextScope,
   /// be the first address of its function). True for actual frame zero as
   /// well as any other frame with the same trait.
   bool m_behaves_like_zeroth_frame;
+  lldb::frame_list_id_t m_frame_list_id = 0;
   lldb::VariableListSP m_variable_list_sp;
-  lldb::StackFrameListWP m_frame_list_wp;
   /// Value objects for each variable in m_variable_list_sp.
   ValueObjectList m_variable_list_value_objects;
   std::optional<lldb::RecognizedStackFrameSP> m_recognized_frame_sp;
diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h
index 539c070ff0f4b..715781abb83a3 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -24,7 +24,8 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> {
 public:
   // Constructors and Destructors
   StackFrameList(Thread &thread, const lldb::StackFrameListSP &prev_frames_sp,
-                 bool show_inline_frames);
+                 bool show_inline_frames,
+                 lldb::frame_list_id_t provider_id = 0);
 
   virtual ~StackFrameList();
 
@@ -104,6 +105,9 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> {
   /// Get the thread associated with this frame list.
   Thread &GetThread() const { return m_thread; }
 
+  /// Get the unique identifier for this frame list.
+  lldb::frame_list_id_t GetIdentifier() const { return m_identifier; }
+
 protected:
   friend class Thread;
   friend class ScriptedFrameProvider;
@@ -212,6 +216,9 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> {
   /// Whether or not to show synthetic (inline) frames. Immutable.
   const bool m_show_inlined_frames;
 
+  /// Unique identifier for this frame list instance.
+  lldb::frame_list_id_t m_identifier = 0;
+
   /// Returns true if fetching frames was interrupted, false otherwise.
   virtual bool FetchFramesUpTo(uint32_t end_idx,
                                InterruptionControl allow_interrupt);
@@ -243,7 +250,9 @@ class SyntheticStackFrameList : public StackFrameList {
 public:
   SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames,
                           const lldb::StackFrameListSP &prev_frames_sp,
-                          bool show_inline_frames);
+                          bool show_inline_frames,
+                          lldb::SyntheticFrameProviderSP provider_sp,
+                          uint64_t provider_id);
 
 protected:
   /// Override FetchFramesUpTo to lazily return frames from the provider
@@ -255,6 +264,9 @@ class SyntheticStackFrameList : public StackFrameList {
   /// The input stack frame list that the provider transforms.
   /// This could be a real StackFrameList or another SyntheticStackFrameList.
   lldb::StackFrameListSP m_input_frames;
+
+  /// The provider that transforms the input frames.
+  lldb::SyntheticFrameProviderSP m_provider;
 };
 
 } // namespace lldb_private
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 812a638910b3b..b2aea3a6ddc39 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -776,6 +776,12 @@ class Target : public std::enable_shared_from_this<Target>,
   const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
   GetScriptedFrameProviderDescriptors() const;
 
+protected:
+  /// Invalidate all potentially cached frame providers for all threads
+  /// and trigger a stack changed event for all threads.
+  void InvalidateThreadFrameProviders();
+
+public:
   // This part handles the breakpoints.
 
   BreakpointList &GetBreakpointList(bool internal = false);
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index 46ce192556756..9cc86a37c63e5 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -19,6 +19,7 @@
 #include "lldb/Target/ExecutionContextScope.h"
 #include "lldb/Target/RegisterCheckpoint.h"
 #include "lldb/Target/StackFrameList.h"
+#include "lldb/Target/SyntheticFrameProvider.h"
 #include "lldb/Utility/Broadcaster.h"
 #include "lldb/Utility/CompletionRequest.h"
 #include "lldb/Utility/Event.h"
@@ -26,6 +27,7 @@
 #include "lldb/Utility/UnimplementedError.h"
 #include "lldb/Utility/UserID.h"
 #include "lldb/lldb-private.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/Support/MemoryBuffer.h"
 
 #define LLDB_THREAD_MAX_STOP_EXC_DATA 8
@@ -1297,13 +1299,20 @@ class Thread : public std::enable_shared_from_this<Thread>,
 
   lldb::StackFrameListSP GetStackFrameList();
 
+  /// Get a frame list by its unique identifier.
+  lldb::StackFrameListSP GetFrameListByIdentifier(lldb::frame_list_id_t id);
+
   llvm::Error
   LoadScriptedFrameProvider(const ScriptedFrameProviderDescriptor &descriptor);
 
+  llvm::Expected<ScriptedFrameProviderDescriptor>
+  GetScriptedFrameProviderDescriptorForID(lldb::frame_list_id_t id) const;
+
   void ClearScriptedFrameProvider();
 
-  lldb::SyntheticFrameProviderSP GetFrameProvider() const {
-    return m_frame_provider_sp;
+  const llvm::DenseMap<lldb::frame_list_id_t, lldb::SyntheticFrameProviderSP> &
+  GetFrameProviders() const {
+    return m_frame_providers;
   }
 
 protected:
@@ -1383,6 +1392,8 @@ class Thread : public std::enable_shared_from_this<Thread>,
       m_state_mutex;       ///< Multithreaded protection for m_state.
   mutable std::recursive_mutex
       m_frame_mutex; ///< Multithreaded protection for m_state.
+  lldb::StackFrameListSP
+      m_unwinder_frames_sp;                ///< The unwinder frame list (ID 0).
   lldb::StackFrameListSP m_curr_frames_sp; ///< The stack frames that get lazily
                                            ///populated after a thread stops.
   lldb::StackFrameListSP m_prev_frames_sp; ///< The previous stack frames from
@@ -1409,8 +1420,23 @@ class Thread : public std::enable_shared_from_this<Thread>,
   /// The Thread backed by this thread, if any.
   lldb::ThreadWP m_backed_thread;
 
-  /// The Scripted Frame Provider, if any.
-  lldb::SyntheticFrameProviderSP m_frame_provider_sp;
+  /// Map from frame list ID to its frame provider.
+  /// Cleared in ClearStackFrames(), repopulated in GetStackFrameList().
+  llvm::DenseMap<lldb::frame_list_id_t, lldb::SyntheticFrameProviderSP>
+      m_frame_providers;
+
+  /// Ordered chain of provider IDs.
+  /// Persists across ClearStackFrames() to maintain stable provider IDs.
+  std::vector<std::pair<ScriptedFrameProviderDescriptor, lldb::frame_list_id_t>>
+      m_provider_chain_ids;
+
+  /// Map from frame list identifier to frame list weak pointer.
+  mutable llvm::DenseMap<lldb::frame_list_id_t, lldb::StackFrameListWP>
+      m_frame_lists_by_id;
+
+  /// Counter for assigning unique provider IDs. Starts at 1 since 0 is
+  /// reserved for normal unwinder frames. Persists across ClearStackFrames.
+  lldb::frame_list_id_t m_next_provider_id = 1;
 
 private:
   bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info
diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h
index 3f9f2b5de8dbe..8a528bad15f94 100644
--- a/lldb/include/lldb/ValueObject/ValueObject.h
+++ b/lldb/include/lldb/ValueObject/ValueObject.h
@@ -1121,6 +1121,84 @@ class ValueObject {
   const ValueObject &operator=(const ValueObject &) = delete;
 };
 
+// The two classes below are used by the public SBValue API implementation. This
+// is useful here because we need them in order to access the underlying
+// ValueObject from SBValue without introducing a back-dependency from the API
+// library to the more core libs.
+
+class ValueImpl {
+public:
+  ValueImpl() = default;
+
+  ValueImpl(lldb::ValueObjectSP in_valobj_sp,
+            lldb::DynamicValueType use_dynamic, bool use_synthetic,
+            const char *name = nullptr);
+
+  ValueImpl(const ValueImpl &rhs) = default;
+
+  ValueImpl &operator=(const ValueImpl &rhs);
+
+  bool IsValid();
+
+  lldb::ValueObjectSP GetRootSP() { return m_valobj_sp; }
+
+  lldb::ValueObjectSP GetSP(Process::StopLocker &stop_locker,
+                            std::unique_lock<std::recursive_mutex> &lock,
+                            Status &error);
+
+  void SetUseDynamic(lldb::DynamicValueType use_dynamic) {
+    m_use_dynamic = use_dynamic;
+  }
+
+  void SetUseSynthetic(bool use_synthetic) { m_use_synthetic = use_synthetic; }
+
+  lldb::DynamicValueType GetUseDynamic() { return m_use_dynamic; }
+
+  bool GetUseSynthetic() { return m_use_synthetic; }
+
+  // All the derived values that we would make from the m_valobj_sp will share
+  // the ExecutionContext with m_valobj_sp, so we don't need to do the
+  // calculations in GetSP to return the Target, Process, Thread or Frame.  It
+  // is convenient to provide simple accessors for these, which I do here.
+  lldb::TargetSP GetTargetSP() {
+    return m_valobj_sp ? m_valobj_sp->GetTargetSP() : lldb::TargetSP{};
+  }
+
+  lldb::ProcessSP GetProcessSP() {
+    return m_valobj_sp ? m_valobj_sp->GetProcessSP() : lldb::ProcessSP{};
+  }
+
+  lldb::ThreadSP GetThreadSP() {
+    return m_valobj_sp ? m_valobj_sp->GetThreadSP() : lldb::ThreadSP{};
+  }
+
+  lldb::StackFrameSP GetFrameSP() {
+    return m_valobj_sp ? m_valobj_sp->GetFrameSP() : lldb::StackFrameSP{};
+  }
+
+private:
+  lldb::ValueObjectSP m_valobj_sp;
+  lldb::DynamicValueType m_use_dynamic;
+  bool m_use_synthetic;
+  ConstString m_name;
+};
+
+class ValueLocker {
+public:
+  ValueLocker() = default;
+
+  lldb::ValueObjectSP GetLockedSP(ValueImpl &in_value) {
+    return in_value.GetSP(m_stop_locker, m_lock, m_lock_error);
+  }
+
+  Status &GetError() { return m_lock_error; }
+
+private:
+  Process::StopLocker m_stop_locker;
+  std::unique_lock<std::recursive_mutex> m_lock;
+  Status m_lock_error;
+};
+
 } // namespace lldb_private
 
 #endif // LLDB_VALUEOBJECT_VALUEOBJECT_H
diff --git a/lldb/include/lldb/lldb-defines.h b/lldb/include/lldb/lldb-defines.h
index 52bf7c5cce947..8e1029387d2da 100644
--- a/lldb/include/lldb/lldb-defines.h
+++ b/lldb/include/lldb/lldb-defines.h
@@ -89,6 +89,7 @@
 #define LLDB_INVALID_PROCESS_ID 0
 #define LLDB_INVALID_THREAD_ID 0
 #define LLDB_INVALID_FRAME_ID UINT32_MAX
+#define LLDB_UNWINDER_FRAME_LIST_ID 0
 #define LLDB_INVALID_SIGNAL_NUMBER INT32_MAX
 #define LLDB_INVALID_SYMBOL_ID UINT32_MAX
 #define LLDB_INVALID_OFFSET UINT64_MAX // Must match max of lldb::offset_t
diff --git a/lldb/include/lldb/lldb-types.h b/lldb/include/lldb/lldb-types.h
index e309fc8833ce9..bb4c34ef8e1f5 100644
--- a/lldb/include/lldb/lldb-types.h
+++ b/lldb/include/lldb/lldb-types.h
@@ -83,6 +83,7 @@ typedef uint64_t user_id_t;
 typedef uint64_t pid_t;
 typedef uint64_t tid_t;
 typedef uint64_t offset_t;
+typedef uint32_t frame_list_id_t;
 typedef int32_t break_id_t;
 typedef int32_t watch_id_t;
 typedef uint32_t wp_resource_id_t;
diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp
index 5b67270859da2..adc03785602e1 100644
--- a/lldb/source/API/SBValue.cpp
+++ b/lldb/source/API/SBValue.cpp
@@ -52,172 +52,6 @@
 using namespace lldb;
 using namespace lldb_private;
 
-class ValueImpl {
-public:
-  ValueImpl() = default;
-
-  ValueImpl(lldb::ValueObjectSP in_valobj_sp,
-            lldb::DynamicValueType use_dynamic, bool use_synthetic,
-            const char *name = nullptr)
-      : m_use_dynamic(use_dynamic), m_use_synthetic(use_synthetic),
-        m_name(name) {
-    if (in_valobj_sp) {
-      if ((m_valobj_sp = in_valobj_sp->GetQualifiedRepresentationIfAvailable(
-               lldb::eNoDynamicValues, false))) {
-        if (!m_name.IsEmpty())
-          m_valobj_sp->SetName(m_name);
-      }
-    }
-  }
-
-  ValueImpl(const ValueImpl &rhs) = default;
-
-  ValueImpl &operator=(const ValueImpl &rhs) {
-    if (this != &rhs) {
-      m_valobj_sp = rhs.m_valobj_sp;
-      m_use_dynamic = rhs.m_use_dynamic;
-      m_use_synthetic = rhs.m_use_synthetic;
-      m_name = rhs.m_name;
-    }
-    return *this;
-  }
-
-  bool IsValid() {
-    if (m_valobj_sp.get() == nullptr)
-      return false;
-    else {
-      // FIXME: This check is necessary but not sufficient.  We for sure don't
-      // want to touch SBValues whose owning
-      // targets have gone away.  This check is a little weak in that it
-      // enforces that restriction when you call IsValid, but since IsValid
-      // doesn't lock the target, you have no guarantee that the SBValue won't
-      // go invalid after you call this... Also, an SBValue could depend on
-      // data from one of the modules in the target, and those could go away
-      // independently of the target, for instance if a module is unloaded.
-      // But right now, neither SBValues nor ValueObjects know which modules
-      // they depend on.  So I have no good way to make that check without
-      // tracking that in all the ValueObject subclasses.
-      TargetSP target_sp = m_valobj_sp->GetTargetSP();
-      return target_sp && target_sp->IsValid();
-    }
-  }
-
-  lldb::ValueObjectSP GetRootSP() { return m_valobj_sp; }
-
-  lldb::ValueObjectSP GetSP(Process::StopLocker &stop_locker,
-                            std::unique_lock<std::recursive_mutex> &lock,
-                            Status &error) {
-    if (!m_valobj_sp) {
-      error = Status::FromError...
[truncated]

@medismailben medismailben changed the title release/22.x: [lldb] Broadcast eBroadcastBitStackChanged when frame providers change (#171482) release/22.x: [lldb] Backport FrameProviders fixes & improvements Feb 5, 2026
Copy link
Member

@medismailben medismailben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-project-automation github-project-automation bot moved this from Needs Triage to Needs Merge in LLVM Release Status Feb 5, 2026
Copy link
Member

@vogelsgesang vogelsgesang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM on a high-level. All commits seem to be worthwhile backporting, but I didn't look at the individual code changes in detail

@c-rhodes
Copy link
Collaborator

c-rhodes commented Feb 6, 2026

there's a lot of commits and changes here, is this a that's feature landed that's required many follow up fixes?

@vogelsgesang
Copy link
Member

Yes, those commits are part of the new ScriptedFrameProvider feature which was added in lldb 22. See #161870 for the "primary commit" of this feature.

  • Crash & deadlock fix (c373d76) - should clearly be backported
  • Usability improvement (943782b) - without this target frame-provider register only takes effect the next time the debugee stops. Not a nice experience without this patch. I would also consider this a bug fix.
  • Chaining multiple frame providers (17b01bb + test fix cd70e2d) - this was functionality was agreed upon as part of the original discussion in [lldb] Introduce ScriptedFrameProvider for real threads #161870, but only landed after the branch cut. I guess we could live without it, but I consider it rather low risk
  • Add support for ScriptedFrame to provide values/variables (10f2611 + preparatory commits 58f623c 8122d0e) - this adds additional capabilities to ScriptedFrameProvider, first discussed in https://discourse.llvm.org/t/synthetic-frame-variables/89585 11 days ago (i.e. after the branch cut). One could argue whether this is a new feature and shouldn't be backported. That being said, not backporting those might be annoying due to rebase conflicts

@medismailben
Copy link
Member

Yes, those commits are part of the new ScriptedFrameProvider feature which was added in lldb 22. See #161870 for the "primary commit" of this feature.

  • Crash & deadlock fix (c373d76) - should clearly be backported
  • Usability improvement (943782b) - without this target frame-provider register only takes effect the next time the debugee stops. Not a nice experience without this patch. I would also consider this a bug fix.
  • Chaining multiple frame providers (17b01bb + test fix cd70e2d) - this was functionality was agreed upon as part of the original discussion in [lldb] Introduce ScriptedFrameProvider for real threads #161870, but only landed after the branch cut. I guess we could live without it, but I consider it rather low risk
  • Add support for ScriptedFrame to provide values/variables (10f2611 + preparatory commits 58f623c 8122d0e) - this adds additional capabilities to ScriptedFrameProvider, first discussed in https://discourse.llvm.org/t/synthetic-frame-variables/89585 11 days ago (i.e. after the branch cut). One could argue whether this is a new feature and shouldn't be backported. That being said, not backporting those might be annoying due to rebase conflicts

+1, this feature has already landed partially in lldb 22 but we found a bug late in the release cycle where if a user had a SBFrame and try running an expression, that would invalidate the SBFrame reference. This is very intrusive because it's very common thing to do, specifically in IDEs ... so yeah, we definitely want to pick this up in the upcoming release.

Thanks.

medismailben and others added 7 commits February 9, 2026 09:52
…lvm#172849)

This patch allows threads to have multiple SyntheticFrameProviderSP
instances that chain together sequentially. Each provider receives the
output of the previous provider as input, creating a transformation
pipeline.

It changes `Thread::m_frame_provider_sp` to a vector, adds provider
parameter to SyntheticStackFrameList to avoid calling back into
`Thread::GetFrameProvider()` during frame fetching, updated
`LoadScriptedFrameProvider()` to chain providers by wrapping each
previous provider's output in a `SyntheticStackFrameList` for the next
provider and finally, loads ALL matching providers in priority order
instead of just the first one.

The chaining works as follows:
```
  Real Unwinder Frames
      ↓
  Provider 1 (priority 10) → adds/transforms frames
      ↓
  Provider 2 (priority 20) → transforms Provider 1's output
      ↓
  Provider 3 (priority 30) → transforms Provider 2's output
      ↓
  Final frame list shown to user
  ```

This patch also adds a test for this (test_chained_frame_providers) to verify that 3 providers chain correctly: `AddFooFrameProvider`, `AddBarFrameProvider`, `AddBazFrameProvider`.

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
(cherry picked from commit 17b01bb)
PC addresses must always be 16-bit aligned on 32-bit Arm CPUs.

Fixes llvm#177666

(cherry picked from commit cd70e2d)
This patch moves ValueImpl and ValueLocker to ValueObject.{h,cpp}. This follows the example set in TypeImpl/SBType, where we have something that SBType uses internally that needs to be exposed in the layer below. In this case, SBValue uses ValueImpl, which wraps ValueObject. The wrapper helps avoid bugs, so we want to keep it, but the script interpreter needs to use it and said interpreter is conceptually *below* the SB layer...which means we can't use methods on SBValue.

This patch is purely the code motion part of that, future patches will actually make use of this moved code.

(cherry picked from commit 58f623c)
…ge. (llvm#178574)

This patch adds support for:
- PyObject -> SBValueList (which was surprisingly not there before!)
- PyObject -> SBValue
- SBValue -> ValueObjectSP using the ScriptInterpreter

These three are the main remaining plumbing changes necessary before we can get to the meat of actually using ScriptedFrame to provide values to the printer/etc. Future patches build off this change in order to allow ScriptedFrames to provide variables and get values for variable expressions.

(cherry picked from commit 8122d0e)
…vm#178575)

This patch adds plumbing to support the implementations of StackFrame::Get{*}Variable{*} on ScriptedFrame. The major pieces required are:
- A modification to ScriptedFrameInterface, so that we can actually call the python methods.
- A corresponding update to the python implementation to call the python methods.
- An implementation in ScriptedFrame that can get the variable list on construction inside ScriptedFrame::Create, and pass that list into the ScriptedFrame so it can get those values on request.

There is a major caveat, which is that if the values from the python side don't have variables attached, right now, they won't be passed into the scripted frame to be stored in the variable list. Future discussions around adding support for 'extended variables' when printing frame variables may create a reason to change the VariableListSP into a ValueObjectListSP, and generate the VariableListSP on the fly, but that should be addressed at a later time.

This patch also adds tests to the frame provider test suite to prove these changes all plumb together correctly.

Related radar: rdar://165708771

(cherry picked from commit 10f2611)
…lls (llvm#178823)

When a user holds an SBFrame reference and then triggers an inferior
function
call (via expression evaluation or GetExtendedBacktraceThread),
variables in
that frame become inaccessible with "register fp is not available"
errors.

This happens because inferior function calls execute through
ThreadPlanCallFunction, which calls ClearStackFrames() during cleanup to
invalidate the unwinder state. ExecutionContextRef objects in the old
SBFrames
were tracking StackFrameLists via weak_ptr, which became stale when
ClearStackFrames() created new instances.

The fix uses stable StackFrameList identifiers that persist across
ClearStackFrames():
- ID = 0: Normal unwinder frames (constant across all instances)
- ID = sequential counter: Scripted frame provider instances

ExecutionContextRef now stores the frame list ID instead of a weak_ptr,
allowing
it to resolve to the current StackFrameList with fresh unwinder state
after an
inferior function call completes.

The Thread object preserves the provider chain configuration
(m_provider_chain_ids and m_next_provider_id) across ClearStackFrames()
so
that recreated StackFrameLists get the same IDs. When providers need to
be
recreated, GetStackFrameList() rebuilds them from the persisted
configuration.

This commit also fixes a deadlock when Python scripted frame providers
call
back into LLDB during frame fetching. The m_list_mutex is now released
before
calling GetFrameAtIndex() on the Python scripted frame provider to
prevent
same-thread deadlock. A dedicated m_unwinder_frames_sp member ensures
GetFrameListByIdentifier(0) always returns the current unwinder frames,
and
proper cleanup in DestroyThread() and ClearStackFrames() to prevent
modules
from lingering after a Thread (and its StackFrameLists) gets destroyed.

Added test validates that variables remain accessible after
GetExtendedBacktraceThread triggers an inferior function call to fetch
libdispatch Queue Info.

rdar://167027676

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
(cherry picked from commit c373d76)
…nge (llvm#171482)

We want to reload the call stack whenever the frame providers are
updated. To do so, we now emit a `eBroadcastBitStackChanged` on all
threads whenever any changes to the frame providers take place.

I found this very useful while iterating on a frame provider in
lldb-dap. So far, the new frame provider only took effect after
continuing execution. Now the backtrace in VS-Code gets refreshed
immediately upon running `target frame-provider add`.

(cherry picked from commit 943782b)
@c-rhodes c-rhodes merged commit bc604c7 into llvm:release/22.x Feb 9, 2026
1 check was pending
@github-project-automation github-project-automation bot moved this from Needs Merge to Done in LLVM Release Status Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Development

Successfully merging this pull request may close these issues.

6 participants