[Lldb-commits] [lldb] [lldb] Introduce ScriptedFrameProvider for real threads (PR #161870)

Med Ismail Bennani via lldb-commits lldb-commits at lists.llvm.org
Mon Oct 6 10:00:19 PDT 2025


https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/161870

>From ab4a9b83732600a65843b19ecf0b5a517c7ae426 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 6 Oct 2025 16:12:14 +0100
Subject: [PATCH] [lldb] Introduce ScriptedFrameProvider for real threads

This patch introduces a new scripting affordance: `ScriptedFrameProvider`.

This allows users to provide custom stack frames for real native threads,
augmenting or replacing the standard unwinding mechanism. This is useful
for:
- Providing frames for custom calling conventions or languages
- Reconstructing missing frames from crash dumps or core files
- Adding diagnostic or synthetic frames for debugging

The frame provider supports four merge strategies:
- Replace: Replace entire stack with scripted frames
- Prepend: Add scripted frames before real stack
- Append: Add scripted frames after real stack
- ReplaceByIndex: Replace specific frames by index

With this change, frames can be synthesized from different sources:
- Either from a dictionary containing a PC address and frame index
- Or by creating a ScriptedFrame python object for full control

To use it, first register the scripted frame provider then use existing
commands:
   (lldb) frame provider register -C my_module.MyFrameProvider

      or

   (lldb) script thread.RegisterFrameProvider("my_module.MyFrameProvider", lldb.SBStructuredData())

      then

   (lldb) bt

See examples/python/templates/scripted_frame_provider.py for details.

Architecture changes:
- Moved ScriptedFrame from `Plugins` to `Interpreter` to avoid
   layering violations
- Moved `RegisterContextMemory` from `Plugins` to `Target` as it only
   depends on Target and Utility layers
- Added `ScriptedFrameProvider` C++ wrapper and Python interface
- Updated `Thread::GetStackFrameList` to apply merge strategies

rdar://161834688

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 lldb/bindings/python/CMakeLists.txt           |   1 +
 .../templates/scripted_frame_provider.py      | 147 +++++++++++
 .../python/templates/scripted_process.py      |  41 ++-
 lldb/include/lldb/API/SBThread.h              |   5 +
 .../ScriptedFrameProviderInterface.h          |  36 +++
 .../lldb/Interpreter/ScriptInterpreter.h      |   6 +
 .../lldb/Interpreter}/ScriptedFrame.h         |  42 ++-
 .../lldb/Interpreter/ScriptedFrameProvider.h  |  58 ++++
 .../lldb/Target}/RegisterContextMemory.h      |   6 +-
 lldb/include/lldb/Target/StackFrame.h         |   5 +-
 lldb/include/lldb/Target/StackFrameList.h     |   1 +
 lldb/include/lldb/Target/Thread.h             |   9 +
 lldb/include/lldb/lldb-enumerations.h         |  13 +
 lldb/include/lldb/lldb-forward.h              |   6 +
 lldb/source/API/SBThread.cpp                  |  30 +++
 lldb/source/Commands/CommandObjectFrame.cpp   |  96 +++++++
 lldb/source/Commands/CommandObjectThread.cpp  |   1 +
 lldb/source/Interpreter/CMakeLists.txt        |   2 +
 .../ScriptedFrame.cpp                         |  84 ++++--
 .../Interpreter/ScriptedFrameProvider.cpp     | 197 ++++++++++++++
 .../Python/OperatingSystemPython.cpp          |   2 +-
 .../Plugins/Process/Utility/CMakeLists.txt    |   1 -
 .../Plugins/Process/scripted/CMakeLists.txt   |   1 -
 .../Process/scripted/ScriptedThread.cpp       |   5 +-
 .../Plugins/Process/scripted/ScriptedThread.h |   2 +-
 .../Python/Interfaces/CMakeLists.txt          |   1 +
 .../ScriptInterpreterPythonInterfaces.h       |   1 +
 .../ScriptedFrameProviderPythonInterface.cpp  |  71 +++++
 .../ScriptedFrameProviderPythonInterface.h    |  45 ++++
 .../Interfaces/ScriptedPythonInterface.h      |   4 +
 .../Python/ScriptInterpreterPython.cpp        |   5 +
 .../Python/ScriptInterpreterPythonImpl.h      |   3 +
 lldb/source/Target/CMakeLists.txt             |   1 +
 .../RegisterContextMemory.cpp                 |   2 +-
 lldb/source/Target/Thread.cpp                 | 131 ++++++++-
 .../scripted_frame_provider/Makefile          |   3 +
 .../TestScriptedFrameProvider.py              | 248 ++++++++++++++++++
 .../scripted_frame_provider/main.c            |  14 +
 .../test_frame_providers.py                   | 182 +++++++++++++
 39 files changed, 1442 insertions(+), 66 deletions(-)
 create mode 100644 lldb/examples/python/templates/scripted_frame_provider.py
 create mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
 rename lldb/{source/Plugins/Process/scripted => include/lldb/Interpreter}/ScriptedFrame.h (60%)
 create mode 100644 lldb/include/lldb/Interpreter/ScriptedFrameProvider.h
 rename lldb/{source/Plugins/Process/Utility => include/lldb/Target}/RegisterContextMemory.h (92%)
 rename lldb/source/{Plugins/Process/scripted => Interpreter}/ScriptedFrame.cpp (66%)
 create mode 100644 lldb/source/Interpreter/ScriptedFrameProvider.cpp
 create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
 create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
 rename lldb/source/{Plugins/Process/Utility => Target}/RegisterContextMemory.cpp (99%)
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/Makefile
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/main.c
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py

diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt
index ef6def3f26872..28a8af8f06319 100644
--- a/lldb/bindings/python/CMakeLists.txt
+++ b/lldb/bindings/python/CMakeLists.txt
@@ -107,6 +107,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar
     "plugins"
     FILES
     "${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py"
+    "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py"
     "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py"
     "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py"
     "${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py"
diff --git a/lldb/examples/python/templates/scripted_frame_provider.py b/lldb/examples/python/templates/scripted_frame_provider.py
new file mode 100644
index 0000000000000..5838e1c44e980
--- /dev/null
+++ b/lldb/examples/python/templates/scripted_frame_provider.py
@@ -0,0 +1,147 @@
+from abc import ABCMeta, abstractmethod
+
+import lldb
+
+
+class ScriptedFrameProvider(metaclass=ABCMeta):
+    """
+    The base class for a scripted frame provider.
+
+    A scripted frame provider allows you to provide custom stack frames for a
+    thread, which can be used to augment or replace the standard unwinding
+    mechanism. This is useful for:
+
+    - Providing frames for custom calling conventions or languages
+    - Reconstructing missing frames from crash dumps or core files
+    - Adding diagnostic or synthetic frames for debugging
+    - Visualizing state machines or async execution contexts
+
+    Most of the base class methods are `@abstractmethod` that need to be
+    overwritten by the inheriting class.
+
+    Example usage:
+
+    .. code-block:: python
+
+        # Attach a frame provider to a thread
+        thread = process.GetSelectedThread()
+        error = lldb.SBError()
+        thread.SetScriptedFrameProvider(
+            "my_module.MyFrameProvider",
+            lldb.SBStructuredData()
+        )
+    """
+
+    @abstractmethod
+    def __init__(self, thread, args):
+        """Construct a scripted frame provider.
+
+        Args:
+            thread (lldb.SBThread): The thread for which to provide frames.
+            args (lldb.SBStructuredData): A Dictionary holding arbitrary
+                key/value pairs used by the scripted frame provider.
+        """
+        self.thread = None
+        self.args = None
+        self.target = None
+        self.process = None
+
+        if isinstance(thread, lldb.SBThread) and thread.IsValid():
+            self.thread = thread
+            self.process = thread.GetProcess()
+            if self.process and self.process.IsValid():
+                self.target = self.process.GetTarget()
+
+        if isinstance(args, lldb.SBStructuredData) and args.IsValid():
+            self.args = args
+
+    def get_merge_strategy(self):
+        """Get the merge strategy for how scripted frames should be integrated.
+
+        The merge strategy determines how the scripted frames are combined with the
+        real unwound frames from the thread's normal unwinder.
+
+        Returns:
+            int: One of the following lldb.ScriptedFrameProviderMergeStrategy values:
+
+            - lldb.eScriptedFrameProviderMergeStrategyReplace: Replace the entire stack
+              with scripted frames. The thread will only show frames provided
+              by this provider.
+
+            - lldb.eScriptedFrameProviderMergeStrategyPrepend: Prepend scripted frames
+              before the real unwound frames. Useful for adding synthetic frames
+              at the top of the stack while preserving the actual callstack below.
+
+            - lldb.eScriptedFrameProviderMergeStrategyAppend: Append scripted frames
+              after the real unwound frames. Useful for showing additional context
+              after the actual callstack ends.
+
+            - lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex: Replace specific
+              frames at given indices with scripted frames, keeping other real frames
+              intact. The idx field in each frame dictionary determines which real
+              frame to replace (e.g., idx=0 replaces frame 0, idx=2 replaces frame 2).
+
+        The default implementation returns Replace strategy.
+
+        Example:
+
+        .. code-block:: python
+
+            def get_merge_strategy(self):
+                # Only show our custom frames
+                return lldb.eScriptedFrameProviderMergeStrategyReplace
+
+            def get_merge_strategy(self):
+                # Add diagnostic frames on top of real stack
+                return lldb.eScriptedFrameProviderMergeStrategyPrepend
+
+            def get_merge_strategy(self):
+                # Replace frame 0 and frame 2 with custom frames, keep others
+                return lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex
+        """
+        return lldb.eScriptedFrameProviderMergeStrategyReplace
+
+    @abstractmethod
+    def get_stackframes(self):
+        """Get the list of stack frames to provide.
+
+        This method is called when the thread's backtrace is requested
+        (e.g., via the 'bt' command). The returned frames will be integrated
+        with the real frames according to the mode returned by get_mode().
+
+        Returns:
+            List[Dict]: A list of frame dictionaries, where each dictionary
+                describes a single stack frame. Each dictionary should contain:
+
+            Required fields:
+            - idx (int): The frame index (0 for innermost/top frame)
+            - pc (int): The program counter address for this frame
+
+            Alternatively, you can return a list of ScriptedFrame objects
+            for more control over frame behavior.
+
+        Example:
+
+        .. code-block:: python
+
+            def get_stackframes(self):
+                frames = []
+
+                # Frame 0: Current function
+                frames.append({
+                    "idx": 0,
+                    "pc": 0x100001234,
+                })
+
+                # Frame 1: Caller
+                frames.append({
+                    "idx": 1,
+                    "pc": 0x100001000,
+                })
+
+                return frames
+
+        Note:
+            The frames are indexed from 0 (innermost/newest) to N (outermost/oldest).
+        """
+        pass
diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py
index 49059d533f38a..6242978100711 100644
--- a/lldb/examples/python/templates/scripted_process.py
+++ b/lldb/examples/python/templates/scripted_process.py
@@ -245,6 +245,7 @@ def __init__(self, process, args):
                 key/value pairs used by the scripted thread.
         """
         self.target = None
+        self.arch = None
         self.originating_process = None
         self.process = None
         self.args = None
@@ -266,6 +267,9 @@ def __init__(self, process, args):
             and process.IsValid()
         ):
             self.target = process.target
+            triple = self.target.triple
+            if triple:
+                self.arch = triple.split("-")[0]
             self.originating_process = process
             self.process = self.target.GetProcess()
             self.get_register_info()
@@ -352,17 +356,14 @@ def get_stackframes(self):
     def get_register_info(self):
         if self.register_info is None:
             self.register_info = dict()
-            if "x86_64" in self.originating_process.arch:
+            if "x86_64" in self.arch:
                 self.register_info["sets"] = ["General Purpose Registers"]
                 self.register_info["registers"] = INTEL64_GPR
-            elif (
-                "arm64" in self.originating_process.arch
-                or self.originating_process.arch == "aarch64"
-            ):
+            elif "arm64" in self.arch or self.arch == "aarch64":
                 self.register_info["sets"] = ["General Purpose Registers"]
                 self.register_info["registers"] = ARM64_GPR
             else:
-                raise ValueError("Unknown architecture", self.originating_process.arch)
+                raise ValueError("Unknown architecture", self.arch)
         return self.register_info
 
     @abstractmethod
@@ -405,11 +406,12 @@ def __init__(self, thread, args):
         """Construct a scripted frame.
 
         Args:
