-
Notifications
You must be signed in to change notification settings - Fork 15.3k
Reland "[lldb] Introduce ScriptedFrameProvider for real threads (#161870)" #170236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@llvm/pr-subscribers-lldb Author: Med Ismail Bennani (medismailben) ChangesThis patch extends ScriptedFrame to work with real (non-scripted) Previously, ScriptedFrame only worked within Key changes:
This enables practical use of the SyntheticFrameProvider infrastructure rdar://161834688 Signed-off-by: Med Ismail Bennani <ismail@bennani.ma> Patch is 158.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/170236.diff 53 Files Affected:
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index ef501fbafc947..0ba152166522b 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -425,6 +425,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *
return sb_ptr;
}
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject * data) {
+ lldb::SBThread *sb_ptr = nullptr;
+
+ int valid_cast =
+ SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBThread, 0);
+
+ if (valid_cast == -1)
+ return NULL;
+
+ return sb_ptr;
+}
+
void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject * data) {
lldb::SBFrame *sb_ptr = nullptr;
diff --git a/lldb/examples/python/templates/scripted_frame_provider.py b/lldb/examples/python/templates/scripted_frame_provider.py
index 20f4d76d188c2..7a72f1a24c9da 100644
--- a/lldb/examples/python/templates/scripted_frame_provider.py
+++ b/lldb/examples/python/templates/scripted_frame_provider.py
@@ -31,7 +31,54 @@ class ScriptedFrameProvider(metaclass=ABCMeta):
)
"""
+ @staticmethod
+ def applies_to_thread(thread):
+ """Determine if this frame provider should be used for a given thread.
+
+ This static method is called before creating an instance of the frame
+ provider to determine if it should be applied to a specific thread.
+ Override this method to provide custom filtering logic.
+
+ Args:
+ thread (lldb.SBThread): The thread to check.
+
+ Returns:
+ bool: True if this frame provider should be used for the thread,
+ False otherwise. The default implementation returns True for
+ all threads.
+
+ Example:
+
+ .. code-block:: python
+
+ @staticmethod
+ def applies_to_thread(thread):
+ # Only apply to thread 1
+ return thread.GetIndexID() == 1
+ """
+ return True
+
+ @staticmethod
@abstractmethod
+ def get_description():
+ """Get a description of this frame provider.
+
+ This method should return a human-readable string describing what
+ this frame provider does. The description is used for debugging
+ and display purposes.
+
+ Returns:
+ str: A description of the frame provider.
+
+ Example:
+
+ .. code-block:: python
+
+ def get_description(self):
+ return "Crash log frame provider for thread 1"
+ """
+ pass
+
def __init__(self, input_frames, args):
"""Construct a scripted frame provider.
diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py
index b4232f632a30a..24aa9818bb989 100644
--- a/lldb/examples/python/templates/scripted_process.py
+++ b/lldb/examples/python/templates/scripted_process.py
@@ -243,6 +243,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
@@ -264,6 +265,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()
@@ -350,17 +354,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
@@ -403,11 +404,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
@@ -417,15 +419,17 @@ def __init__(self, thread, args):
self.register_ctx = {}
self.variables = []
- if (
- isinstance(thread, ScriptedThread)
- or isinstance(thread, lldb.SBThread)
- and thread.IsValid()
+ if isinstance(thread, ScriptedThread) 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
@@ -506,7 +510,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
@@ -640,12 +655,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/SBTarget.h b/lldb/include/lldb/API/SBTarget.h
index ce81ae46a0905..0318492f1054c 100644
--- a/lldb/include/lldb/API/SBTarget.h
+++ b/lldb/include/lldb/API/SBTarget.h
@@ -19,6 +19,7 @@
#include "lldb/API/SBLaunchInfo.h"
#include "lldb/API/SBStatisticsOptions.h"
#include "lldb/API/SBSymbolContextList.h"
+#include "lldb/API/SBThreadCollection.h"
#include "lldb/API/SBType.h"
#include "lldb/API/SBValue.h"
#include "lldb/API/SBWatchpoint.h"
@@ -1003,6 +1004,35 @@ class LLDB_API SBTarget {
lldb::SBMutex GetAPIMutex() const;
+ /// Register a scripted frame provider for this target.
+ /// If a scripted frame provider with the same name and same argument
+ /// dictionary is already registered on this target, it will be overwritten.
+ ///
+ /// \param[in] class_name
+ /// The name of the Python class that implements the frame provider.
+ ///
+ /// \param[in] args_dict
+ /// A dictionary of arguments to pass to the frame provider class.
+ ///
+ /// \param[out] error
+ /// An error object indicating success or failure.
+ ///
+ /// \return
+ /// A unique identifier for the frame provider descriptor that was
+ /// registered. 0 if the registration failed.
+ uint32_t RegisterScriptedFrameProvider(const char *class_name,
+ lldb::SBStructuredData args_dict,
+ lldb::SBError &error);
+
+ /// Remove a scripted frame provider from this target by name.
+ ///
+ /// \param[in] provider_id
+ /// The id of the frame provider class to remove.
+ ///
+ /// \return
+ /// An error object indicating success or failure.
+ lldb::SBError RemoveScriptedFrameProvider(uint32_t provider_id);
+
protected:
friend class SBAddress;
friend class SBAddressRange;
diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h
index f6a6d19935b83..639e7a0a1a5c0 100644
--- a/lldb/include/lldb/API/SBThread.h
+++ b/lldb/include/lldb/API/SBThread.h
@@ -256,6 +256,7 @@ class LLDB_API SBThread {
friend class SBThreadPlan;
friend class SBTrace;
+ friend class lldb_private::ScriptInterpreter;
friend class lldb_private::python::SWIGBridge;
SBThread(const lldb::ThreadSP &lldb_object_sp);
diff --git a/lldb/include/lldb/API/SBThreadCollection.h b/lldb/include/lldb/API/SBThreadCollection.h
index 5a052e6246026..d13dea0f11cd2 100644
--- a/lldb/include/lldb/API/SBThreadCollection.h
+++ b/lldb/include/lldb/API/SBThreadCollection.h
@@ -46,6 +46,7 @@ class LLDB_API SBThreadCollection {
void SetOpaque(const lldb::ThreadCollectionSP &threads);
private:
+ friend class SBTarget;
friend class SBProcess;
friend class SBThread;
friend class SBSaveCoreOptions;
diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h
index 40916dc48a70b..107c30a000979 100644
--- a/lldb/include/lldb/Core/FormatEntity.h
+++ b/lldb/include/lldb/Core/FormatEntity.h
@@ -81,6 +81,7 @@ struct Entry {
FrameRegisterByName,
FrameIsArtificial,
FrameKind,
+ FrameBorrowedInfo,
ScriptFrame,
FunctionID,
FunctionDidChange,
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
index 2d9f713676f90..49b60131399d5 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
@@ -16,11 +16,29 @@
namespace lldb_private {
class ScriptedFrameProviderInterface : public ScriptedInterface {
public:
+ virtual bool AppliesToThread(llvm::StringRef class_name,
+ lldb::ThreadSP thread_sp) {
+ return true;
+ }
+
virtual llvm::Expected<StructuredData::GenericSP>
CreatePluginObject(llvm::StringRef class_name,
lldb::StackFrameListSP input_frames,
StructuredData::DictionarySP args_sp) = 0;
+ /// Get a description string for the frame provider.
+ ///
+ /// This is called by the descriptor to fetch a description from the
+ /// scripted implementation. Implementations should call a static method
+ /// on the scripting class to retrieve the description.
+ ///
+ /// \param class_name The name of the scripting class implementing the
+ /// provider.
+ ///
+ /// \return A string describing what this frame provider does, or an
+ /// empty string if no description is available.
+ virtual std::string GetDescription(llvm::StringRef class_name) { return {}; }
+
virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) {
return {};
}
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 7fed4940b85bf..0b91d6756552d 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -21,6 +21,7 @@
#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBSymbolContext.h"
+#include "lldb/API/SBThread.h"
#include "lldb/Breakpoint/BreakpointOptions.h"
#include "lldb/Core/PluginInterface.h"
#include "lldb/Core/SearchFilter.h"
@@ -580,6 +581,8 @@ class ScriptInterpreter : public PluginInterface {
lldb::StreamSP GetOpaqueTypeFromSBStream(const lldb::SBStream &stream) const;
+ lldb::ThreadSP GetOpaqueTypeFromSBThread(const lldb::SBThread &exe_ctx) const;
+
lldb::StackFrameSP GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const;
SymbolContext
diff --git a/lldb/include/lldb/Target/BorrowedStackFrame.h b/lldb/include/lldb/Target/BorrowedStackFrame.h
new file mode 100644
index 0000000000000..72e7777961da7
--- /dev/null
+++ b/lldb/include/lldb/Target/BorrowedStackFrame.h
@@ -0,0 +1,146 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TARGET_BORROWEDSTACKFRAME_H
+#define LLDB_TARGET_BORROWEDSTACKFRAME_H
+
+#include "lldb/Target/StackFrame.h"
+
+namespace lldb_private {
+
+/// \class BorrowedStackFrame BorrowedStackFrame.h
+/// "lldb/Target/BorrowedStackFrame.h"
+///
+/// A wrapper around an existing StackFrame that supersedes its frame indices.
+///
+/// This class is useful when you need to present an existing stack frame
+/// with a different index, such as when creating synthetic frame views or
+/// renumbering frames without copying all the underlying data.
+///
+/// All methods delegate to the borrowed frame except for GetFrameIndex()
+/// & GetConcreteFrameIndex() which uses the overridden indices.
+class BorrowedStackFrame : public StackFrame {
+public:
+ /// Construct a BorrowedStackFrame that wraps an existing frame.
+ ///
+ /// \param [in] borrowed_frame_sp
+ /// The existing StackFrame to borrow from. This frame's data will be
+ /// used for all operations except frame index queries.
+ ///
+ /// \param [in] new_frame_index
+ /// The frame index to report instead of the borrowed frame's index.
+ ///
+ /// \param [in] new_concrete_frame_index
+ /// Optional concrete frame index. If not provided, defaults to
+ /// new_frame_index.
+ BorrowedStackFrame(
+ lldb::StackFrameSP borrowed_frame_sp, uint32_t new_frame_index,
+ std::optional<uint32_t> new_concrete_frame_index = std::nullopt);
+
+ ~BorrowedStackFrame() override = default;
+
+ uint32_t GetFrameIndex() const override;
+ void SetFrameIndex(uint32_t index);
+
+ /// Get the concrete frame index for this borrowed frame.
+ ///
+ /// Returns the overridden concrete frame index provided at construction,
+ /// or LLDB_INVALID_FRAME_ID if the borrowed frame represents an inlined
+ /// function, since this would require some computation if we chain inlined
+ /// borrowed stack frames.
+ ///
+ /// \return
+ /// The concrete frame index, or LLDB_INVALID_FRAME_ID for inline frames.
+ uint32_t GetConcreteFrameIndex() override;
+
+ StackID &GetStackID() override;
+
+ const Address &GetFrameCodeAddress() override;
+
+ Address GetFrameCodeAddressForSymbolication() override;
+
+ bool ChangePC(lldb::addr_t pc) override;
+
+ const SymbolContext &
+ GetSymbolContext(lldb::SymbolContextItem resolve_scope) override;
+
+ llvm::Error GetFrameBaseValue(Scalar &value) override;
+
+ DWARFExpressionList *GetFrameBaseExpression(Status *error_ptr) override;
+
+ Block *GetFrameBlock() override;
+
+ lldb::RegisterContextSP GetRegisterContext() override;
+
+ VariableList *GetVariableList(bool get_file_globals,
+ Status *error_ptr) override;
+
+ lldb::VariableListSP
+ GetInScopeVariableList(bool get_file_globals,
+ bool must_have_valid_location = false) override;
+
+ lldb::ValueObjectSP GetValueForVariableExpressionPath(
+ llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
+ uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
+
+ bool HasDebugInformation() override;
+
+ const char *Disassemble() override;
+
+ lldb::ValueObjectSP
+ GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp,
+ lldb::DynamicValueType use_dynamic) override;
+
+ bool IsInlined() override;
+
+ bool IsSynthetic() const override;
+
+ bool IsHistorical() const override;
+
+ bool IsArtificial() const override;
+
+ bool IsHidden() override;
+
+ const char *GetFunctionName() override;
+
+ const char *GetDisplayFunctionName() override;
+
+ lldb::ValueObjectSP FindVariable(ConstString name) override;
+
+ SourceLanguage GetLanguage() override;
+
+ SourceLanguage GuessLanguage() override;
+
+ lldb::ValueObjectSP GuessValueForAddress(lldb::addr_t addr) override;
+
+ lldb::ValueObjectSP GuessValueForRegisterAndOffset(ConstString reg,
+ int64_t offset) override;
+
+ StructuredData::ObjectSP GetLanguageSpecificData() override;
+
+ lldb::RecognizedStackFrameSP GetRecognizedFrame() override;
+
+ /// Get the underlying borrowed frame.
+ lldb::StackFrameSP GetBorrowedFrame() const;
+
+ bool isA(const void *ClassID) const override;
+ static bool classof(const StackFrame *obj);
+
+private:
+ lldb::StackFrameSP m_borrowed_frame_sp;
+ uint32_t m_new_frame_index;
+ uint32_t m_new_concrete_frame_index;
+ static char ID;
+
+ BorrowedStackFrame(const BorrowedStackFrame &) = delete;
+ const BorrowedStackFrame &operator=(const BorrowedStackFrame &) = delete;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_BORROWEDSTACKFRAME_H
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 135bd81e4e8d4..46922448d6e59 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -43,6 +43,13 @@ namespace lldb_private {
class StackFrame : public ExecutionContextScope,
public std::enable_shared_from_this<StackFrame> {
public:
+ /// LLVM RTTI support.
+ /// \{
+ static char ID;
+ virtual bool isA(const void *ClassID) const { return ClassID == &ID; }
+ static bool classof(const StackFrame *obj) { return obj->isA(&ID); }
+ /// \}
+
enum ExpressionPathOption {
eExpressionPathOptionCheckPtrVsMember = (1u << 0),
eExpressionPathOptionsNoFragileObjcIvar = (1u << 1),
@@ -127,7 +134,7 @@ class StackFrame : public ExecutionContextScope,
lldb::ThreadSP GetThread() const { return m_thread_wp.lock(); }
- StackID &GetStackID();
+ virtual StackID &GetStackID();
/// Get an Address for the current pc value in this StackFrame.
///
@@ -135,7 +142,7 @@ class StackFrame : public ExecutionContextScope,
///
/// \return
/// The Address object set to the current PC value.
- const Address &GetFrameCodeAddress();
+ virtual const Address &GetFrameCodeAddress();
/// Get the current code Address suitable for symbolication,
/// may not be the same as GetFrameCodeAddress().
@@ -153,7 +160,7 @@ class StackFrame : public ExecutionContextScope,
///
/// \return
/// The Address object set to the current PC value.
- Address GetFrameCodeAddressForSymbolication...
[truncated]
|
You can test this locally with the following command:darker --check --diff -r origin/main...HEAD lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py lldb/examples/python/templates/scripted_frame_provider.py lldb/examples/python/templates/scripted_process.py
View the diff from darker here.--- test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py 2025-12-02 18:41:52.000000 +0000
+++ test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py 2025-12-02 18:44:17.259852 +0000
@@ -113,7 +113,5 @@
# These calls should not trigger circular dependency.
pc = frame.GetPC()
self.assertNotEqual(pc, 0, f"Frame {i} should have valid PC")
func_name = frame.GetFunctionName()
self.assertIsNotNone(func_name, f"Frame {i} should have function name")
-
-
|
5ffc853 to
49bc29f
Compare
|
This depends on #170191. After that lands, it should only contain one commit. |
5b760e8 to
6430265
Compare
JDevlieghere
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the original PR so this (still) LGTM.
…#161870)" This patch extends ScriptedFrame to work with real (non-scripted) threads, enabling frame providers to synthesize frames for native processes. Previously, ScriptedFrame only worked within ScriptedProcess/ScriptedThread contexts. This patch decouples ScriptedFrame from ScriptedThread, allowing users to augment or replace stack frames in real debugging sessions for use cases like custom calling conventions, reconstructing corrupted frames from core files, or adding diagnostic frames. Key changes: - ScriptedFrame::Create() now accepts ThreadSP instead of requiring ScriptedThread, extracting architecture from the target triple rather than ScriptedProcess.arch - Added SBTarget::RegisterScriptedFrameProvider() and ClearScriptedFrameProvider() APIs, with Target storing a SyntheticFrameProviderDescriptor template for new threads - Added "target frame-provider register/clear" commands for CLI access - Thread class gains LoadScriptedFrameProvider(), ClearScriptedFrameProvider(), and GetFrameProvider() methods for per-thread frame provider management - New SyntheticStackFrameList overrides FetchFramesUpTo() to lazily provide frames from either the frame provider or the real stack This enables practical use of the SyntheticFrameProvider infrastructure in real debugging workflows. rdar://161834688 Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
6430265 to
3f7c5f4
Compare
This patch re-lands #161870 with fixes to the previous test failures.
rdar://161834688
Signed-off-by: Med Ismail Bennani ismail@bennani.ma