[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 ¤t_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