-            thread (ScriptedThread): The thread owning this frame.
+            thread (ScriptedThread/lldb.SBThread): The thread owning this frame.
             args (lldb.SBStructuredData): A Dictionary holding arbitrary
                 key/value pairs used by the scripted frame.
         """
         self.target = None
+        self.arch = None
         self.originating_thread = None
         self.thread = None
         self.args = None
@@ -424,10 +426,14 @@ def __init__(self, thread, args):
             or isinstance(thread, lldb.SBThread)
             and thread.IsValid()
         ):
-            self.target = thread.target
             self.process = thread.process
+            self.target = self.process.target
+            triple = self.target.triple
+            if triple:
+                self.arch = triple.split("-")[0]
+            tid = thread.tid if isinstance(thread, ScriptedThread) else thread.id
             self.originating_thread = thread
-            self.thread = self.process.GetThreadByIndexID(thread.tid)
+            self.thread = self.process.GetThreadByIndexID(tid)
             self.get_register_info()
 
     @abstractmethod
@@ -508,7 +514,18 @@ def get_variables(self, filters):
 
     def get_register_info(self):
         if self.register_info is None:
-            self.register_info = self.originating_thread.get_register_info()
+            if isinstance(self.originating_thread, ScriptedThread):
+                self.register_info = self.originating_thread.get_register_info()
+            elif isinstance(self.originating_thread, lldb.SBThread):
+                self.register_info = dict()
+                if "x86_64" in self.arch:
+                    self.register_info["sets"] = ["General Purpose Registers"]
+                    self.register_info["registers"] = INTEL64_GPR
+                elif "arm64" in self.arch or self.arch == "aarch64":
+                    self.register_info["sets"] = ["General Purpose Registers"]
+                    self.register_info["registers"] = ARM64_GPR
+                else:
+                    raise ValueError("Unknown architecture", self.arch)
         return self.register_info
 
     @abstractmethod
@@ -642,12 +659,12 @@ def get_stop_reason(self):
 
             # TODO: Passthrough stop reason from driving process
             if self.driving_thread.GetStopReason() != lldb.eStopReasonNone:
-                if "arm64" in self.originating_process.arch:
+                if "arm64" in self.arch:
                     stop_reason["type"] = lldb.eStopReasonException
                     stop_reason["data"]["desc"] = (
                         self.driving_thread.GetStopDescription(100)
                     )
-                elif self.originating_process.arch == "x86_64":
+                elif self.arch == "x86_64":
                     stop_reason["type"] = lldb.eStopReasonSignal
                     stop_reason["data"]["signal"] = signal.SIGTRAP
                 else:
diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h
index e9fe5858d125e..d43016eff7879 100644
--- a/lldb/include/lldb/API/SBThread.h
+++ b/lldb/include/lldb/API/SBThread.h
@@ -229,6 +229,11 @@ class LLDB_API SBThread {
 
   SBValue GetSiginfo();
 
+  SBError RegisterFrameProvider(const char *class_name,
+                                SBStructuredData &args_data);
+
+  void ClearScriptedFrameProvider();
+
 private:
   friend class SBBreakpoint;
   friend class SBBreakpointLocation;
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
new file mode 100644
index 0000000000000..99df9b765a3a7
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
@@ -0,0 +1,36 @@
+//===-- ScriptedFrameProviderInterface.h ------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
+
+#include "lldb/lldb-private.h"
+
+#include "ScriptedInterface.h"
+
+namespace lldb_private {
+class ScriptedFrameProviderInterface : public ScriptedInterface {
+public:
+  virtual llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name, lldb::ThreadSP thread_sp,
+                     StructuredData::DictionarySP args_sp) = 0;
+
+  /// Get the merge strategy for how scripted frames should be integrated with
+  /// real frames
+  virtual lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy() {
+    return lldb::eScriptedFrameProviderMergeStrategyReplace;
+  }
+
+  virtual StructuredData::ArraySP
+  GetStackFrames(lldb::StackFrameListSP real_frames) {
+    return {};
+  }
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 024bbc90a9a39..47f994a532199 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -27,6 +27,7 @@
 #include "lldb/Host/StreamFile.h"
 #include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
+#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
@@ -536,6 +537,11 @@ class ScriptInterpreter : public PluginInterface {
     return {};
   }
 
+  virtual lldb::ScriptedFrameProviderInterfaceSP
+  CreateScriptedFrameProviderInterface() {
+    return {};
+  }
+
   virtual lldb::ScriptedThreadPlanInterfaceSP
   CreateScriptedThreadPlanInterface() {
     return {};
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/include/lldb/Interpreter/ScriptedFrame.h
similarity index 60%
rename from lldb/source/Plugins/Process/scripted/ScriptedFrame.h
rename to lldb/include/lldb/Interpreter/ScriptedFrame.h
index 6e01e2fd7653e..6d3f1e5506bc4 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
+++ b/lldb/include/lldb/Interpreter/ScriptedFrame.h
@@ -1,4 +1,4 @@
-//===----------------------------------------------------------------------===//
+//===-- ScriptedFrame.h -----------------------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,26 +6,22 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
-#define LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
+#ifndef LLDB_INTERPRETER_SCRIPTEDFRAME_H
+#define LLDB_INTERPRETER_SCRIPTEDFRAME_H
 
-#include "Plugins/Process/Utility/RegisterContextMemory.h"
-#include "ScriptedThread.h"
-#include "lldb/Interpreter/ScriptInterpreter.h"
 #include "lldb/Target/DynamicRegisterInfo.h"
 #include "lldb/Target/StackFrame.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/Support/Error.h"
+#include <memory>
 #include <string>
 
-namespace lldb_private {
-class ScriptedThread;
-}
-
 namespace lldb_private {
 
 class ScriptedFrame : public lldb_private::StackFrame {
 
 public:
-  ScriptedFrame(ScriptedThread &thread,
+  ScriptedFrame(lldb::ThreadSP thread_sp,
                 lldb::ScriptedFrameInterfaceSP interface_sp,
                 lldb::user_id_t frame_idx, lldb::addr_t pc,
                 SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp,
@@ -34,8 +30,28 @@ class ScriptedFrame : public lldb_private::StackFrame {
 
   ~ScriptedFrame() override;
 
+  /// Create a ScriptedFrame from a script object.
+  ///
+  /// \param[in] thread_sp
+  ///     The thread this frame belongs to.
+  ///
+  /// \param[in] scripted_thread_interface_sp
+  ///     The scripted thread interface (needed for ScriptedThread
+  ///     compatibility). Can be nullptr for frames on real threads.
+  ///
+  /// \param[in] args_sp
+  ///     Arguments to pass to the frame creation.
+  ///
+  /// \param[in] script_object
+  ///     The script object representing this frame.
+  ///
+  /// \return
+  ///     An Expected containing the ScriptedFrame shared pointer if successful,
+  ///     otherwise an error.
   static llvm::Expected<std::shared_ptr<ScriptedFrame>>
-  Create(ScriptedThread &thread, StructuredData::DictionarySP args_sp,
+  Create(lldb::ThreadSP thread_sp,
+         lldb::ScriptedThreadInterfaceSP scripted_thread_interface_sp,
+         StructuredData::DictionarySP args_sp,
          StructuredData::Generic *script_object = nullptr);
 
   bool IsInlined() override;
@@ -60,4 +76,4 @@ class ScriptedFrame : public lldb_private::StackFrame {
 
 } // namespace lldb_private
 
-#endif // LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
+#endif // LLDB_INTERPRETER_SCRIPTEDFRAME_H
\ No newline at end of file
diff --git a/lldb/include/lldb/Interpreter/ScriptedFrameProvider.h b/lldb/include/lldb/Interpreter/ScriptedFrameProvider.h
new file mode 100644
index 0000000000000..5a84afd29d7e0
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/ScriptedFrameProvider.h
@@ -0,0 +1,58 @@
+//===-- ScriptedFrameProvider.h --------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H
+#define LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H
+
+#include "lldb/Utility/ScriptedMetadata.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/Support/Error.h"
+
+namespace lldb_private {
+
+class ScriptedFrameProvider {
+public:
+  /// Constructor that initializes the scripted frame provider.
+  ///
+  /// \param[in] thread_sp
+  ///     The thread for which to provide scripted frames.
+  ///
+  /// \param[in] scripted_metadata
+  ///     The metadata containing the class name and arguments for the
+  ///     scripted frame provider.
+  ///
+  /// \param[out] error
+  ///     Status object to report any errors during initialization.
+  ScriptedFrameProvider(lldb::ThreadSP thread_sp,
+                        const ScriptedMetadata &scripted_metadata,
+                        Status &error);
+  ~ScriptedFrameProvider();
+
+  /// Get the stack frames from the scripted frame provider.
+  ///
+  /// \return
+  ///     An Expected containing the StackFrameListSP if successful,
+  ///     otherwise an error describing what went wrong.
+  llvm::Expected<lldb::StackFrameListSP>
+  GetStackFrames(lldb::StackFrameListSP real_frames);
+
+  /// Get the merge strategy for how scripted frames should be integrated.
+  ///
+  /// \return
+  ///     The ScriptedFrameProviderMergeStrategy indicating how to merge frames.
+  lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy();
+
+private:
+  lldb::ThreadSP m_thread_sp;
+  lldb::ScriptedFrameProviderInterfaceSP m_interface_sp;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H
diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.h b/lldb/include/lldb/Target/RegisterContextMemory.h
similarity index 92%
rename from lldb/source/Plugins/Process/Utility/RegisterContextMemory.h
rename to lldb/include/lldb/Target/RegisterContextMemory.h
index 2aad99ec9b210..d156a5c881267 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.h
+++ b/lldb/include/lldb/Target/RegisterContextMemory.h
@@ -6,8 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H
-#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H
+#ifndef LLDB_TARGET_REGISTERCONTEXTMEMORY_H
+#define LLDB_TARGET_REGISTERCONTEXTMEMORY_H
 
 #include <vector>
 
@@ -72,4 +72,4 @@ class RegisterContextMemory : public lldb_private::RegisterContext {
   operator=(const RegisterContextMemory &) = delete;
 };
 
-#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H
+#endif // LLDB_TARGET_REGISTERCONTEXTMEMORY_H
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index cdbe8ae3c6779..9cfbf58df0e2d 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -442,7 +442,10 @@ class StackFrame : public ExecutionContextScope,
   uint32_t GetFrameIndex() const;
 
   /// Set this frame's synthetic frame index.
-  void SetFrameIndex(uint32_t index) { m_frame_index = index; }
+  void SetFrameIndex(uint32_t index) {
+    m_frame_index = index;
+    m_concrete_frame_index = index;
+  }
 
   /// Query this frame to find what frame it is in this Thread's
   /// StackFrameList, not counting inlined frames.
diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h
index ea9aab86b8ea1..c99f9fd27dd3a 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -103,6 +103,7 @@ class StackFrameList {
 
 protected:
   friend class Thread;
+  friend class ScriptedFrameProvider;
   friend class ScriptedThread;
 
   /// Use this API to build a stack frame list (used for scripted threads, for
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index 688c056da2633..a5e3bb53cccb4 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -1295,6 +1295,10 @@ class Thread : public std::enable_shared_from_this<Thread>,
   ///     an empty std::optional is returned in that case.
   std::optional<lldb::addr_t> GetPreviousFrameZeroPC();
 
+  Status SetScriptedFrameProvider(const ScriptedMetadata &scripted_metadata);
+
+  void ClearScriptedFrameProvider();
+
 protected:
   friend class ThreadPlan;
   friend class ThreadList;
@@ -1338,6 +1342,8 @@ class Thread : public std::enable_shared_from_this<Thread>,
 
   lldb::StackFrameListSP GetStackFrameList();
 
+  llvm::Expected<lldb::StackFrameListSP> GetScriptedFrameList();
+
   void SetTemporaryResumeState(lldb::StateType new_state) {
     m_temporary_resume_state = new_state;
   }
@@ -1400,6 +1406,9 @@ 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::ScriptedFrameProviderSP m_frame_provider_sp;
+
 private:
   bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info
                                 // for this thread?
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index fec9fdef44df9..d52823f3674c0 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -266,6 +266,19 @@ enum StopReason {
   eStopReasonHistoryBoundary,
 };
 
+/// Scripted Frame Provider Merge Strategies.
+enum ScriptedFrameProviderMergeStrategy {
+  /// Replace the entire stack with scripted frames
+  eScriptedFrameProviderMergeStrategyReplace = 0,
+  /// Prepend scripted frames before real unwound frames
+  eScriptedFrameProviderMergeStrategyPrepend,
+  /// Append scripted frames after real unwound frames
+  eScriptedFrameProviderMergeStrategyAppend,
+  /// Replace specific frame indices with scripted frames, keeping other real
+  /// frames
+  eScriptedFrameProviderMergeStrategyReplaceByIndex,
+};
+
 /// Command Return Status Types.
 enum ReturnStatus {
   eReturnStatusInvalid,
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index af5656b3dcad1..85045a803b07a 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -188,6 +188,8 @@ class Scalar;
 class ScriptInterpreter;
 class ScriptInterpreterLocker;
 class ScriptedFrameInterface;
+class ScriptedFrameProvider;
+class ScriptedFrameProviderInterface;
 class ScriptedMetadata;
 class ScriptedBreakpointInterface;
 class ScriptedPlatformInterface;
@@ -411,6 +413,10 @@ typedef std::shared_ptr<lldb_private::ScriptSummaryFormat>
 typedef std::shared_ptr<lldb_private::ScriptInterpreter> ScriptInterpreterSP;
 typedef std::shared_ptr<lldb_private::ScriptedFrameInterface>
     ScriptedFrameInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedFrameProvider>
+    ScriptedFrameProviderSP;
+typedef std::shared_ptr<lldb_private::ScriptedFrameProviderInterface>
+    ScriptedFrameProviderInterfaceSP;
 typedef std::shared_ptr<lldb_private::ScriptedMetadata> ScriptedMetadataSP;
 typedef std::unique_ptr<lldb_private::ScriptedPlatformInterface>
     ScriptedPlatformInterfaceUP;
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index 4e4aa48bc9a2e..d7b44a3926e15 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -39,6 +39,7 @@
 #include "lldb/Target/ThreadPlanStepOut.h"
 #include "lldb/Target/ThreadPlanStepRange.h"
 #include "lldb/Utility/Instrumentation.h"
+#include "lldb/Utility/ScriptedMetadata.h"
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/Utility/StructuredData.h"
@@ -1324,3 +1325,32 @@ SBValue SBThread::GetSiginfo() {
     return SBValue();
   return thread_sp->GetSiginfoValue();
 }
+
+SBError SBThread::RegisterFrameProvider(const char *class_name,
+                                        SBStructuredData &dict) {
+  LLDB_INSTRUMENT_VA(this, class_name, dict);
+
+  ThreadSP thread_sp = m_opaque_sp->GetThreadSP();
+  if (!thread_sp)
+    return SBError("invalid thread");
+
+  if (!dict.m_impl_up)
+    return SBError("invalid dictionary");
+
+  StructuredData::DictionarySP dict_sp =
+      std::make_shared<StructuredData::Dictionary>(
+          dict.m_impl_up->GetObjectSP());
+  if (!dict_sp)
+    return SBError("invalid dictionary");
+
+  ScriptedMetadata metadata(class_name, dict_sp);
+  return SBError(thread_sp->SetScriptedFrameProvider(metadata));
+}
+
+void SBThread::ClearScriptedFrameProvider() {
+  LLDB_INSTRUMENT_VA(this);
+
+  ThreadSP thread_sp = m_opaque_sp->GetThreadSP();
+  if (thread_sp)
+    thread_sp->ClearScriptedFrameProvider();
+}
diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp
index 88a02dce35b9d..7dabc8f22b8ad 100644
--- a/lldb/source/Commands/CommandObjectFrame.cpp
+++ b/lldb/source/Commands/CommandObjectFrame.cpp
@@ -16,6 +16,7 @@
 #include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Interpreter/OptionArgParser.h"
 #include "lldb/Interpreter/OptionGroupFormat.h"
+#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h"
 #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h"
 #include "lldb/Interpreter/OptionGroupVariable.h"
 #include "lldb/Interpreter/Options.h"
@@ -29,6 +30,7 @@
 #include "lldb/Target/Target.h"
 #include "lldb/Target/Thread.h"
 #include "lldb/Utility/Args.h"
+#include "lldb/Utility/ScriptedMetadata.h"
 #include "lldb/ValueObject/ValueObject.h"
 
 #include <memory>
@@ -1223,6 +1225,98 @@ class CommandObjectFrameRecognizer : public CommandObjectMultiword {
   ~CommandObjectFrameRecognizer() override = default;
 };
 
+#pragma mark CommandObjectFrameProvider
+
+#define LLDB_OPTIONS_frame_provider_register
+#include "CommandOptions.inc"
+
+class CommandObjectFrameProviderRegister : public CommandObjectParsed {
+public:
+  CommandObjectFrameProviderRegister(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "frame provider register",
+                            "Register frame provider into current thread.",
+                            nullptr, eCommandRequiresThread),
+
+        m_class_options("frame provider", true, 'C', 'k', 'v', 0) {
+    m_all_options.Append(&m_class_options, LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+                         LLDB_OPT_SET_ALL);
+    m_all_options.Finalize();
+
+    AddSimpleArgumentList(eArgTypeRunArgs, eArgRepeatOptional);
+  }
+
+  ~CommandObjectFrameProviderRegister() override = default;
+
+  Options *GetOptions() override { return &m_all_options; }
+
+  std::optional<std::string> GetRepeatCommand(Args &current_command_args,
+                                              uint32_t index) override {
+    // No repeat for "process launch"...
+    return std::string("");
+  }
+
+protected:
+  void DoExecute(Args &launch_args, CommandReturnObject &result) override {
+    ScriptedMetadata metadata(m_class_options.GetName(),
+                              m_class_options.GetStructuredData());
+
+    Thread *thread = m_exe_ctx.GetThreadPtr();
+    if (!thread) {
+      result.AppendError("invalid thread");
+      return;
+    }
+
+    Status error = thread->SetScriptedFrameProvider(metadata);
+    if (error.Success())
+      result.AppendMessageWithFormat(
+          "Successfully registered scripted frame provider '%s'\n",
+          m_class_options.GetName().c_str());
+    result.SetError(std::move(error));
+  }
+
+  OptionGroupPythonClassWithDict m_class_options;
+  OptionGroupOptions m_all_options;
+};
+
+class CommandObjectFrameProviderClear : public CommandObjectParsed {
+public:
+  CommandObjectFrameProviderClear(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "frame provider clear",
+                            "Delete registered frame provider.", nullptr) {}
+
+  ~CommandObjectFrameProviderClear() override = default;
+
+protected:
+  void DoExecute(Args &command, CommandReturnObject &result) override {
+    Thread *thread = m_exe_ctx.GetThreadPtr();
+    if (!thread) {
+      result.AppendError("invalid thread");
+      return;
+    }
+
+    thread->ClearScriptedFrameProvider();
+
+    result.SetStatus(eReturnStatusSuccessFinishResult);
+  }
+};
+
+class CommandObjectFrameProvider : public CommandObjectMultiword {
+public:
+  CommandObjectFrameProvider(CommandInterpreter &interpreter)
+      : CommandObjectMultiword(
+            interpreter, "frame provider",
+            "Commands for registering and viewing frame providers.",
+            "frame provider [<sub-command-options>] ") {
+    LoadSubCommand(
+        "register",
+        CommandObjectSP(new CommandObjectFrameProviderRegister(interpreter)));
+    LoadSubCommand("clear", CommandObjectSP(new CommandObjectFrameProviderClear(
+                                interpreter)));
+  }
+
+  ~CommandObjectFrameProvider() override = default;
+};
+
 #pragma mark CommandObjectMultiwordFrame
 
 // CommandObjectMultiwordFrame
@@ -1243,6 +1337,8 @@ CommandObjectMultiwordFrame::CommandObjectMultiwordFrame(
   LoadSubCommand("variable",
                  CommandObjectSP(new CommandObjectFrameVariable(interpreter)));
 #if LLDB_ENABLE_PYTHON
+  LoadSubCommand("provider",
+                 CommandObjectSP(new CommandObjectFrameProvider(interpreter)));
   LoadSubCommand("recognizer", CommandObjectSP(new CommandObjectFrameRecognizer(
                                    interpreter)));
 #endif
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index bbec714642ec9..0092151a13dd8 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -35,6 +35,7 @@
 #include "lldb/Target/ThreadPlanStepInRange.h"
 #include "lldb/Target/Trace.h"
 #include "lldb/Target/TraceDumper.h"
+#include "lldb/Utility/ScriptedMetadata.h"
 #include "lldb/Utility/State.h"
 #include "lldb/ValueObject/ValueObject.h"
 
diff --git a/lldb/source/Interpreter/CMakeLists.txt b/lldb/source/Interpreter/CMakeLists.txt
index 8af7373702c38..7efc5b4efad89 100644
--- a/lldb/source/Interpreter/CMakeLists.txt
+++ b/lldb/source/Interpreter/CMakeLists.txt
@@ -53,6 +53,8 @@ add_lldb_library(lldbInterpreter NO_PLUGIN_DEPENDENCIES
   OptionGroupWatchpoint.cpp
   Options.cpp
   Property.cpp
+  ScriptedFrame.cpp
+  ScriptedFrameProvider.cpp
   ScriptInterpreter.cpp
 
   ADDITIONAL_HEADER_DIRS
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Interpreter/ScriptedFrame.cpp
similarity index 66%
rename from lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
rename to lldb/source/Interpreter/ScriptedFrame.cpp
index 6519df9185df0..2c31a2a2b6e72 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Interpreter/ScriptedFrame.cpp
@@ -1,4 +1,4 @@
-//===----------------------------------------------------------------------===//
+//===-- ScriptedFrame.cpp -------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,9 +6,23 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "ScriptedFrame.h"
-
+#include "lldb/Interpreter/ScriptedFrame.h"
+
+#include "lldb/Core/Address.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
+#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/RegisterContextMemory.h"
+#include "lldb/Target/Thread.h"
 #include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/StructuredData.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -19,42 +33,56 @@ void ScriptedFrame::CheckInterpreterAndScriptObject() const {
 }
 
 llvm::Expected<std::shared_ptr<ScriptedFrame>>
-ScriptedFrame::Create(ScriptedThread &thread,
+ScriptedFrame::Create(ThreadSP thread_sp,
+                      ScriptedThreadInterfaceSP scripted_thread_interface_sp,
                       StructuredData::DictionarySP args_sp,
                       StructuredData::Generic *script_object) {
-  if (!thread.IsValid())
-    return llvm::createStringError("Invalid scripted thread.");
+  if (!thread_sp || !thread_sp->IsValid())
+    return llvm::createStringError("Invalid thread.");
+
+  ProcessSP process_sp = thread_sp->GetProcess();
+  if (!process_sp || !process_sp->IsValid())
+    return llvm::createStringError("Invalid process.");
 
-  thread.CheckInterpreterAndScriptObject();
+  ScriptInterpreter *script_interp =
+      process_sp->GetTarget().GetDebugger().GetScriptInterpreter();
+  if (!script_interp)
+    return llvm::createStringError("No script interpreter.");
 
-  auto scripted_frame_interface =
-      thread.GetInterface()->CreateScriptedFrameInterface();
+  auto scripted_frame_interface = script_interp->CreateScriptedFrameInterface();
   if (!scripted_frame_interface)
-    return llvm::createStringError("failed to create scripted frame interface");
+    return llvm::createStringError("Failed to create scripted frame interface");
 
   llvm::StringRef frame_class_name;
   if (!script_object) {
-    std::optional<std::string> class_name =
-        thread.GetInterface()->GetScriptedFramePluginName();
-    if (!class_name || class_name->empty())
+    // If no script object is provided and we have a scripted thread interface,
+    // try to get the frame class name from it
+    if (scripted_thread_interface_sp) {
+      std::optional<std::string> class_name =
+          scripted_thread_interface_sp->GetScriptedFramePluginName();
+      if (!class_name || class_name->empty())
+        return llvm::createStringError(
+            "Failed to get scripted frame class name");
+      frame_class_name = *class_name;
+    } else {
       return llvm::createStringError(
-          "failed to get scripted thread class name");
-    frame_class_name = *class_name;
+          "No script object provided and no scripted thread interface");
+    }
   }
 
-  ExecutionContext exe_ctx(thread);
+  ExecutionContext exe_ctx(thread_sp);
   auto obj_or_err = scripted_frame_interface->CreatePluginObject(
       frame_class_name, exe_ctx, args_sp, script_object);
 
   if (!obj_or_err)
     return llvm::createStringError(
-        "failed to create script object: %s",
+        "Failed to create script object: %s",
         llvm::toString(obj_or_err.takeError()).c_str());
 
   StructuredData::GenericSP owned_script_object_sp = *obj_or_err;
 
   if (!owned_script_object_sp->IsValid())
-    return llvm::createStringError("created script object is invalid");
+    return llvm::createStringError("Created script object is invalid");
 
   lldb::user_id_t frame_id = scripted_frame_interface->GetID();
 
@@ -62,7 +90,7 @@ ScriptedFrame::Create(ScriptedThread &thread,
   SymbolContext sc;
   Address symbol_addr;
   if (pc != LLDB_INVALID_ADDRESS) {
-    symbol_addr.SetLoadAddress(pc, &thread.GetProcess()->GetTarget());
+    symbol_addr.SetLoadAddress(pc, &process_sp->GetTarget());
     symbol_addr.CalculateSymbolContext(&sc);
   }
 
@@ -77,11 +105,11 @@ ScriptedFrame::Create(ScriptedThread &thread,
 
   if (!reg_info)
     return llvm::createStringError(
-        "failed to get scripted thread registers info");
+        "Failed to get scripted frame registers info");
 
   std::shared_ptr<DynamicRegisterInfo> register_info_sp =
-      DynamicRegisterInfo::Create(
-          *reg_info, thread.GetProcess()->GetTarget().GetArchitecture());
+      DynamicRegisterInfo::Create(*reg_info,
+                                  process_sp->GetTarget().GetArchitecture());
 
   lldb::RegisterContextSP reg_ctx_sp;
 
@@ -92,31 +120,31 @@ ScriptedFrame::Create(ScriptedThread &thread,
         std::make_shared<DataBufferHeap>(reg_data->c_str(), reg_data->size()));
 
     if (!data_sp->GetByteSize())
-      return llvm::createStringError("failed to copy raw registers data");
+      return llvm::createStringError("Failed to copy raw registers data");
 
     std::shared_ptr<RegisterContextMemory> reg_ctx_memory =
         std::make_shared<RegisterContextMemory>(
-            thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS);
+            *thread_sp, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS);
     if (!reg_ctx_memory)
-      return llvm::createStringError("failed to create a register context.");
+      return llvm::createStringError("Failed to create a register context.");
 
     reg_ctx_memory->SetAllRegisterData(data_sp);
     reg_ctx_sp = reg_ctx_memory;
   }
 
   return std::make_shared<ScriptedFrame>(
-      thread, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
+      thread_sp, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
       register_info_sp, owned_script_object_sp);
 }
 
-ScriptedFrame::ScriptedFrame(ScriptedThread &thread,
+ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
                              ScriptedFrameInterfaceSP interface_sp,
                              lldb::user_id_t id, lldb::addr_t pc,
                              SymbolContext &sym_ctx,
                              lldb::RegisterContextSP reg_ctx_sp,
                              std::shared_ptr<DynamicRegisterInfo> reg_info_sp,
                              StructuredData::GenericSP script_object_sp)
-    : StackFrame(thread.shared_from_this(), /*frame_idx=*/id,
+    : StackFrame(thread_sp, /*frame_idx=*/id,
                  /*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp,
                  /*cfa=*/0, /*pc=*/pc,
                  /*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx),
diff --git a/lldb/source/Interpreter/ScriptedFrameProvider.cpp b/lldb/source/Interpreter/ScriptedFrameProvider.cpp
new file mode 100644
index 0000000000000..4c2d8f7d41cb7
--- /dev/null
+++ b/lldb/source/Interpreter/ScriptedFrameProvider.cpp
@@ -0,0 +1,197 @@
+//===-- ScriptedFrameProvider.cpp ----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Interpreter/ScriptedFrameProvider.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Interpreter/ScriptedFrame.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/StackFrame.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Utility/ScriptedMetadata.h"
+#include "lldb/Utility/Status.h"
+#include "llvm/Support/Error.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+ScriptedFrameProvider::ScriptedFrameProvider(
+    ThreadSP thread_sp, const ScriptedMetadata &scripted_metadata,
+    Status &error)
+    : m_thread_sp(thread_sp), m_interface_sp(nullptr) {
+  if (!m_thread_sp) {
+    error = Status::FromErrorString(
+        "cannot create scripted frame provider: Invalid thread");
+    return;
+  }
+
+  ProcessSP process_sp = m_thread_sp->GetProcess();
+  if (!process_sp) {
+    error = Status::FromErrorString(
+        "cannot create scripted frame provider: Invalid process");
+    return;
+  }
+
+  ScriptInterpreter *script_interp =
+      process_sp->GetTarget().GetDebugger().GetScriptInterpreter();
+  if (!script_interp) {
+    error = Status::FromErrorString("cannot create scripted frame provider: No "
+                                    "script interpreter installed");
+    return;
+  }
+
+  m_interface_sp = script_interp->CreateScriptedFrameProviderInterface();
+  if (!m_interface_sp) {
+    error = Status::FromErrorString(
+        "cannot create scripted frame provider: Script interpreter couldn't "
+        "create Scripted Frame Provider Interface");
+    return;
+  }
+
+  auto obj_or_err = m_interface_sp->CreatePluginObject(
+      scripted_metadata.GetClassName(), m_thread_sp,
+      scripted_metadata.GetArgsSP());
+  if (!obj_or_err) {
+    error = Status::FromError(obj_or_err.takeError());
+    return;
+  }
+
+  StructuredData::ObjectSP object_sp = *obj_or_err;
+  if (!object_sp || !object_sp->IsValid()) {
+    error = Status::FromErrorString(
+        "cannot create scripted frame provider: Failed to create valid script "
+        "object");
+    return;
+  }
+
+  error.Clear();
+}
+
+ScriptedFrameProvider::~ScriptedFrameProvider() = default;
+
+lldb::ScriptedFrameProviderMergeStrategy
+ScriptedFrameProvider::GetMergeStrategy() {
+  if (!m_interface_sp)
+    return lldb::eScriptedFrameProviderMergeStrategyReplace;
+
+  return m_interface_sp->GetMergeStrategy();
+}
+
+llvm::Expected<StackFrameListSP>
+ScriptedFrameProvider::GetStackFrames(StackFrameListSP real_frames) {
+  if (!m_interface_sp)
+    return llvm::createStringError(
+        "cannot get stack frames: Scripted frame provider not initialized");
+
+  StructuredData::ArraySP arr_sp = m_interface_sp->GetStackFrames(real_frames);
+
+  Status error;
+  if (!arr_sp)
+    return llvm::createStringError(
+        "Failed to get scripted thread stackframes.");
+
+  size_t arr_size = arr_sp->GetSize();
+  if (arr_size > std::numeric_limits<uint32_t>::max())
+    return llvm::createStringError(llvm::Twine(
+        "StackFrame array size (" + llvm::Twine(arr_size) +
+        llvm::Twine(
+            ") is greater than maximum authorized for a StackFrameList.")));
+
+  auto create_frame_from_dict =
+      [this, arr_sp](size_t iteration_idx) -> llvm::Expected<StackFrameSP> {
+    std::optional<StructuredData::Dictionary *> maybe_dict =
+        arr_sp->GetItemAtIndexAsDictionary(iteration_idx);
+    if (!maybe_dict)
+      return llvm::createStringError("invalid scripted frame dictionary.");
+    StructuredData::Dictionary *dict = *maybe_dict;
+
+    lldb::addr_t pc;
+    if (!dict->GetValueForKeyAsInteger("pc", pc))
+      return llvm::createStringError(
+          "missing 'pc' key from scripted frame dictionary.");
+
+    // For ReplaceByIndex strategy, use the idx from dictionary if provided
+    // For other strategies (Replace, Prepend, Append), use iteration index
+    uint64_t frame_idx = iteration_idx;
+    if (!dict->GetValueForKeyAsInteger("idx", frame_idx))
+      return llvm::createStringError(
+          "missing 'idx' key from scripted frame dictionary.");
+
+    Address symbol_addr;
+    symbol_addr.SetLoadAddress(pc, &m_thread_sp->GetProcess()->GetTarget());
+
+    lldb::addr_t cfa = LLDB_INVALID_ADDRESS;
+    bool cfa_is_valid = false;
+    const bool artificial = false;
+    const bool behaves_like_zeroth_frame = false;
+    SymbolContext sc;
+    symbol_addr.CalculateSymbolContext(&sc);
+
+    return std::make_shared<StackFrame>(m_thread_sp, frame_idx, frame_idx, cfa,
+                                        cfa_is_valid, pc,
+                                        StackFrame::Kind::Synthetic, artificial,
+                                        behaves_like_zeroth_frame, &sc);
+  };
+
+  auto create_frame_from_script_object =
+      [this, arr_sp](size_t idx) -> llvm::Expected<StackFrameSP> {
+    Status error;
+    StructuredData::ObjectSP object_sp = arr_sp->GetItemAtIndex(idx);
+    if (!object_sp || !object_sp->GetAsGeneric())
+      return error.ToError();
+
+    auto frame_or_error = ScriptedFrame::Create(m_thread_sp, nullptr, nullptr,
+                                                object_sp->GetAsGeneric());
+
+    if (!frame_or_error) {
+      ScriptedInterface::ErrorWithMessage<bool>(
+          LLVM_PRETTY_FUNCTION, toString(frame_or_error.takeError()), error);
+      return error.ToError();
+    }
+
+    StackFrameSP frame_sp = frame_or_error.get();
+    lldbassert(frame_sp && "Couldn't initialize scripted frame.");
+
+    return frame_sp;
+  };
+
+  StackFrameListSP scripted_frames =
+      std::make_shared<StackFrameList>(*m_thread_sp, StackFrameListSP(), true);
+
+  for (size_t idx = 0; idx < arr_size; idx++) {
+    StackFrameSP synth_frame_sp = nullptr;
+
+    auto frame_from_dict_or_err = create_frame_from_dict(idx);
+    if (!frame_from_dict_or_err) {
+      auto frame_from_script_obj_or_err = create_frame_from_script_object(idx);
+
+      if (!frame_from_script_obj_or_err) {
+        return llvm::createStringError(
+            llvm::Twine("Couldn't add artificial frame (" + llvm::Twine(idx) +
+                        llvm::Twine(") to ScriptedThread StackFrameList.")));
+      } else {
+        llvm::consumeError(frame_from_dict_or_err.takeError());
+        synth_frame_sp = *frame_from_script_obj_or_err;
+      }
+    } else {
+      synth_frame_sp = *frame_from_dict_or_err;
+    }
+
+    if (!scripted_frames->SetFrameAtIndex(static_cast<uint32_t>(idx),
+                                          synth_frame_sp))
+      return llvm::createStringError(
+          llvm::Twine("Couldn't add frame (" + llvm::Twine(idx) +
+                      llvm::Twine(") to ScriptedThread StackFrameList.")));
+  }
+
+  // Mark that all frames have been fetched to prevent automatic unwinding
+  scripted_frames->SetAllFramesFetched();
+
+  return scripted_frames;
+}
diff --git a/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp b/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp
index 96b2b9d9ee088..5c559b36b555d 100644
--- a/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp
+++ b/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp
@@ -13,7 +13,6 @@
 #include "OperatingSystemPython.h"
 
 #include "Plugins/Process/Utility/RegisterContextDummy.h"
-#include "Plugins/Process/Utility/RegisterContextMemory.h"
 #include "Plugins/Process/Utility/ThreadMemory.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Module.h"
@@ -23,6 +22,7 @@
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/Process.h"
+#include "lldb/Target/RegisterContextMemory.h"
 #include "lldb/Target/StopInfo.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Target/Thread.h"
diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt
index b1e326ec064e4..2f07cf2de6dd3 100644
--- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt
@@ -36,7 +36,6 @@ add_lldb_library(lldbPluginProcessUtility
   RegisterContextLinux_s390x.cpp
   RegisterContextMach_arm.cpp
   RegisterContextMach_x86_64.cpp
-  RegisterContextMemory.cpp
   RegisterContextNetBSD_i386.cpp
   RegisterContextNetBSD_x86_64.cpp
   RegisterContextOpenBSD_i386.cpp
diff --git a/lldb/source/Plugins/Process/scripted/CMakeLists.txt b/lldb/source/Plugins/Process/scripted/CMakeLists.txt
index 1516ad3132e3b..590166591a41e 100644
--- a/lldb/source/Plugins/Process/scripted/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/scripted/CMakeLists.txt
@@ -1,7 +1,6 @@
 add_lldb_library(lldbPluginScriptedProcess PLUGIN
   ScriptedProcess.cpp
   ScriptedThread.cpp
-  ScriptedFrame.cpp
 
   LINK_COMPONENTS
     BinaryFormat
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
index 491efac5aadef..5ad2a896e6129 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
@@ -7,10 +7,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "ScriptedThread.h"
-#include "ScriptedFrame.h"
 
 #include "Plugins/Process/Utility/RegisterContextThreadMemory.h"
 #include "Plugins/Process/Utility/StopInfoMachException.h"
+#include "lldb/Interpreter/ScriptedFrame.h"
 #include "lldb/Target/OperatingSystem.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/RegisterContext.h"
@@ -232,7 +232,8 @@ bool ScriptedThread::LoadArtificialStackFrames() {
     }
 
     auto frame_or_error =
-        ScriptedFrame::Create(*this, nullptr, object_sp->GetAsGeneric());
+        ScriptedFrame::Create(this->shared_from_this(), GetInterface(), nullptr,
+                              object_sp->GetAsGeneric());
 
     if (!frame_or_error) {
       ScriptedInterface::ErrorWithMessage<bool>(
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.h b/lldb/source/Plugins/Process/scripted/ScriptedThread.h
index ee5ace4059673..a66d344f8316a 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedThread.h
+++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.h
@@ -13,9 +13,9 @@
 
 #include "ScriptedProcess.h"
 
-#include "Plugins/Process/Utility/RegisterContextMemory.h"
 #include "lldb/Interpreter/ScriptInterpreter.h"
 #include "lldb/Target/DynamicRegisterInfo.h"
+#include "lldb/Target/RegisterContextMemory.h"
 #include "lldb/Target/Thread.h"
 
 namespace lldb_private {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt
index 09103573b89c5..50569cdefaafa 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt
@@ -23,6 +23,7 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN
   OperatingSystemPythonInterface.cpp
   ScriptInterpreterPythonInterfaces.cpp
   ScriptedFramePythonInterface.cpp
+  ScriptedFrameProviderPythonInterface.cpp
   ScriptedPlatformPythonInterface.cpp
   ScriptedProcessPythonInterface.cpp
   ScriptedPythonInterface.cpp
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
index 3814f46615078..b2a347951d0f2 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
@@ -17,6 +17,7 @@
 
 #include "OperatingSystemPythonInterface.h"
 #include "ScriptedBreakpointPythonInterface.h"
+#include "ScriptedFrameProviderPythonInterface.h"
 #include "ScriptedFramePythonInterface.h"
 #include "ScriptedPlatformPythonInterface.h"
 #include "ScriptedProcessPythonInterface.h"
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
new file mode 100644
index 0000000000000..16336db351897
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
@@ -0,0 +1,71 @@
+//===-- ScriptedFrameProviderPythonInterface.cpp -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/lldb-enumerations.h"
+
+#if LLDB_ENABLE_PYTHON
+
+// LLDB Python header must be included first
+#include "../lldb-python.h"
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "ScriptedFrameProviderPythonInterface.h"
+#include <optional>
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+using Locker = ScriptInterpreterPythonImpl::Locker;
+
+ScriptedFrameProviderPythonInterface::ScriptedFrameProviderPythonInterface(
+    ScriptInterpreterPythonImpl &interpreter)
+    : ScriptedFrameProviderInterface(), ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+ScriptedFrameProviderPythonInterface::CreatePluginObject(
+    const llvm::StringRef class_name, lldb::ThreadSP thread_sp,
+    StructuredData::DictionarySP args_sp) {
+  if (!thread_sp)
+    return llvm::createStringError("Invalid thread");
+
+  StructuredDataImpl sd_impl(args_sp);
+  return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr,
+                                                     thread_sp, sd_impl);
+}
+
+lldb::ScriptedFrameProviderMergeStrategy
+ScriptedFrameProviderPythonInterface::GetMergeStrategy() {
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("get_merge_strategy", error);
+
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return {};
+
+  return static_cast<lldb::ScriptedFrameProviderMergeStrategy>(
+      obj->GetUnsignedIntegerValue());
+}
+
+StructuredData::ArraySP ScriptedFrameProviderPythonInterface::GetStackFrames(
+    lldb::StackFrameListSP real_frames) {
+  Status error;
+  StructuredData::ArraySP arr =
+      Dispatch<StructuredData::ArraySP>("get_stackframes", error);
+
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, arr,
+                                                    error))
+    return {};
+
+  return arr;
+}
+
+#endif
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
new file mode 100644
index 0000000000000..9f5db55560ee9
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
@@ -0,0 +1,45 @@
+//===-- ScriptedFrameProviderPythonInterface.h -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H
+#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "ScriptedPythonInterface.h"
+#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
+#include <optional>
+
+namespace lldb_private {
+class ScriptedFrameProviderPythonInterface
+    : public ScriptedFrameProviderInterface,
+      public ScriptedPythonInterface {
+public:
+  ScriptedFrameProviderPythonInterface(
+      ScriptInterpreterPythonImpl &interpreter);
+
+  llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name, lldb::ThreadSP thread_sp,
+                     StructuredData::DictionarySP args_sp) override;
+
+  llvm::SmallVector<AbstractMethodRequirement>
+  GetAbstractMethodRequirements() const override {
+    return llvm::SmallVector<AbstractMethodRequirement>({{"get_stackframes"}});
+  }
+
+  lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy() override;
+
+  StructuredData::ArraySP
+  GetStackFrames(lldb::StackFrameListSP real_frames) override;
+};
+} // namespace lldb_private
+
+#endif // LLDB_ENABLE_PYTHON
+#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index f769d3d29add7..92b28c051c290 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -440,6 +440,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     return python::SWIGBridge::ToSWIGWrapper(arg);
   }
 
+  python::PythonObject Transform(lldb::ThreadSP arg) {
+    return python::SWIGBridge::ToSWIGWrapper(arg);
+  }
+
   python::PythonObject Transform(lldb::ThreadPlanSP arg) {
     return python::SWIGBridge::ToSWIGWrapper(arg);
   }
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 73c5c72932ff1..9ef5ac4acb6a3 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1526,6 +1526,11 @@ ScriptInterpreterPythonImpl::CreateScriptedFrameInterface() {
   return std::make_shared<ScriptedFramePythonInterface>(*this);
 }
 
+ScriptedFrameProviderInterfaceSP
+ScriptInterpreterPythonImpl::CreateScriptedFrameProviderInterface() {
+  return std::make_shared<ScriptedFrameProviderPythonInterface>(*this);
+}
+
 ScriptedThreadPlanInterfaceSP
 ScriptInterpreterPythonImpl::CreateScriptedThreadPlanInterface() {
   return std::make_shared<ScriptedThreadPlanPythonInterface>(*this);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index dedac280788f4..0dd5ae52c8955 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -101,6 +101,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
 
   lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override;
 
+  lldb::ScriptedFrameProviderInterfaceSP
+  CreateScriptedFrameProviderInterface() override;
+
   lldb::ScriptedThreadPlanInterfaceSP
   CreateScriptedThreadPlanInterface() override;
 
diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt
index b7788e80eecac..f270ec0ce5300 100644
--- a/lldb/source/Target/CMakeLists.txt
+++ b/lldb/source/Target/CMakeLists.txt
@@ -33,6 +33,7 @@ add_lldb_library(lldbTarget
   QueueItem.cpp
   QueueList.cpp
   RegisterContext.cpp
+  RegisterContextMemory.cpp
   RegisterContextUnwind.cpp
   RegisterFlags.cpp
   RegisterNumber.cpp
diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.cpp b/lldb/source/Target/RegisterContextMemory.cpp
similarity index 99%
rename from lldb/source/Plugins/Process/Utility/RegisterContextMemory.cpp
rename to lldb/source/Target/RegisterContextMemory.cpp
index 84a19d5b13035..245b90de75cd3 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.cpp
+++ b/lldb/source/Target/RegisterContextMemory.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "RegisterContextMemory.h"
+#include "lldb/Target/RegisterContextMemory.h"
 
 #include "lldb/Target/Process.h"
 #include "lldb/Target/Thread.h"
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 8c3e19725f8cb..3551813f384a1 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -13,9 +13,13 @@
 #include "lldb/Core/Module.h"
 #include "lldb/Core/StructuredDataImpl.h"
 #include "lldb/Host/Host.h"
+#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
+#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
 #include "lldb/Interpreter/OptionValueFileSpecList.h"
 #include "lldb/Interpreter/OptionValueProperties.h"
 #include "lldb/Interpreter/Property.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Interpreter/ScriptedFrameProvider.h"
 #include "lldb/Symbol/Function.h"
 #include "lldb/Target/ABI.h"
 #include "lldb/Target/DynamicLoader.h"
@@ -45,6 +49,7 @@
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/RegularExpression.h"
+#include "lldb/Utility/ScriptedMetadata.h"
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/Utility/StreamString.h"
@@ -1439,13 +1444,135 @@ void Thread::CalculateExecutionContext(ExecutionContext &exe_ctx) {
 StackFrameListSP Thread::GetStackFrameList() {
   std::lock_guard<std::recursive_mutex> guard(m_frame_mutex);
 
-  if (!m_curr_frames_sp)
-    m_curr_frames_sp =
+  if (!m_curr_frames_sp) {
+
+    StackFrameListSP real_frames_sp =
         std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true);
 
+    if (m_frame_provider_sp) {
+      lldb::ScriptedFrameProviderMergeStrategy strategy =
+          m_frame_provider_sp->GetMergeStrategy();
+
+      auto scripted_list_or_err = GetScriptedFrameList();
+      if (!scripted_list_or_err) {
+        LLDB_LOG_ERROR(GetLog(LLDBLog::Thread),
+                       scripted_list_or_err.takeError(),
+                       "Failed to get scripted frame list: {0}");
+        m_curr_frames_sp = real_frames_sp;
+        return m_curr_frames_sp;
+      }
+
+      StackFrameListSP scripted_frames_sp = *scripted_list_or_err;
+      uint32_t num_real = real_frames_sp->GetNumFrames(true);
+      uint32_t num_scripted = scripted_frames_sp->GetNumFrames(false);
+
+      switch (strategy) {
+      case lldb::eScriptedFrameProviderMergeStrategyReplace: {
+        m_curr_frames_sp = *scripted_list_or_err;
+        return m_curr_frames_sp;
+      }
+
+      case lldb::eScriptedFrameProviderMergeStrategyReplaceByIndex: {
+        // Create normal frame list first
+        for (uint32_t i = 0; i < num_scripted; i++) {
+          StackFrameSP scripted_frame = scripted_frames_sp->GetFrameAtIndex(i);
+          if (scripted_frame) {
+            uint32_t frame_idx = scripted_frame->GetFrameIndex();
+            m_curr_frames_sp->SetFrameAtIndex(frame_idx, scripted_frame);
+          }
+        }
+        return m_curr_frames_sp;
+      }
+
+      case lldb::eScriptedFrameProviderMergeStrategyPrepend: {
+        // Prepend: Scripted frames go first (0..n-1), real frames follow
+        // (n..n+m-1) Start with scripted frames and add adjusted real frames
+        m_curr_frames_sp = scripted_frames_sp;
+
+        // Real frames need to be shifted by num_scripted
+        for (uint32_t i = 0; i < num_real; i++) {
+          StackFrameSP real_frame = real_frames_sp->GetFrameAtIndex(i);
+          if (real_frame) {
+            uint32_t new_idx = num_scripted + i;
+            real_frame->SetFrameIndex(new_idx);
+            m_curr_frames_sp->SetFrameAtIndex(new_idx, real_frame);
+          }
+        }
+
+        m_curr_frames_sp->SetAllFramesFetched();
+        return m_curr_frames_sp;
+      }
+
+      case lldb::eScriptedFrameProviderMergeStrategyAppend: {
+        // Append: Real frames go first (0..m-1), scripted frames follow
+        // (m..m+n-1)
+        m_curr_frames_sp = real_frames_sp;
+
+        // Scripted frames should be at indices m, m+1, ...
+        // But they were created with indices 0, 1, ...
+        // Update their indices to place them after real frames
+        for (uint32_t i = 0; i < num_scripted; i++) {
+          StackFrameSP scripted_frame = scripted_frames_sp->GetFrameAtIndex(i);
+          if (scripted_frame) {
+            uint32_t new_idx = num_real + i;
+            scripted_frame->SetFrameIndex(new_idx);
+            m_curr_frames_sp->SetFrameAtIndex(new_idx, scripted_frame);
+          }
+        }
+
+        m_curr_frames_sp->SetAllFramesFetched();
+        return m_curr_frames_sp;
+      }
+      }
+    }
+
+    m_curr_frames_sp = real_frames_sp;
+  }
+
   return m_curr_frames_sp;
 }
 
+llvm::Expected<StackFrameListSP> Thread::GetScriptedFrameList() {
+  std::lock_guard<std::recursive_mutex> guard(m_frame_mutex);
+
+  if (!m_frame_provider_sp)
+    return llvm::createStringError("No scripted frame provider has been set");
+
+  return m_frame_provider_sp->GetStackFrames(m_curr_frames_sp);
+}
+
+Status
+Thread::SetScriptedFrameProvider(const ScriptedMetadata &scripted_metadata) {
+  std::lock_guard<std::recursive_mutex> guard(m_frame_mutex);
+
+  Status error;
+  m_frame_provider_sp = std::make_shared<ScriptedFrameProvider>(
+      shared_from_this(), scripted_metadata, error);
+
+  if (error.Fail()) {
+    m_frame_provider_sp.reset();
+    return error;
+  }
+
+  // Clear cached frames to ensure scripted frames are used
+  if (m_curr_frames_sp)
+    m_curr_frames_sp.reset();
+  if (m_prev_frames_sp)
+    m_prev_frames_sp.reset();
+
+  return {};
+}
+
+void Thread::ClearScriptedFrameProvider() {
+  std::lock_guard<std::recursive_mutex> guard(m_frame_mutex);
+  if (m_frame_provider_sp)
+    m_frame_provider_sp.reset();
+  if (m_curr_frames_sp)
+    m_curr_frames_sp.reset();
+  if (m_prev_frames_sp)
+    m_prev_frames_sp.reset();
+}
+
 std::optional<addr_t> Thread::GetPreviousFrameZeroPC() {
   return m_prev_framezero_pc;
 }
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/Makefile
new file mode 100644
index 0000000000000..451278a0946ef
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
\ No newline at end of file
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
new file mode 100644
index 0000000000000..d776dcf673a24
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -0,0 +1,248 @@
+"""
+Test scripted frame provider functionality with all merge strategies.
+"""
+
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class ScriptedFrameProviderTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def setUp(self):
+        TestBase.setUp(self)
+        self.source = "main.c"
+
+    def test_replace_strategy(self):
+        """Test that Replace strategy replaces entire stack."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source)
+        )
+
+        # Import the test frame provider
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        # Attach the Replace provider
+        thread.RegisterFrameProvider(
+            "test_frame_providers.ReplaceFrameProvider", lldb.SBStructuredData()
+        )
+
+        # Verify we have exactly 3 synthetic frames
+        self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames")
+
+        # Verify frame indices and PCs (dictionary-based frames don't have custom function names)
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertIsNotNone(frame0)
+        self.assertEqual(frame0.GetPC(), 0x1000)
+
+        frame1 = thread.GetFrameAtIndex(1)
+        self.assertIsNotNone(frame1)
+        self.assertEqual(frame1.GetPC(), 0x2000)
+
+        frame2 = thread.GetFrameAtIndex(2)
+        self.assertIsNotNone(frame2)
+        self.assertEqual(frame2.GetPC(), 0x3000)
+
+    def test_prepend_strategy(self):
+        """Test that Prepend strategy adds frames before real stack."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source)
+        )
+
+        # Get original frame count and PC
+        original_frame_count = thread.GetNumFrames()
+        self.assertGreaterEqual(
+            original_frame_count, 2, "Should have at least 2 real frames"
+        )
+        original_frame_0_pc = thread.GetFrameAtIndex(0).GetPC()
+
+        # Import and attach Prepend provider
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        thread.RegisterFrameProvider(
+            "test_frame_providers.PrependFrameProvider", lldb.SBStructuredData()
+        )
+
+        # Verify we have 2 more frames
+        new_frame_count = thread.GetNumFrames()
+        self.assertEqual(new_frame_count, original_frame_count + 2)
+
+        # Verify first 2 frames are synthetic (check PCs, not function names)
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertEqual(frame0.GetPC(), original_frame_0_pc)
+
+        frame1 = thread.GetFrameAtIndex(1)
+        self.assertEqual(frame1.GetPC(), original_frame_0_pc - 0x10)
+
+        # Verify frame 2 is the original real frame 0
+        frame2 = thread.GetFrameAtIndex(2)
+        self.assertIn("foo", frame2.GetFunctionName())
+
+    def test_append_strategy(self):
+        """Test that Append strategy adds frames after real stack."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source)
+        )
+
+        # Get original frame count
+        original_frame_count = thread.GetNumFrames()
+
+        # Import and attach Append provider
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        thread.RegisterFrameProvider(
+            "test_frame_providers.AppendFrameProvider", lldb.SBStructuredData()
+        )
+
+        # Verify we have 2 more frames
+        new_frame_count = thread.GetNumFrames()
+        self.assertEqual(new_frame_count, original_frame_count + 2)
+
+        # Verify first frames are still real
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertIn("foo", frame0.GetFunctionName())
+
+        # Verify last 2 frames are synthetic (check PCs, not function names)
+        frame_n = thread.GetFrameAtIndex(new_frame_count - 2)
+        self.assertEqual(frame_n.GetPC(), 0x9000)
+
+        frame_n_plus_1 = thread.GetFrameAtIndex(new_frame_count - 1)
+        self.assertEqual(frame_n_plus_1.GetPC(), 0xA000)
+
+    def test_replace_by_index_strategy(self):
+        """Test that ReplaceByIndex strategy replaces specific frames."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source)
+        )
+
+        # Get original frame count and info
+        original_frame_count = thread.GetNumFrames()
+        self.assertGreaterEqual(original_frame_count, 3, "Need at least 3 frames")
+
+        original_frame_0_pc = thread.GetFrameAtIndex(0).GetPC()
+        original_frame_1 = thread.GetFrameAtIndex(1)
+        original_frame_1_name = original_frame_1.GetFunctionName()
+        original_frame_2_pc = thread.GetFrameAtIndex(2).GetPC()
+
+        # Import and attach ReplaceByIndex provider
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        thread.RegisterFrameProvider(
+            "test_frame_providers.ReplaceByIndexFrameProvider", lldb.SBStructuredData()
+        )
+
+        # Verify frame count unchanged
+        self.assertEqual(
+            thread.GetNumFrames(),
+            original_frame_count,
+            "Frame count should remain the same",
+        )
+
+        # Verify frame 0 is replaced (PC should match original since provider uses it)
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertIsNotNone(frame0)
+        self.assertEqual(
+            frame0.GetPC(), original_frame_0_pc, "Frame 0 should be replaced"
+        )
+
+        # Verify frame 1 is still the original (not replaced)
+        frame1 = thread.GetFrameAtIndex(1)
+        self.assertIsNotNone(frame1)
+        self.assertEqual(
+            frame1.GetFunctionName(),
+            original_frame_1_name,
+            "Frame 1 should remain unchanged",
+        )
+
+        # Verify frame 2 is replaced (PC should match original since provider uses it)
+        frame2 = thread.GetFrameAtIndex(2)
+        self.assertIsNotNone(frame2)
+        self.assertEqual(
+            frame2.GetPC(), original_frame_2_pc, "Frame 2 should be replaced"
+        )
+
+    def test_clear_frame_provider(self):
+        """Test that clearing provider restores normal unwinding."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source)
+        )
+
+        # Get original state
+        original_frame_0 = thread.GetFrameAtIndex(0)
+        original_frame_0_name = original_frame_0.GetFunctionName()
+        original_frame_count = thread.GetNumFrames()
+
+        # Import and attach provider
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        thread.RegisterFrameProvider(
+            "test_frame_providers.ReplaceFrameProvider", lldb.SBStructuredData()
+        )
+
+        # Verify frames are synthetic (3 frames with specific PCs)
+        self.assertEqual(thread.GetNumFrames(), 3)
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertEqual(frame0.GetPC(), 0x1000)
+
+        # Clear the provider
+        thread.ClearScriptedFrameProvider()
+
+        # Verify frames are back to normal
+        self.assertEqual(thread.GetNumFrames(), original_frame_count)
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertEqual(
+            frame0.GetFunctionName(),
+            original_frame_0_name,
+            "Should restore original frames after clearing provider",
+        )
+
+    def test_scripted_frame_objects(self):
+        """Test that provider can return ScriptedFrame objects."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source)
+        )
+
+        # Import the provider that returns ScriptedFrame objects
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        thread.RegisterFrameProvider(
+            "test_frame_providers.ScriptedFrameObjectProvider", lldb.SBStructuredData()
+        )
+
+        # Verify we have 3 frames
+        self.assertEqual(
+            thread.GetNumFrames(), 3, "Should have 3 custom scripted frames"
+        )
+
+        # Verify frame properties from CustomScriptedFrame
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertIsNotNone(frame0)
+        self.assertEqual(frame0.GetFunctionName(), "custom_scripted_frame_0")
+        self.assertEqual(frame0.GetPC(), 0x5000)
+        self.assertTrue(frame0.IsArtificial(), "Frame should be marked as artificial")
+
+        frame1 = thread.GetFrameAtIndex(1)
+        self.assertIsNotNone(frame1)
+        self.assertEqual(frame1.GetFunctionName(), "custom_scripted_frame_1")
+        self.assertEqual(frame1.GetPC(), 0x6000)
+
+        frame2 = thread.GetFrameAtIndex(2)
+        self.assertIsNotNone(frame2)
+        self.assertEqual(frame2.GetFunctionName(), "custom_scripted_frame_2")
+        self.assertEqual(frame2.GetPC(), 0x7000)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/main.c b/lldb/test/API/functionalities/scripted_frame_provider/main.c
new file mode 100644
index 0000000000000..3ca941fb587a5
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/main.c
@@ -0,0 +1,14 @@
+// Simple test program with a few stack frames
+
+#include <stdio.h>
+
+int foo(int x) {
+  printf("In foo: %d\n", x); // Break here
+  return x * 2;
+}
+
+int main(int argc, char **argv) {
+  int result = foo(42);
+  printf("Result: %d\n", result);
+  return 0;
+}
\ No newline at end of file
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
new file mode 100644
index 0000000000000..bb1e9b55dc5cd
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
@@ -0,0 +1,182 @@
+"""
+Test frame providers for scripted frame provider functionality.
+
+These providers exercise all merge strategies:
+- Replace: Replace entire stack
+- Prepend: Add frames before real stack
+- Append: Add frames after real stack
+- ReplaceByIndex: Replace specific frames by index
+"""
+
+import lldb
+from lldb.plugins.scripted_process import ScriptedFrame
+from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
+
+
+class ReplaceFrameProvider(ScriptedFrameProvider):
+    """Replace entire stack with custom frames."""
+
+    def __init__(self, thread, args):
+        super().__init__(thread, args)
+
+    def get_merge_strategy(self):
+        return lldb.eScriptedFrameProviderMergeStrategyReplace
+
+    def get_stackframes(self):
+        return [
+            {
+                "idx": 0,
+                "pc": 0x1000,
+            },
+            {
+                "idx": 1,
+                "pc": 0x2000,
+            },
+            {
+                "idx": 2,
+                "pc": 0x3000,
+            },
+        ]
+
+
+class PrependFrameProvider(ScriptedFrameProvider):
+    """Prepend synthetic frames before real stack."""
+
+    def __init__(self, thread, args):
+        super().__init__(thread, args)
+
+    def get_merge_strategy(self):
+        return lldb.eScriptedFrameProviderMergeStrategyPrepend
+
+    def get_stackframes(self):
+        # Get real frame 0 PC
+        real_frame_0 = self.thread.GetFrameAtIndex(0)
+        real_pc = (
+            real_frame_0.GetPC() if real_frame_0 and real_frame_0.IsValid() else 0x1000
+        )
+
+        return [
+            {
+                "idx": 0,
+                "pc": real_pc,
+            },
+            {
+                "idx": 1,
+                "pc": real_pc - 0x10,
+            },
+        ]
+
+
+class AppendFrameProvider(ScriptedFrameProvider):
+    """Append synthetic frames after real stack."""
+
+    def __init__(self, thread, args):
+        super().__init__(thread, args)
+
+    def get_merge_strategy(self):
+        return lldb.eScriptedFrameProviderMergeStrategyAppend
+
+    def get_stackframes(self):
+        # Count real frames
+        num_real_frames = self.thread.GetNumFrames()
+
+        return [
+            {
+                "idx": num_real_frames,
+                "pc": 0x9000,
+            },
+            {
+                "idx": num_real_frames + 1,
+                "pc": 0xA000,
+            },
+        ]
+
+
+class ReplaceByIndexFrameProvider(ScriptedFrameProvider):
+    """Replace only frames 0 and 2, keep frame 1 real."""
+
+    def __init__(self, thread, args):
+        super().__init__(thread, args)
+
+    def get_merge_strategy(self):
+        return lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex
+
+    def get_stackframes(self):
+        frames = []
+
+        # Replace frame 0
+        real_frame_0 = self.thread.GetFrameAtIndex(0)
+        if real_frame_0 and real_frame_0.IsValid():
+            frames.append(
+                {
+                    "idx": 0,
+                    "pc": real_frame_0.GetPC(),
+                }
+            )
+
+        # Replace frame 2
+        real_frame_2 = self.thread.GetFrameAtIndex(2)
+        if real_frame_2 and real_frame_2.IsValid():
+            frames.append(
+                {
+                    "idx": 2,
+                    "pc": real_frame_2.GetPC(),
+                }
+            )
+
+        return frames
+
+
+class CustomScriptedFrame(ScriptedFrame):
+    """Custom scripted frame with full control over frame behavior."""
+
+    def __init__(self, thread, idx, pc, function_name):
+        # Initialize structured data args
+        args = lldb.SBStructuredData()
+        super().__init__(thread, args)
+
+        self.idx = idx
+        self.pc = pc
+        self.function_name = function_name
+
+    def get_id(self):
+        """Return the frame index."""
+        return self.idx
+
+    def get_pc(self):
+        """Return the program counter."""
+        return self.pc
+
+    def get_function_name(self):
+        """Return the function name."""
+        return self.function_name
+
+    def is_artificial(self):
+        """Mark as artificial frame."""
+        return True
+
+    def is_hidden(self):
+        """Not hidden."""
+        return False
+
+    def get_register_context(self):
+        """No register context for this test."""
+        return None
+
+
+class ScriptedFrameObjectProvider(ScriptedFrameProvider):
+    """Provider that returns ScriptedFrame objects instead of dictionaries."""
+
+    def __init__(self, thread, args):
+        super().__init__(thread, args)
+
+    def get_merge_strategy(self):
+        return lldb.eScriptedFrameProviderMergeStrategyReplace
+
+    def get_stackframes(self):
+        """Return list of ScriptedFrame objects."""
+        return [
+            CustomScriptedFrame(self.thread, 0, 0x5000, "custom_scripted_frame_0"),
+            CustomScriptedFrame(self.thread, 1, 0x6000, "custom_scripted_frame_1"),
+            CustomScriptedFrame(self.thread, 2, 0x7000, "custom_scripted_frame_2"),
+        ]



More information about the lldb-commits mailing list