From a484880617a11857ed56995ff5326ac816e1a248 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani Date: Tue, 2 Dec 2025 10:46:06 -0800 Subject: [PATCH] Reland "[lldb] Introduce ScriptedFrameProvider for real threads (#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 --- lldb/bindings/python/python-wrapper.swig | 12 + .../templates/scripted_frame_provider.py | 47 ++ .../python/templates/scripted_process.py | 47 +- lldb/include/lldb/API/SBTarget.h | 30 ++ lldb/include/lldb/API/SBThread.h | 1 + lldb/include/lldb/API/SBThreadCollection.h | 1 + .../ScriptedFrameProviderInterface.h | 18 + .../lldb/Interpreter/ScriptInterpreter.h | 3 + lldb/include/lldb/Target/StackFrame.h | 3 +- lldb/include/lldb/Target/StackFrameList.h | 34 +- .../lldb/Target/SyntheticFrameProvider.h | 30 +- lldb/include/lldb/Target/Target.h | 38 ++ lldb/include/lldb/Target/Thread.h | 12 + lldb/include/lldb/Target/ThreadSpec.h | 2 + lldb/include/lldb/Utility/ScriptedMetadata.h | 27 ++ lldb/include/lldb/lldb-private-interfaces.h | 4 +- lldb/source/API/SBTarget.cpp | 82 ++++ lldb/source/Commands/CommandObjectTarget.cpp | 200 +++++++++ lldb/source/Interpreter/ScriptInterpreter.cpp | 7 + lldb/source/Plugins/CMakeLists.txt | 1 + .../Process/scripted/ScriptedFrame.cpp | 85 ++-- .../Plugins/Process/scripted/ScriptedFrame.h | 33 +- .../Process/scripted/ScriptedThread.cpp | 6 +- .../ScriptInterpreterPythonInterfaces.cpp | 2 + .../ScriptedFrameProviderPythonInterface.cpp | 58 ++- .../ScriptedFrameProviderPythonInterface.h | 23 +- .../Interfaces/ScriptedPythonInterface.cpp | 13 + .../Interfaces/ScriptedPythonInterface.h | 121 ++++- .../Python/SWIGPythonBridge.h | 1 + .../SyntheticFrameProvider/CMakeLists.txt | 1 + .../ScriptedFrameProvider/CMakeLists.txt | 12 + .../ScriptedFrameProvider.cpp | 221 +++++++++ .../ScriptedFrameProvider.h | 53 +++ lldb/source/Target/StackFrameList.cpp | 39 ++ lldb/source/Target/SyntheticFrameProvider.cpp | 25 +- lldb/source/Target/Target.cpp | 55 +++ lldb/source/Target/Thread.cpp | 72 ++- lldb/source/Target/ThreadSpec.cpp | 4 + .../scripted_frame_provider/Makefile | 3 + .../TestScriptedFrameProvider.py | 418 ++++++++++++++++++ .../circular_dependency/Makefile | 3 + .../TestFrameProviderCircularDependency.py | 117 +++++ .../circular_dependency/frame_provider.py | 102 +++++ .../circular_dependency/main.c | 21 + .../scripted_frame_provider/main.cpp | 53 +++ .../test_frame_providers.py | 222 ++++++++++ .../Python/PythonTestSuite.cpp | 5 + 47 files changed, 2290 insertions(+), 77 deletions(-) create mode 100644 lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt create mode 100644 lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt create mode 100644 lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp create mode 100644 lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h 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/circular_dependency/Makefile create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/main.cpp create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py 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/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 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/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index c7ebb1708d589..46922448d6e59 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -451,7 +451,7 @@ class StackFrame : public ExecutionContextScope, /// frames are included in this frame index count. virtual uint32_t GetFrameIndex() const; - /// Set this frame's synthetic frame index. + /// Set this frame's frame index. void SetFrameIndex(uint32_t index) { m_frame_index = index; } /// Query this frame to find what frame it is in this Thread's @@ -558,6 +558,7 @@ class StackFrame : public ExecutionContextScope, protected: friend class BorrowedStackFrame; friend class StackFrameList; + friend class SyntheticStackFrameList; void SetSymbolContextScope(SymbolContextScope *symbol_scope); diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index 8c14e92a41a4e..539c070ff0f4b 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -26,7 +26,7 @@ class StackFrameList : public std::enable_shared_from_this { StackFrameList(Thread &thread, const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames); - ~StackFrameList(); + virtual ~StackFrameList(); /// Get the number of visible frames. Frames may be created if \p can_create /// is true. Synthetic (inline) frames expanded from the concrete frame #0 @@ -106,6 +106,7 @@ class StackFrameList : public std::enable_shared_from_this { protected: friend class Thread; + friend class ScriptedFrameProvider; friend class ScriptedThread; /// Use this API to build a stack frame list (used for scripted threads, for @@ -211,19 +212,23 @@ class StackFrameList : public std::enable_shared_from_this { /// Whether or not to show synthetic (inline) frames. Immutable. const bool m_show_inlined_frames; + /// Returns true if fetching frames was interrupted, false otherwise. + virtual bool FetchFramesUpTo(uint32_t end_idx, + InterruptionControl allow_interrupt); + private: uint32_t SetSelectedFrameNoLock(lldb_private::StackFrame *frame); lldb::StackFrameSP GetFrameAtIndexNoLock(uint32_t idx, std::shared_lock &guard); + /// @{ /// These two Fetch frames APIs and SynthesizeTailCallFrames are called in /// GetFramesUpTo, they are the ones that actually add frames. They must be /// called with the writer end of the list mutex held. - - /// Returns true if fetching frames was interrupted, false otherwise. - bool FetchFramesUpTo(uint32_t end_idx, InterruptionControl allow_interrupt); + /// /// Not currently interruptible so returns void. + /// }@ void FetchOnlyConcreteFramesUpTo(uint32_t end_idx); void SynthesizeTailCallFrames(StackFrame &next_frame); @@ -231,6 +236,27 @@ class StackFrameList : public std::enable_shared_from_this { const StackFrameList &operator=(const StackFrameList &) = delete; }; +/// A StackFrameList that wraps another StackFrameList and uses a +/// SyntheticFrameProvider to lazily provide frames from either the provider +/// or the underlying real stack frame list. +class SyntheticStackFrameList : public StackFrameList { +public: + SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames, + const lldb::StackFrameListSP &prev_frames_sp, + bool show_inline_frames); + +protected: + /// Override FetchFramesUpTo to lazily return frames from the provider + /// or from the actual stack frame list. + bool FetchFramesUpTo(uint32_t end_idx, + InterruptionControl allow_interrupt) override; + +private: + /// The input stack frame list that the provider transforms. + /// This could be a real StackFrameList or another SyntheticStackFrameList. + lldb::StackFrameListSP m_input_frames; +}; + } // namespace lldb_private #endif // LLDB_TARGET_STACKFRAMELIST_H diff --git a/lldb/include/lldb/Target/SyntheticFrameProvider.h b/lldb/include/lldb/Target/SyntheticFrameProvider.h index 61a492f356ece..2d5330cb03105 100644 --- a/lldb/include/lldb/Target/SyntheticFrameProvider.h +++ b/lldb/include/lldb/Target/SyntheticFrameProvider.h @@ -24,22 +24,25 @@ namespace lldb_private { /// This struct contains the metadata needed to instantiate a frame provider /// and optional filters to control which threads it applies to. -struct SyntheticFrameProviderDescriptor { +struct ScriptedFrameProviderDescriptor { /// Metadata for instantiating the provider (e.g. script class name and args). lldb::ScriptedMetadataSP scripted_metadata_sp; + /// Interface for calling static methods on the provider class. + lldb::ScriptedFrameProviderInterfaceSP interface_sp; + /// Optional list of thread specifications to which this provider applies. /// If empty, the provider applies to all threads. A thread matches if it /// satisfies ANY of the specs in this vector (OR logic). std::vector thread_specs; - SyntheticFrameProviderDescriptor() = default; + ScriptedFrameProviderDescriptor() = default; - SyntheticFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp) + ScriptedFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp) : scripted_metadata_sp(metadata_sp) {} - SyntheticFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp, - const std::vector &specs) + ScriptedFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp, + const std::vector &specs) : scripted_metadata_sp(metadata_sp), thread_specs(specs) {} /// Get the name of this descriptor (the scripted class name). @@ -47,6 +50,12 @@ struct SyntheticFrameProviderDescriptor { return scripted_metadata_sp ? scripted_metadata_sp->GetClassName() : ""; } + /// Get the description of this frame provider. + /// + /// \return A string describing what this frame provider does, or an + /// empty string if no description is available. + std::string GetDescription() const; + /// Check if this descriptor applies to the given thread. bool AppliesToThread(Thread &thread) const { // If no thread specs specified, applies to all threads. @@ -64,6 +73,13 @@ struct SyntheticFrameProviderDescriptor { /// Check if this descriptor has valid metadata for script-based providers. bool IsValid() const { return scripted_metadata_sp != nullptr; } + /// Get a unique identifier for this descriptor based on its contents. + /// The ID is computed from the class name and arguments dictionary, + /// not from the pointer address, so two descriptors with the same + /// contents will have the same ID. + uint32_t GetID() const; + + /// Dump a description of this descriptor to the given stream. void Dump(Stream *s) const; }; @@ -95,7 +111,7 @@ class SyntheticFrameProvider : public PluginInterface { /// otherwise an \a llvm::Error. static llvm::Expected CreateInstance(lldb::StackFrameListSP input_frames, - const SyntheticFrameProviderDescriptor &descriptor); + const ScriptedFrameProviderDescriptor &descriptor); /// Try to create a SyntheticFrameProvider instance for the given input /// frames using a specific C++ plugin. @@ -125,6 +141,8 @@ class SyntheticFrameProvider : public PluginInterface { ~SyntheticFrameProvider() override; + virtual std::string GetDescription() const = 0; + /// Get a single stack frame at the specified index. /// /// This method is called lazily - frames are only created when requested. diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index c0fcda7c0d960..812a638910b3b 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -32,6 +32,7 @@ #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" #include "lldb/Target/Statistics.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" @@ -745,6 +746,36 @@ class Target : public std::enable_shared_from_this, Status Attach(ProcessAttachInfo &attach_info, Stream *stream); // Optional stream to receive first stop info + /// Add or update a scripted frame provider descriptor for this target. + /// All new threads in this target will check if they match any descriptors + /// to create their frame providers. + /// + /// \param[in] descriptor + /// The descriptor to add or update. + /// + /// \return + /// The descriptor identifier if the registration succeeded, otherwise an + /// llvm::Error. + llvm::Expected AddScriptedFrameProviderDescriptor( + const ScriptedFrameProviderDescriptor &descriptor); + + /// Remove a scripted frame provider descriptor by id. + /// + /// \param[in] id + /// The id of the descriptor to remove. + /// + /// \return + /// True if a descriptor was removed, false if no descriptor with that + /// id existed. + bool RemoveScriptedFrameProviderDescriptor(uint32_t id); + + /// Clear all scripted frame provider descriptors for this target. + void ClearScriptedFrameProviderDescriptors(); + + /// Get all scripted frame provider descriptors for this target. + const llvm::DenseMap & + GetScriptedFrameProviderDescriptors() const; + // This part handles the breakpoints. BreakpointList &GetBreakpointList(bool internal = false); @@ -1744,6 +1775,13 @@ class Target : public std::enable_shared_from_this, PathMappingList m_image_search_paths; TypeSystemMap m_scratch_type_system_map; + /// Map of scripted frame provider descriptors for this target. + /// Keys are the provider descriptors ids, values are the descriptors. + /// Used to initialize frame providers for new threads. + llvm::DenseMap + m_frame_provider_descriptors; + mutable std::recursive_mutex m_frame_provider_descriptors_mutex; + typedef std::map REPLMap; REPLMap m_repl_map; diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index 841f80cd1b1eb..46ce192556756 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1297,6 +1297,15 @@ class Thread : public std::enable_shared_from_this, lldb::StackFrameListSP GetStackFrameList(); + llvm::Error + LoadScriptedFrameProvider(const ScriptedFrameProviderDescriptor &descriptor); + + void ClearScriptedFrameProvider(); + + lldb::SyntheticFrameProviderSP GetFrameProvider() const { + return m_frame_provider_sp; + } + protected: friend class ThreadPlan; friend class ThreadList; @@ -1400,6 +1409,9 @@ class Thread : public std::enable_shared_from_this, /// The Thread backed by this thread, if any. lldb::ThreadWP m_backed_thread; + /// The Scripted Frame Provider, if any. + lldb::SyntheticFrameProviderSP m_frame_provider_sp; + private: bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info // for this thread? diff --git a/lldb/include/lldb/Target/ThreadSpec.h b/lldb/include/lldb/Target/ThreadSpec.h index 7c7c832741196..63f8f8b5ec181 100644 --- a/lldb/include/lldb/Target/ThreadSpec.h +++ b/lldb/include/lldb/Target/ThreadSpec.h @@ -34,6 +34,8 @@ class ThreadSpec { public: ThreadSpec(); + ThreadSpec(Thread &thread); + static std::unique_ptr CreateFromStructuredData(const StructuredData::Dictionary &data_dict, Status &error); diff --git a/lldb/include/lldb/Utility/ScriptedMetadata.h b/lldb/include/lldb/Utility/ScriptedMetadata.h index 69c83edce909a..8523c95429718 100644 --- a/lldb/include/lldb/Utility/ScriptedMetadata.h +++ b/lldb/include/lldb/Utility/ScriptedMetadata.h @@ -10,7 +10,9 @@ #define LLDB_INTERPRETER_SCRIPTEDMETADATA_H #include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/StreamString.h" #include "lldb/Utility/StructuredData.h" +#include "llvm/ADT/Hashing.h" namespace lldb_private { class ScriptedMetadata { @@ -27,11 +29,36 @@ class ScriptedMetadata { } } + ScriptedMetadata(const ScriptedMetadata &other) + : m_class_name(other.m_class_name), m_args_sp(other.m_args_sp) {} + explicit operator bool() const { return !m_class_name.empty(); } llvm::StringRef GetClassName() const { return m_class_name; } StructuredData::DictionarySP GetArgsSP() const { return m_args_sp; } + /// Get a unique identifier for this metadata based on its contents. + /// The ID is computed from the class name and arguments dictionary, + /// not from the pointer address, so two metadata objects with the same + /// contents will have the same ID. + uint32_t GetID() const { + if (m_class_name.empty()) + return 0; + + // Hash the class name. + llvm::hash_code hash = llvm::hash_value(m_class_name); + + // Hash the arguments dictionary if present. + if (m_args_sp) { + StreamString ss; + m_args_sp->GetDescription(ss); + hash = llvm::hash_combine(hash, llvm::hash_value(ss.GetData())); + } + + // Return the lower 32 bits of the hash. + return static_cast(hash); + } + private: std::string m_class_name; StructuredData::DictionarySP m_args_sp; diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h index 5fc5c14c52f9e..52806eea190a7 100644 --- a/lldb/include/lldb/lldb-private-interfaces.h +++ b/lldb/include/lldb/lldb-private-interfaces.h @@ -26,7 +26,7 @@ class Value; namespace lldb_private { class ScriptedInterfaceUsages; -struct SyntheticFrameProviderDescriptor; +struct ScriptedFrameProviderDescriptor; typedef lldb::ABISP (*ABICreateInstance)(lldb::ProcessSP process_sp, const ArchSpec &arch); typedef std::unique_ptr (*ArchitectureCreateInstance)( @@ -91,7 +91,7 @@ typedef lldb::ScriptInterpreterSP (*ScriptInterpreterCreateInstance)( typedef llvm::Expected ( *ScriptedFrameProviderCreateInstance)( lldb::StackFrameListSP input_frames, - const lldb_private::SyntheticFrameProviderDescriptor &descriptor); + const lldb_private::ScriptedFrameProviderDescriptor &descriptor); typedef llvm::Expected ( *SyntheticFrameProviderCreateInstance)( lldb::StackFrameListSP input_frames, diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp index 578a7bdf7433d..78c2d49d647b5 100644 --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -23,6 +23,7 @@ #include "lldb/API/SBStringList.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBSymbolContextList.h" +#include "lldb/API/SBThreadCollection.h" #include "lldb/API/SBTrace.h" #include "lldb/Breakpoint/BreakpointID.h" #include "lldb/Breakpoint/BreakpointIDList.h" @@ -39,6 +40,7 @@ #include "lldb/Core/Section.h" #include "lldb/Core/StructuredDataImpl.h" #include "lldb/Host/Host.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Symbol/DeclVendor.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/SymbolFile.h" @@ -50,6 +52,7 @@ #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/StackFrame.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/Target.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/ArchSpec.h" @@ -59,6 +62,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/ValueObject/ValueObjectList.h" #include "lldb/ValueObject/ValueObjectVariable.h" @@ -2435,3 +2439,81 @@ lldb::SBMutex SBTarget::GetAPIMutex() const { return lldb::SBMutex(target_sp); return lldb::SBMutex(); } + +uint32_t +SBTarget::RegisterScriptedFrameProvider(const char *class_name, + lldb::SBStructuredData args_dict, + lldb::SBError &error) { + LLDB_INSTRUMENT_VA(this, class_name, args_dict, error); + + TargetSP target_sp = GetSP(); + if (!target_sp) { + error.SetErrorString("invalid target"); + return 0; + } + + if (!class_name || !class_name[0]) { + error.SetErrorString("invalid class name"); + return 0; + } + + // Extract the dictionary from SBStructuredData. + StructuredData::DictionarySP dict_sp; + if (args_dict.IsValid() && args_dict.m_impl_up) { + StructuredData::ObjectSP obj_sp = args_dict.m_impl_up->GetObjectSP(); + if (obj_sp && obj_sp->GetType() != lldb::eStructuredDataTypeDictionary) { + error.SetErrorString("SBStructuredData argument isn't a dictionary"); + return 0; + } + dict_sp = std::make_shared(obj_sp); + } + + // Create the ScriptedMetadata. + ScriptedMetadataSP metadata_sp = + std::make_shared(class_name, dict_sp); + + // Create the interface for calling static methods. + ScriptedFrameProviderInterfaceSP interface_sp = + target_sp->GetDebugger() + .GetScriptInterpreter() + ->CreateScriptedFrameProviderInterface(); + + // Create a descriptor (applies to all threads by default). + ScriptedFrameProviderDescriptor descriptor(metadata_sp); + descriptor.interface_sp = interface_sp; + + llvm::Expected descriptor_id_or_err = + target_sp->AddScriptedFrameProviderDescriptor(descriptor); + if (!descriptor_id_or_err) { + error.SetErrorString( + llvm::toString(descriptor_id_or_err.takeError()).c_str()); + return 0; + } + + // Register the descriptor with the target. + return *descriptor_id_or_err; +} + +lldb::SBError SBTarget::RemoveScriptedFrameProvider(uint32_t provider_id) { + LLDB_INSTRUMENT_VA(this, provider_id); + + SBError error; + TargetSP target_sp = GetSP(); + if (!target_sp) { + error.SetErrorString("invalid target"); + return error; + } + + if (!provider_id) { + error.SetErrorString("invalid provider id"); + return error; + } + + if (!target_sp->RemoveScriptedFrameProviderDescriptor(provider_id)) { + error.SetErrorStringWithFormat("no frame provider named '%u' found", + provider_id); + return error; + } + + return {}; +} diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 7f880d223d6c3..6e8c94fa234cd 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -51,6 +51,7 @@ #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StructuredData.h" @@ -5402,6 +5403,202 @@ class CommandObjectTargetDump : public CommandObjectMultiword { ~CommandObjectTargetDump() override = default; }; +#pragma mark CommandObjectTargetFrameProvider + +#define LLDB_OPTIONS_target_frame_provider_register +#include "CommandOptions.inc" + +class CommandObjectTargetFrameProviderRegister : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderRegister(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider register", + "Register frame provider for all threads in this target.", nullptr, + eCommandRequiresTarget), + + m_class_options("target 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); + } + + ~CommandObjectTargetFrameProviderRegister() override = default; + + Options *GetOptions() override { return &m_all_options; } + + std::optional GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + return std::string(""); + } + +protected: + void DoExecute(Args &launch_args, CommandReturnObject &result) override { + ScriptedMetadataSP metadata_sp = std::make_shared( + m_class_options.GetName(), m_class_options.GetStructuredData()); + + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) + target = &GetDebugger().GetDummyTarget(); + + // Create the interface for calling static methods. + ScriptedFrameProviderInterfaceSP interface_sp = + GetDebugger() + .GetScriptInterpreter() + ->CreateScriptedFrameProviderInterface(); + + // Create a descriptor from the metadata (applies to all threads by + // default). + ScriptedFrameProviderDescriptor descriptor(metadata_sp); + descriptor.interface_sp = interface_sp; + + auto id_or_err = target->AddScriptedFrameProviderDescriptor(descriptor); + if (!id_or_err) { + result.SetError(id_or_err.takeError()); + return; + } + + result.AppendMessageWithFormat( + "successfully registered scripted frame provider '%s' for target\n", + m_class_options.GetName().c_str()); + } + + OptionGroupPythonClassWithDict m_class_options; + OptionGroupOptions m_all_options; +}; + +class CommandObjectTargetFrameProviderClear : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderClear(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider clear", + "Clear all registered frame providers from this target.", nullptr, + eCommandRequiresTarget) {} + + ~CommandObjectTargetFrameProviderClear() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) { + result.AppendError("invalid target"); + return; + } + + target->ClearScriptedFrameProviderDescriptors(); + + result.SetStatus(eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectTargetFrameProviderList : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider list", + "List all registered frame providers for the target.", nullptr, + eCommandRequiresTarget) {} + + ~CommandObjectTargetFrameProviderList() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) + target = &GetDebugger().GetDummyTarget(); + + const auto &descriptors = target->GetScriptedFrameProviderDescriptors(); + if (descriptors.empty()) { + result.AppendMessage("no frame providers registered for this target."); + result.SetStatus(eReturnStatusSuccessFinishResult); + return; + } + + result.AppendMessageWithFormat("%u frame provider(s) registered:\n\n", + descriptors.size()); + + for (const auto &entry : descriptors) { + const ScriptedFrameProviderDescriptor &descriptor = entry.second; + descriptor.Dump(&result.GetOutputStream()); + result.GetOutputStream().PutChar('\n'); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectTargetFrameProviderRemove : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderRemove(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider remove", + "Remove a registered frame provider from the target by id.", + "target frame-provider remove ", + eCommandRequiresTarget) { + AddSimpleArgumentList(eArgTypeUnsignedInteger, eArgRepeatPlus); + } + + ~CommandObjectTargetFrameProviderRemove() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) + target = &GetDebugger().GetDummyTarget(); + + std::vector removed_provider_ids; + for (size_t i = 0; i < command.GetArgumentCount(); i++) { + uint32_t provider_id = 0; + if (!llvm::to_integer(command[i].ref(), provider_id)) { + result.AppendError("target frame-provider remove requires integer " + "provider id argument"); + return; + } + + if (!target->RemoveScriptedFrameProviderDescriptor(provider_id)) { + result.AppendErrorWithFormat( + "no frame provider named '%u' found in target\n", provider_id); + return; + } + removed_provider_ids.push_back(provider_id); + } + + if (size_t num_removed_providers = removed_provider_ids.size()) { + result.AppendMessageWithFormat( + "Successfully removed %zu frame-providers.\n", num_removed_providers); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("0 frame providers removed.\n"); + } + } +}; + +class CommandObjectTargetFrameProvider : public CommandObjectMultiword { +public: + CommandObjectTargetFrameProvider(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target frame-provider", + "Commands for registering and viewing frame providers for the " + "target.", + "target frame-provider [] ") { + LoadSubCommand("register", + CommandObjectSP(new CommandObjectTargetFrameProviderRegister( + interpreter))); + LoadSubCommand("clear", + CommandObjectSP( + new CommandObjectTargetFrameProviderClear(interpreter))); + LoadSubCommand( + "list", + CommandObjectSP(new CommandObjectTargetFrameProviderList(interpreter))); + LoadSubCommand( + "remove", CommandObjectSP( + new CommandObjectTargetFrameProviderRemove(interpreter))); + } + + ~CommandObjectTargetFrameProvider() override = default; +}; + #pragma mark CommandObjectMultiwordTarget // CommandObjectMultiwordTarget @@ -5417,6 +5614,9 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget( CommandObjectSP(new CommandObjectTargetDelete(interpreter))); LoadSubCommand("dump", CommandObjectSP(new CommandObjectTargetDump(interpreter))); + LoadSubCommand( + "frame-provider", + CommandObjectSP(new CommandObjectTargetFrameProvider(interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectTargetList(interpreter))); LoadSubCommand("select", diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp index d2fd372bfe9e3..7bad10ff3ea61 100644 --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -106,6 +106,13 @@ ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const { return Status(); } +lldb::ThreadSP ScriptInterpreter::GetOpaqueTypeFromSBThread( + const lldb::SBThread &thread) const { + if (thread.m_opaque_sp) + return thread.m_opaque_sp->GetThreadSP(); + return nullptr; +} + lldb::StackFrameSP ScriptInterpreter::GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const { if (frame.m_opaque_sp) diff --git a/lldb/source/Plugins/CMakeLists.txt b/lldb/source/Plugins/CMakeLists.txt index 08f444e7b15e8..b6878b21ff71a 100644 --- a/lldb/source/Plugins/CMakeLists.txt +++ b/lldb/source/Plugins/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(SymbolFile) add_subdirectory(SystemRuntime) add_subdirectory(SymbolLocator) add_subdirectory(SymbolVendor) +add_subdirectory(SyntheticFrameProvider) add_subdirectory(Trace) add_subdirectory(TraceExporter) add_subdirectory(TypeSystem) diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp index cd22618c10fa2..265bc28a8957f 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp @@ -7,8 +7,22 @@ //===----------------------------------------------------------------------===// #include "ScriptedFrame.h" - +#include "Plugins/Process/Utility/RegisterContextMemory.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/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; @@ -21,30 +35,44 @@ void ScriptedFrame::CheckInterpreterAndScriptObject() const { } llvm::Expected> -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"); llvm::StringRef frame_class_name; if (!script_object) { - std::optional 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 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); @@ -64,7 +92,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); } @@ -79,11 +107,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 register_info_sp = - DynamicRegisterInfo::Create( - *reg_info, thread.GetProcess()->GetTarget().GetArchitecture()); + DynamicRegisterInfo::Create(*reg_info, + process_sp->GetTarget().GetArchitecture()); lldb::RegisterContextSP reg_ctx_sp; @@ -98,32 +126,35 @@ ScriptedFrame::Create(ScriptedThread &thread, std::shared_ptr reg_ctx_memory = std::make_shared( - 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( - 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 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), m_scripted_frame_interface_sp(interface_sp), - m_script_object_sp(script_object_sp), m_register_info_sp(reg_info_sp) {} + m_script_object_sp(script_object_sp), m_register_info_sp(reg_info_sp) { + // FIXME: This should be part of the base class constructor. + m_stack_frame_kind = StackFrame::Kind::Synthetic; +} ScriptedFrame::~ScriptedFrame() {} @@ -166,7 +197,7 @@ std::shared_ptr ScriptedFrame::GetDynamicRegisterInfo() { if (!reg_info) return ScriptedInterface::ErrorWithMessage< std::shared_ptr>( - LLVM_PRETTY_FUNCTION, "Failed to get scripted frame registers info.", + LLVM_PRETTY_FUNCTION, "failed to get scripted frame registers info", error, LLDBLog::Thread); ThreadSP thread_sp = m_thread_wp.lock(); @@ -174,7 +205,7 @@ std::shared_ptr ScriptedFrame::GetDynamicRegisterInfo() { return ScriptedInterface::ErrorWithMessage< std::shared_ptr>( LLVM_PRETTY_FUNCTION, - "Failed to get scripted frame registers info: invalid thread.", error, + "failed to get scripted frame registers info: invalid thread", error, LLDBLog::Thread); ProcessSP process_sp = thread_sp->GetProcess(); @@ -182,8 +213,8 @@ std::shared_ptr ScriptedFrame::GetDynamicRegisterInfo() { return ScriptedInterface::ErrorWithMessage< std::shared_ptr>( LLVM_PRETTY_FUNCTION, - "Failed to get scripted frame registers info: invalid process.", - error, LLDBLog::Thread); + "failed to get scripted frame registers info: invalid process", error, + LLDBLog::Thread); m_register_info_sp = DynamicRegisterInfo::Create( *reg_info, process_sp->GetTarget().GetArchitecture()); diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h index dd77ad4b62469..d1cbd429d4979 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h +++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h @@ -10,21 +10,19 @@ #define LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_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 #include -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, @@ -33,8 +31,29 @@ class ScriptedFrame : public lldb_private::StackFrame { ~ScriptedFrame() override; + /// Create a ScriptedFrame from a object instanciated in the script + /// interpreter. + /// + /// \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 optional script object representing this frame. + /// + /// \return + /// An Expected containing the ScriptedFrame shared pointer if successful, + /// otherwise an error. static llvm::Expected> - 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; diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp index 491efac5aadef..1dd9c48f56a59 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp @@ -210,7 +210,7 @@ bool ScriptedThread::LoadArtificialStackFrames() { SymbolContext sc; symbol_addr.CalculateSymbolContext(&sc); - return std::make_shared(this->shared_from_this(), idx, idx, cfa, + return std::make_shared(shared_from_this(), idx, idx, cfa, cfa_is_valid, pc, StackFrame::Kind::Synthetic, artificial, behaves_like_zeroth_frame, &sc); @@ -231,8 +231,8 @@ bool ScriptedThread::LoadArtificialStackFrames() { return error.ToError(); } - auto frame_or_error = - ScriptedFrame::Create(*this, nullptr, object_sp->GetAsGeneric()); + auto frame_or_error = ScriptedFrame::Create( + shared_from_this(), GetInterface(), nullptr, object_sp->GetAsGeneric()); if (!frame_or_error) { ScriptedInterface::ErrorWithMessage( diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp index d43036d6fe544..f6c707b2bd168 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp @@ -31,6 +31,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() { ScriptedStopHookPythonInterface::Initialize(); ScriptedBreakpointPythonInterface::Initialize(); ScriptedThreadPlanPythonInterface::Initialize(); + ScriptedFrameProviderPythonInterface::Initialize(); } void ScriptInterpreterPythonInterfaces::Terminate() { @@ -40,6 +41,7 @@ void ScriptInterpreterPythonInterfaces::Terminate() { ScriptedStopHookPythonInterface::Terminate(); ScriptedBreakpointPythonInterface::Terminate(); ScriptedThreadPlanPythonInterface::Terminate(); + ScriptedFrameProviderPythonInterface::Terminate(); } #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp index b866bf332b7b6..3dde5036453f4 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "lldb/Core/PluginManager.h" #include "lldb/Host/Config.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/Log.h" @@ -30,18 +31,45 @@ ScriptedFrameProviderPythonInterface::ScriptedFrameProviderPythonInterface( ScriptInterpreterPythonImpl &interpreter) : ScriptedFrameProviderInterface(), ScriptedPythonInterface(interpreter) {} +bool ScriptedFrameProviderPythonInterface::AppliesToThread( + llvm::StringRef class_name, lldb::ThreadSP thread_sp) { + // If there is any issue with this method, we will just assume it also applies + // to this thread which is the default behavior. + constexpr bool fail_value = true; + Status error; + StructuredData::ObjectSP obj = + CallStaticMethod(class_name, "applies_to_thread", error, thread_sp); + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return fail_value; + + return obj->GetBooleanValue(fail_value); +} + llvm::Expected ScriptedFrameProviderPythonInterface::CreatePluginObject( const llvm::StringRef class_name, lldb::StackFrameListSP input_frames, StructuredData::DictionarySP args_sp) { if (!input_frames) - return llvm::createStringError("Invalid frame list"); + return llvm::createStringError("invalid frame list"); StructuredDataImpl sd_impl(args_sp); return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr, input_frames, sd_impl); } +std::string ScriptedFrameProviderPythonInterface::GetDescription( + llvm::StringRef class_name) { + Status error; + StructuredData::ObjectSP obj = + CallStaticMethod(class_name, "get_description", error); + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return {}; + + return obj->GetStringValue().str(); +} + StructuredData::ObjectSP ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) { Status error; @@ -54,4 +82,32 @@ ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) { return obj; } +bool ScriptedFrameProviderPythonInterface::CreateInstance( + lldb::ScriptLanguage language, ScriptedInterfaceUsages usages) { + if (language != eScriptLanguagePython) + return false; + + return true; +} + +void ScriptedFrameProviderPythonInterface::Initialize() { + const std::vector ci_usages = { + "target frame-provider register -C [-k key -v value ...]", + "target frame-provider list", + "target frame-provider remove ", + "target frame-provider clear"}; + const std::vector api_usages = { + "SBTarget.RegisterScriptedFrameProvider", + "SBTarget.RemoveScriptedFrameProvider", + "SBTarget.ClearScriptedFrameProvider"}; + PluginManager::RegisterPlugin( + GetPluginNameStatic(), + llvm::StringRef("Provide scripted stack frames for threads"), + CreateInstance, eScriptLanguagePython, {ci_usages, api_usages}); +} + +void ScriptedFrameProviderPythonInterface::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h index fd163984028d3..97a5cc7c669ea 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h @@ -14,17 +14,22 @@ #if LLDB_ENABLE_PYTHON #include "ScriptedPythonInterface.h" +#include "lldb/Core/PluginInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include namespace lldb_private { class ScriptedFrameProviderPythonInterface : public ScriptedFrameProviderInterface, - public ScriptedPythonInterface { + public ScriptedPythonInterface, + public PluginInterface { public: ScriptedFrameProviderPythonInterface( ScriptInterpreterPythonImpl &interpreter); + bool AppliesToThread(llvm::StringRef class_name, + lldb::ThreadSP thread_sp) override; + llvm::Expected CreatePluginObject(llvm::StringRef class_name, lldb::StackFrameListSP input_frames, @@ -33,10 +38,24 @@ class ScriptedFrameProviderPythonInterface llvm::SmallVector GetAbstractMethodRequirements() const override { return llvm::SmallVector( - {{"get_frame_at_index"}}); + {{"get_description"}, {"get_frame_at_index"}}); } + std::string GetDescription(llvm::StringRef class_name) override; + StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) override; + + static void Initialize(); + static void Terminate(); + + static bool CreateInstance(lldb::ScriptLanguage language, + ScriptedInterfaceUsages usages); + + static llvm::StringRef GetPluginNameStatic() { + return "ScriptedFrameProviderPythonInterface"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } }; } // namespace lldb_private diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp index af2e0b5df4d22..ba4473cf9ec4d 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp @@ -93,6 +93,19 @@ ScriptedPythonInterface::ExtractValueFromPythonObject( return nullptr; } +template <> +lldb::ThreadSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error) { + if (lldb::SBThread *sb_thread = reinterpret_cast( + python::LLDBSWIGPython_CastPyObjectToSBThread(p.get()))) + return m_interpreter.GetOpaqueTypeFromSBThread(*sb_thread); + error = Status::FromErrorString( + "Couldn't cast lldb::SBThread to lldb_private::Thread."); + + return nullptr; +} + template <> SymbolContext ScriptedPythonInterface::ExtractValueFromPythonObject( diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h index 23c56610124a6..53a7ba65f64b7 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h @@ -387,6 +387,112 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { return m_object_instance_sp; } + /// Call a static method on a Python class without creating an instance. + /// + /// This method resolves a Python class by name and calls a static method + /// on it, returning the result. This is useful for calling class-level + /// methods that don't require an instance. + /// + /// \param class_name The fully-qualified name of the Python class. + /// \param method_name The name of the static method to call. + /// \param error Output parameter to receive error information if the call + /// fails. + /// \param args Arguments to pass to the static method. + /// + /// \return The return value of the static method call, or an error value. + template + T CallStaticMethod(llvm::StringRef class_name, llvm::StringRef method_name, + Status &error, Args &&...args) { + using namespace python; + using Locker = ScriptInterpreterPythonImpl::Locker; + + std::string caller_signature = + llvm::Twine(LLVM_PRETTY_FUNCTION + llvm::Twine(" (") + + llvm::Twine(class_name) + llvm::Twine(".") + + llvm::Twine(method_name) + llvm::Twine(")")) + .str(); + + if (class_name.empty()) + return ErrorWithMessage(caller_signature, "missing script class name", + error); + + Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, + Locker::FreeLock); + + // Get the interpreter dictionary. + auto dict = + PythonModule::MainModule().ResolveName( + m_interpreter.GetDictionaryName()); + if (!dict.IsAllocated()) + return ErrorWithMessage( + caller_signature, + llvm::formatv("could not find interpreter dictionary: {0}", + m_interpreter.GetDictionaryName()) + .str(), + error); + + // Resolve the class. + auto class_obj = + PythonObject::ResolveNameWithDictionary( + class_name, dict); + if (!class_obj.IsAllocated()) + return ErrorWithMessage( + caller_signature, + llvm::formatv("could not find script class: {0}", class_name).str(), + error); + + // Get the static method from the class. + if (!class_obj.HasAttribute(method_name)) + return ErrorWithMessage( + caller_signature, + llvm::formatv("class {0} does not have method {1}", class_name, + method_name) + .str(), + error); + + PythonCallable method = + class_obj.GetAttributeValue(method_name).AsType(); + if (!method.IsAllocated()) + return ErrorWithMessage(caller_signature, + llvm::formatv("method {0}.{1} is not callable", + class_name, method_name) + .str(), + error); + + // Transform the arguments. + std::tuple original_args = std::forward_as_tuple(args...); + auto transformed_args = TransformArgs(original_args); + + // Call the static method. + llvm::Expected expected_return_object = + llvm::make_error("Not initialized.", + llvm::inconvertibleErrorCode()); + std::apply( + [&method, &expected_return_object](auto &&...args) { + llvm::consumeError(expected_return_object.takeError()); + expected_return_object = method(args...); + }, + transformed_args); + + if (llvm::Error e = expected_return_object.takeError()) { + error = Status::FromError(std::move(e)); + return ErrorWithMessage( + caller_signature, "python static method could not be called", error); + } + + PythonObject py_return = std::move(expected_return_object.get()); + + // Re-assign reference and pointer arguments if needed. + if (sizeof...(Args) > 0) + if (!ReassignPtrsOrRefsArgs(original_args, transformed_args)) + return ErrorWithMessage( + caller_signature, + "couldn't re-assign reference and pointer arguments", error); + + // Extract value from Python object (handles unallocated case). + return ExtractValueFromPythonObject(py_return, error); + } + protected: template T ExtractValueFromPythonObject(python::PythonObject &p, Status &error) { @@ -403,7 +509,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { llvm::Twine(method_name) + llvm::Twine(")")) .str(); if (!m_object_instance_sp) - return ErrorWithMessage(caller_signature, "Python object ill-formed", + return ErrorWithMessage(caller_signature, "python object ill-formed", error); Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, @@ -415,7 +521,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { if (!implementor.IsAllocated()) return llvm::is_contained(GetAbstractMethods(), method_name) ? ErrorWithMessage(caller_signature, - "Python implementor not allocated.", + "python implementor not allocated", error) : T{}; @@ -436,20 +542,20 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { if (llvm::Error e = expected_return_object.takeError()) { error = Status::FromError(std::move(e)); return ErrorWithMessage(caller_signature, - "Python method could not be called.", error); + "python method could not be called", error); } PythonObject py_return = std::move(expected_return_object.get()); // Now that we called the python method with the transformed arguments, - // we need to interate again over both the original and transformed + // we need to iterate again over both the original and transformed // parameter pack, and transform back the parameter that were passed in // the original parameter pack as references or pointers. if (sizeof...(Args) > 0) if (!ReassignPtrsOrRefsArgs(original_args, transformed_args)) return ErrorWithMessage( caller_signature, - "Couldn't re-assign reference and pointer arguments.", error); + "couldn't re-assign reference and pointer arguments", error); if (!py_return.IsAllocated()) return {}; @@ -655,6 +761,11 @@ lldb::StreamSP ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); +template <> +lldb::ThreadSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error); + template <> lldb::StackFrameSP ScriptedPythonInterface::ExtractValueFromPythonObject( diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 2c971262fc34e..32948ffd30023 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -265,6 +265,7 @@ void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data); diff --git a/lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt b/lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt new file mode 100644 index 0000000000000..85b405e648c1f --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ScriptedFrameProvider) diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt new file mode 100644 index 0000000000000..fe67d39efdf11 --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt @@ -0,0 +1,12 @@ +add_lldb_library(lldbPluginScriptedFrameProvider PLUGIN + ScriptedFrameProvider.cpp + + LINK_COMPONENTS + Support + + LINK_LIBS + lldbCore + lldbInterpreter + lldbTarget + lldbUtility + ) diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp new file mode 100644 index 0000000000000..739963e6f0c2f --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp @@ -0,0 +1,221 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ScriptedFrameProvider.h" +#include "Plugins/Process/scripted/ScriptedFrame.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Target/BorrowedStackFrame.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" +#include + +using namespace lldb; +using namespace lldb_private; + +void ScriptedFrameProvider::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + "Provides synthetic frames via scripting", + nullptr, ScriptedFrameProvider::CreateInstance); +} + +void ScriptedFrameProvider::Terminate() { + PluginManager::UnregisterPlugin(ScriptedFrameProvider::CreateInstance); +} + +llvm::Expected +ScriptedFrameProvider::CreateInstance( + lldb::StackFrameListSP input_frames, + const ScriptedFrameProviderDescriptor &descriptor) { + if (!input_frames) + return llvm::createStringError( + "failed to create scripted frame provider: invalid input frames"); + + Thread &thread = input_frames->GetThread(); + ProcessSP process_sp = thread.GetProcess(); + if (!process_sp) + return nullptr; + + if (!descriptor.IsValid()) + return llvm::createStringError( + "failed to create scripted frame provider: invalid scripted metadata"); + + if (!descriptor.AppliesToThread(thread)) + return nullptr; + + ScriptInterpreter *script_interp = + process_sp->GetTarget().GetDebugger().GetScriptInterpreter(); + if (!script_interp) + return llvm::createStringError("cannot create scripted frame provider: No " + "script interpreter installed"); + + ScriptedFrameProviderInterfaceSP interface_sp = + script_interp->CreateScriptedFrameProviderInterface(); + if (!interface_sp) + return llvm::createStringError( + "cannot create scripted frame provider: script interpreter couldn't " + "create Scripted Frame Provider Interface"); + + const ScriptedMetadataSP scripted_metadata = descriptor.scripted_metadata_sp; + + // If we shouldn't attach a frame provider to this thread, just exit early. + if (!interface_sp->AppliesToThread(scripted_metadata->GetClassName(), + thread.shared_from_this())) + return nullptr; + + auto obj_or_err = interface_sp->CreatePluginObject( + scripted_metadata->GetClassName(), input_frames, + scripted_metadata->GetArgsSP()); + if (!obj_or_err) + return obj_or_err.takeError(); + + StructuredData::ObjectSP object_sp = *obj_or_err; + if (!object_sp || !object_sp->IsValid()) + return llvm::createStringError( + "cannot create scripted frame provider: failed to create valid scripted" + "frame provider object"); + + return std::make_shared(input_frames, interface_sp, + descriptor); +} + +ScriptedFrameProvider::ScriptedFrameProvider( + StackFrameListSP input_frames, + lldb::ScriptedFrameProviderInterfaceSP interface_sp, + const ScriptedFrameProviderDescriptor &descriptor) + : SyntheticFrameProvider(input_frames), m_interface_sp(interface_sp), + m_descriptor(descriptor) {} + +ScriptedFrameProvider::~ScriptedFrameProvider() = default; + +std::string ScriptedFrameProvider::GetDescription() const { + if (!m_interface_sp) + return {}; + + return m_interface_sp->GetDescription(m_descriptor.GetName()); +} + +llvm::Expected +ScriptedFrameProvider::GetFrameAtIndex(uint32_t idx) { + if (!m_interface_sp) + return llvm::createStringError( + "cannot get stack frame: scripted frame provider not initialized"); + + auto create_frame_from_dict = + [this](StructuredData::Dictionary *dict, + uint32_t index) -> llvm::Expected { + lldb::addr_t pc; + if (!dict->GetValueForKeyAsInteger("pc", pc)) + return llvm::createStringError( + "missing 'pc' key from scripted frame dictionary"); + + Address symbol_addr; + symbol_addr.SetLoadAddress(pc, &GetThread().GetProcess()->GetTarget()); + + const lldb::addr_t cfa = LLDB_INVALID_ADDRESS; + const bool cfa_is_valid = false; + const bool artificial = false; + const bool behaves_like_zeroth_frame = false; + SymbolContext sc; + symbol_addr.CalculateSymbolContext(&sc); + + ThreadSP thread_sp = GetThread().shared_from_this(); + return std::make_shared(thread_sp, index, index, cfa, + cfa_is_valid, pc, + StackFrame::Kind::Synthetic, artificial, + behaves_like_zeroth_frame, &sc); + }; + + auto create_frame_from_script_object = + [this]( + StructuredData::ObjectSP object_sp) -> llvm::Expected { + Status error; + if (!object_sp || !object_sp->GetAsGeneric()) + return llvm::createStringError("invalid script object"); + + ThreadSP thread_sp = GetThread().shared_from_this(); + auto frame_or_error = ScriptedFrame::Create(thread_sp, nullptr, nullptr, + object_sp->GetAsGeneric()); + + if (!frame_or_error) { + ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, toString(frame_or_error.takeError()), error); + return error.ToError(); + } + + return *frame_or_error; + }; + + StructuredData::ObjectSP obj_sp = m_interface_sp->GetFrameAtIndex(idx); + + // None/null means no more frames or error. + if (!obj_sp || !obj_sp->IsValid()) + return llvm::createStringError("invalid script object returned for frame " + + llvm::Twine(idx)); + + StackFrameSP synth_frame_sp = nullptr; + if (StructuredData::UnsignedInteger *int_obj = + obj_sp->GetAsUnsignedInteger()) { + uint32_t real_frame_index = int_obj->GetValue(); + if (real_frame_index < m_input_frames->GetNumFrames()) { + StackFrameSP real_frame_sp = + m_input_frames->GetFrameAtIndex(real_frame_index); + synth_frame_sp = + (real_frame_index == idx) + ? real_frame_sp + : std::make_shared(real_frame_sp, idx); + } + } else if (StructuredData::Dictionary *dict = obj_sp->GetAsDictionary()) { + // Check if it's a dictionary describing a frame. + auto frame_from_dict_or_err = create_frame_from_dict(dict, idx); + if (!frame_from_dict_or_err) { + return llvm::createStringError(llvm::Twine( + "couldn't create frame from dictionary at index " + llvm::Twine(idx) + + ": " + toString(frame_from_dict_or_err.takeError()))); + } + synth_frame_sp = *frame_from_dict_or_err; + } else if (obj_sp->GetAsGeneric()) { + // It's a ScriptedFrame object. + auto frame_from_script_obj_or_err = create_frame_from_script_object(obj_sp); + if (!frame_from_script_obj_or_err) { + return llvm::createStringError( + llvm::Twine("couldn't create frame from script object at index " + + llvm::Twine(idx) + ": " + + toString(frame_from_script_obj_or_err.takeError()))); + } + synth_frame_sp = *frame_from_script_obj_or_err; + } else { + return llvm::createStringError( + llvm::Twine("invalid return type from get_frame_at_index at index " + + llvm::Twine(idx))); + } + + if (!synth_frame_sp) + return llvm::createStringError( + llvm::Twine("failed to create frame at index " + llvm::Twine(idx))); + + synth_frame_sp->SetFrameIndex(idx); + + return synth_frame_sp; +} + +namespace lldb_private { +void lldb_initialize_ScriptedFrameProvider() { + ScriptedFrameProvider::Initialize(); +} + +void lldb_terminate_ScriptedFrameProvider() { + ScriptedFrameProvider::Terminate(); +} +} // namespace lldb_private diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h new file mode 100644 index 0000000000000..3434bf26ade24 --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// 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_SYNTHETICFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_H +#define LLDB_PLUGINS_SYNTHETICFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_H + +#include "lldb/Target/SyntheticFrameProvider.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 SyntheticFrameProvider { +public: + static llvm::StringRef GetPluginNameStatic() { + return "ScriptedFrameProvider"; + } + + static llvm::Expected + CreateInstance(lldb::StackFrameListSP input_frames, + const ScriptedFrameProviderDescriptor &descriptor); + + static void Initialize(); + + static void Terminate(); + + ScriptedFrameProvider(lldb::StackFrameListSP input_frames, + lldb::ScriptedFrameProviderInterfaceSP interface_sp, + const ScriptedFrameProviderDescriptor &descriptor); + ~ScriptedFrameProvider() override; + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + std::string GetDescription() const override; + + /// Get a single stack frame at the specified index. + llvm::Expected GetFrameAtIndex(uint32_t idx) override; + +private: + lldb::ScriptedFrameProviderInterfaceSP m_interface_sp; + const ScriptedFrameProviderDescriptor &m_descriptor; +}; + +} // namespace lldb_private + +#endif // LLDB_PLUGINS_SYNTHETICFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_H diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 8412e33aaba32..5d1a8a8370414 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -20,6 +20,7 @@ #include "lldb/Target/StackFrame.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/StopInfo.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Target/Unwind.h" @@ -55,6 +56,44 @@ StackFrameList::~StackFrameList() { Clear(); } +SyntheticStackFrameList::SyntheticStackFrameList( + Thread &thread, lldb::StackFrameListSP input_frames, + const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames) + : StackFrameList(thread, prev_frames_sp, show_inline_frames), + m_input_frames(std::move(input_frames)) {} + +bool SyntheticStackFrameList::FetchFramesUpTo( + uint32_t end_idx, InterruptionControl allow_interrupt) { + // Check if the thread has a synthetic frame provider. + if (auto provider_sp = m_thread.GetFrameProvider()) { + // Use the synthetic frame provider to generate frames lazily. + // Keep fetching until we reach end_idx or the provider returns an error. + for (uint32_t idx = m_frames.size(); idx <= end_idx; idx++) { + if (allow_interrupt && + m_thread.GetProcess()->GetTarget().GetDebugger().InterruptRequested()) + return true; + auto frame_or_err = provider_sp->GetFrameAtIndex(idx); + if (!frame_or_err) { + // Provider returned error - we've reached the end. + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), frame_or_err.takeError(), + "Frame provider reached end at index {0}: {1}", idx); + SetAllFramesFetched(); + break; + } + StackFrameSP frame_sp = *frame_or_err; + // Set the frame list weak pointer so ExecutionContextRef can resolve + // the frame without calling Thread::GetStackFrameList(). + frame_sp->m_frame_list_wp = shared_from_this(); + m_frames.push_back(frame_sp); + } + + return false; // Not interrupted. + } + + // If no provider, fall back to the base implementation. + return StackFrameList::FetchFramesUpTo(end_idx, allow_interrupt); +} + void StackFrameList::CalculateCurrentInlinedDepth() { uint32_t cur_inlined_depth = GetCurrentInlinedDepth(); if (cur_inlined_depth == UINT32_MAX) { diff --git a/lldb/source/Target/SyntheticFrameProvider.cpp b/lldb/source/Target/SyntheticFrameProvider.cpp index 241ce82c39be3..97ff42d1ed53e 100644 --- a/lldb/source/Target/SyntheticFrameProvider.cpp +++ b/lldb/source/Target/SyntheticFrameProvider.cpp @@ -8,10 +8,12 @@ #include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" using namespace lldb; using namespace lldb_private; @@ -21,12 +23,17 @@ SyntheticFrameProvider::SyntheticFrameProvider(StackFrameListSP input_frames) SyntheticFrameProvider::~SyntheticFrameProvider() = default; -void SyntheticFrameProviderDescriptor::Dump(Stream *s) const { +void ScriptedFrameProviderDescriptor::Dump(Stream *s) const { if (!s) return; + s->Format(" ID: {0:x}\n", GetID()); s->Printf(" Name: %s\n", GetName().str().c_str()); + std::string description = GetDescription(); + if (!description.empty()) + s->Printf(" Description: %s\n", description.c_str()); + // Show thread filter information. if (thread_specs.empty()) { s->PutCString(" Thread Filter: (applies to all threads)\n"); @@ -41,9 +48,23 @@ void SyntheticFrameProviderDescriptor::Dump(Stream *s) const { } } +uint32_t ScriptedFrameProviderDescriptor::GetID() const { + if (!scripted_metadata_sp) + return 0; + + return scripted_metadata_sp->GetID(); +} + +std::string ScriptedFrameProviderDescriptor::GetDescription() const { + // If we have an interface, call get_description() to fetch it. + if (interface_sp && scripted_metadata_sp) + return interface_sp->GetDescription(scripted_metadata_sp->GetClassName()); + return {}; +} + llvm::Expected SyntheticFrameProvider::CreateInstance( StackFrameListSP input_frames, - const SyntheticFrameProviderDescriptor &descriptor) { + const ScriptedFrameProviderDescriptor &descriptor) { if (!input_frames) return llvm::createStringError( "cannot create synthetic frame provider: invalid input frames"); diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 3a936b85f6339..b6a662ad3f14d 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3720,6 +3720,61 @@ Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { return error; } +llvm::Expected Target::AddScriptedFrameProviderDescriptor( + const ScriptedFrameProviderDescriptor &descriptor) { + if (!descriptor.IsValid()) + return llvm::createStringError("invalid frame provider descriptor"); + + llvm::StringRef name = descriptor.GetName(); + if (name.empty()) + return llvm::createStringError( + "frame provider descriptor has no class name"); + + std::lock_guard guard( + m_frame_provider_descriptors_mutex); + + uint32_t descriptor_id = descriptor.GetID(); + m_frame_provider_descriptors[descriptor_id] = descriptor; + + // Clear frame providers on existing threads so they reload with new config. + if (ProcessSP process_sp = GetProcessSP()) + for (ThreadSP thread_sp : process_sp->Threads()) + thread_sp->ClearScriptedFrameProvider(); + + return descriptor_id; +} + +bool Target::RemoveScriptedFrameProviderDescriptor(uint32_t id) { + std::lock_guard guard( + m_frame_provider_descriptors_mutex); + bool removed = m_frame_provider_descriptors.erase(id); + + if (removed) + if (ProcessSP process_sp = GetProcessSP()) + for (ThreadSP thread_sp : process_sp->Threads()) + thread_sp->ClearScriptedFrameProvider(); + + return removed; +} + +void Target::ClearScriptedFrameProviderDescriptors() { + std::lock_guard guard( + m_frame_provider_descriptors_mutex); + + m_frame_provider_descriptors.clear(); + + if (ProcessSP process_sp = GetProcessSP()) + for (ThreadSP thread_sp : process_sp->Threads()) + thread_sp->ClearScriptedFrameProvider(); +} + +const llvm::DenseMap & +Target::GetScriptedFrameProviderDescriptors() const { + std::lock_guard guard( + m_frame_provider_descriptors_mutex); + return m_frame_provider_descriptors; +} + void Target::FinalizeFileActions(ProcessLaunchInfo &info) { Log *log = GetLog(LLDBLog::Process); diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 8c3e19725f8cb..b40e753aca1e9 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -13,9 +13,12 @@ #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/Symbol/Function.h" #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" @@ -26,6 +29,7 @@ #include "lldb/Target/ScriptedThreadPlan.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/StopInfo.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadPlan.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" @@ -257,6 +262,7 @@ void Thread::DestroyThread() { std::lock_guard guard(m_frame_mutex); m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); + m_frame_provider_sp.reset(); m_prev_framezero_pc.reset(); } @@ -1439,13 +1445,76 @@ void Thread::CalculateExecutionContext(ExecutionContext &exe_ctx) { StackFrameListSP Thread::GetStackFrameList() { std::lock_guard guard(m_frame_mutex); - if (!m_curr_frames_sp) + if (m_curr_frames_sp) + return m_curr_frames_sp; + + // First, try to load a frame provider if we don't have one yet. + if (!m_frame_provider_sp) { + ProcessSP process_sp = GetProcess(); + if (process_sp) { + Target &target = process_sp->GetTarget(); + const auto &descriptors = target.GetScriptedFrameProviderDescriptors(); + + // Find first descriptor that applies to this thread. + for (const auto &entry : descriptors) { + const ScriptedFrameProviderDescriptor &descriptor = entry.second; + if (descriptor.IsValid() && descriptor.AppliesToThread(*this)) { + if (llvm::Error error = LoadScriptedFrameProvider(descriptor)) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error), + "Failed to load scripted frame provider: {0}"); + } + break; // Use first matching descriptor (success or failure). + } + } + } + } + + // Create the frame list based on whether we have a provider. + if (m_frame_provider_sp) { + // We have a provider - create synthetic frame list. + StackFrameListSP input_frames = m_frame_provider_sp->GetInputFrames(); + m_curr_frames_sp = std::make_shared( + *this, input_frames, m_prev_frames_sp, true); + } else { + // No provider - use normal unwinder frames. m_curr_frames_sp = std::make_shared(*this, m_prev_frames_sp, true); + } return m_curr_frames_sp; } +llvm::Error Thread::LoadScriptedFrameProvider( + const ScriptedFrameProviderDescriptor &descriptor) { + std::lock_guard guard(m_frame_mutex); + + // Note: We don't create input_frames here - it will be created lazily + // by SyntheticStackFrameList when frames are first fetched. + // Creating them too early can cause crashes during thread initialization. + + // Create a temporary StackFrameList just to get the thread reference for the + // provider. The provider won't actually use this - it will get real input + // frames from SyntheticStackFrameList later. + StackFrameListSP temp_frames = + std::make_shared(*this, m_prev_frames_sp, true); + + auto provider_or_err = + SyntheticFrameProvider::CreateInstance(temp_frames, descriptor); + if (!provider_or_err) + return provider_or_err.takeError(); + + ClearScriptedFrameProvider(); + m_frame_provider_sp = *provider_or_err; + return llvm::Error::success(); +} + +void Thread::ClearScriptedFrameProvider() { + std::lock_guard guard(m_frame_mutex); + m_frame_provider_sp.reset(); + m_curr_frames_sp.reset(); + m_prev_frames_sp.reset(); +} + std::optional Thread::GetPreviousFrameZeroPC() { return m_prev_framezero_pc; } @@ -1466,6 +1535,7 @@ void Thread::ClearStackFrames() { m_prev_frames_sp.swap(m_curr_frames_sp); m_curr_frames_sp.reset(); + m_frame_provider_sp.reset(); m_extended_info.reset(); m_extended_info_fetched = false; } diff --git a/lldb/source/Target/ThreadSpec.cpp b/lldb/source/Target/ThreadSpec.cpp index ba4c3aa894553..624f64e3af800 100644 --- a/lldb/source/Target/ThreadSpec.cpp +++ b/lldb/source/Target/ThreadSpec.cpp @@ -19,6 +19,10 @@ const char *ThreadSpec::g_option_names[static_cast( ThreadSpec::ThreadSpec() : m_name(), m_queue_name() {} +ThreadSpec::ThreadSpec(Thread &thread) + : m_index(thread.GetIndexID()), m_tid(thread.GetID()), + m_name(thread.GetName()), m_queue_name(thread.GetQueueName()) {} + std::unique_ptr ThreadSpec::CreateFromStructuredData( const StructuredData::Dictionary &spec_dict, Status &error) { uint32_t index = UINT32_MAX; 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..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules 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..3c0390ef72fd2 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -0,0 +1,418 @@ +""" +Test scripted frame provider functionality. +""" + +import os + +import lldb +from lldbsuite.test.lldbtest import TestBase +from lldbsuite.test import lldbutil + + +class ScriptedFrameProviderTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.source = "main.cpp" + + def test_replace_all_frames(self): + """Test that we can replace the entire stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # 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 + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ReplaceFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # 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.assertIn("thread_func", frame1.GetFunctionName()) + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual(frame2.GetPC(), 0x3000) + + def test_prepend_frames(self): + """Test that we can add frames before real stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count and PC + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + + # Import and attach Prepend provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.PrependFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # 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(), 0x9000) + + frame1 = thread.GetFrameAtIndex(1) + self.assertEqual(frame1.GetPC(), 0xA000) + + # Verify frame 2 is the original real frame 0 + frame2 = thread.GetFrameAtIndex(2) + self.assertIn("thread_func", frame2.GetFunctionName()) + + def test_append_frames(self): + """Test that we can add frames after real stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # 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) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.AppendFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 1 more frame + new_frame_count = thread.GetNumFrames() + self.assertEqual(new_frame_count, original_frame_count + 1) + + # Verify first frames are still real + frame0 = thread.GetFrameAtIndex(0) + self.assertIn("thread_func", frame0.GetFunctionName()) + + frame_n_plus_1 = thread.GetFrameAtIndex(new_frame_count - 1) + self.assertEqual(frame_n_plus_1.GetPC(), 0x10) + + 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), only_one_thread=False + ) + + # 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) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ScriptedFrameObjectProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 5 frames + self.assertEqual( + thread.GetNumFrames(), 5, "Should have 5 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.IsSynthetic(), "Frame should be marked as synthetic") + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + 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) + self.assertTrue(frame2.IsSynthetic(), "Frame should be marked as synthetic") + + def test_applies_to_thread(self): + """Test that applies_to_thread filters which threads get the provider.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # We should have at least 2 threads (worker threads) at the breakpoint + num_threads = process.GetNumThreads() + self.assertGreaterEqual( + num_threads, 2, "Should have at least 2 threads at breakpoint" + ) + + # Import the test frame provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Collect original thread info before applying provider + thread_info = {} + for i in range(num_threads): + t = process.GetThreadAtIndex(i) + thread_info[t.GetIndexID()] = { + "frame_count": t.GetNumFrames(), + "pc": t.GetFrameAtIndex(0).GetPC(), + } + + # Register the ThreadFilterFrameProvider which only applies to thread ID 1 + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ThreadFilterFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Check each thread + thread_id_1_found = False + for i in range(num_threads): + t = process.GetThreadAtIndex(i) + thread_id = t.GetIndexID() + + if thread_id == 1: + # Thread with ID 1 should have synthetic frame + thread_id_1_found = True + self.assertEqual( + t.GetNumFrames(), + 1, + f"Thread with ID 1 should have 1 synthetic frame", + ) + self.assertEqual( + t.GetFrameAtIndex(0).GetPC(), + 0xFFFF, + f"Thread with ID 1 should have synthetic PC 0xFFFF", + ) + else: + # Other threads should keep their original frames + self.assertEqual( + t.GetNumFrames(), + thread_info[thread_id]["frame_count"], + f"Thread with ID {thread_id} should not be affected by provider", + ) + self.assertEqual( + t.GetFrameAtIndex(0).GetPC(), + thread_info[thread_id]["pc"], + f"Thread with ID {thread_id} should have its original PC", + ) + + # We should have found at least one thread with ID 1 + self.assertTrue( + thread_id_1_found, + "Should have found a thread with ID 1 to test filtering", + ) + + def test_remove_frame_provider_by_id(self): + """Test that RemoveScriptedFrameProvider removes a specific provider by ID.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Import the test frame providers + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Get original frame count + original_frame_count = thread.GetNumFrames() + original_pc = thread.GetFrameAtIndex(0).GetPC() + + # Register the first provider and get its ID + error = lldb.SBError() + provider_id_1 = target.RegisterScriptedFrameProvider( + "test_frame_providers.ReplaceFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider 1: {error}") + + # Verify first provider is active (3 synthetic frames) + self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames") + self.assertEqual( + thread.GetFrameAtIndex(0).GetPC(), 0x1000, "Should have first provider's PC" + ) + + # Register a second provider and get its ID + provider_id_2 = target.RegisterScriptedFrameProvider( + "test_frame_providers.PrependFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider 2: {error}") + + # Verify IDs are different + self.assertNotEqual( + provider_id_1, provider_id_2, "Provider IDs should be unique" + ) + + # Now remove the first provider by ID + result = target.RemoveScriptedFrameProvider(provider_id_1) + self.assertSuccess( + result, f"Should successfully remove provider with ID {provider_id_1}" + ) + + # After removing the first provider, the second provider should still be active + # The PrependFrameProvider adds 2 frames before the real stack + # Since ReplaceFrameProvider had 3 frames, and we removed it, we should now + # have the original frames (from real stack) with PrependFrameProvider applied + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 2, + "Should have original frames + 2 prepended frames", + ) + + # First two frames should be from PrependFrameProvider + self.assertEqual( + thread.GetFrameAtIndex(0).GetPC(), + 0x9000, + "First frame should be from PrependFrameProvider", + ) + self.assertEqual( + thread.GetFrameAtIndex(1).GetPC(), + 0xA000, + "Second frame should be from PrependFrameProvider", + ) + + # Remove the second provider + result = target.RemoveScriptedFrameProvider(provider_id_2) + self.assertSuccess( + result, f"Should successfully remove provider with ID {provider_id_2}" + ) + + # After removing both providers, frames should be back to original + self.assertEqual( + thread.GetNumFrames(), + original_frame_count, + "Should restore original frame count", + ) + self.assertEqual( + thread.GetFrameAtIndex(0).GetPC(), + original_pc, + "Should restore original PC", + ) + + # Try to remove a provider that doesn't exist + result = target.RemoveScriptedFrameProvider(999999) + self.assertTrue(result.Fail(), "Should fail to remove non-existent provider") + + def test_circular_dependency_fix(self): + """Test that accessing input_frames in __init__ doesn't cause circular dependency. + + This test verifies the fix for the circular dependency issue where: + 1. Thread::GetStackFrameList() creates the frame provider + 2. Provider's __init__ accesses input_frames and calls methods on frames + 3. SBFrame methods trigger ExecutionContextRef::GetFrameSP() + 4. Before the fix: GetFrameSP() would call Thread::GetStackFrameList() again -> circular dependency! + 5. After the fix: GetFrameSP() uses the remembered frame list -> no circular dependency + + The fix works by: + - StackFrame stores m_frame_list_wp (weak pointer to originating list) + - ExecutionContextRef stores m_frame_list_wp when created from a frame + - ExecutionContextRef::GetFrameSP() tries the remembered list first before asking the thread + """ + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count and PC + original_frame_count = thread.GetNumFrames() + original_pc = thread.GetFrameAtIndex(0).GetPC() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + + # Import the provider that accesses input frames in __init__ + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Register the CircularDependencyTestProvider + # Before the fix, this would crash or hang due to circular dependency + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.CircularDependencyTestProvider", + lldb.SBStructuredData(), + error, + ) + + # If we get here without crashing, the fix is working! + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify the provider worked correctly + # Should have 1 synthetic frame + all original frames + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 1, + "Should have original frames + 1 synthetic frame", + ) + + # First frame should be synthetic + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetPC(), + 0xDEADBEEF, + "First frame should be synthetic frame with PC 0xDEADBEEF", + ) + + # Second frame should be the original first frame + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetPC(), + original_pc, + "Second frame should be original first frame", + ) + + # Verify we can still call methods on frames (no circular dependency!) + for i in range(min(3, new_frame_count)): + frame = thread.GetFrameAtIndex(i) + self.assertIsNotNone(frame) + # These calls should not trigger circular dependency + pc = frame.GetPC() + self.assertNotEqual(pc, 0, f"Frame {i} should have valid PC") diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py new file mode 100644 index 0000000000000..e03583a99425b --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py @@ -0,0 +1,117 @@ +""" +Test that frame providers wouldn't cause a hang due to a circular dependency +during its initialization. +""" + +import os +import lldb +from lldbsuite.test.lldbtest import TestBase +from lldbsuite.test import lldbutil + + +class FrameProviderCircularDependencyTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.source = "main.c" + + def test_circular_dependency_with_function_replacement(self): + """ + Test the circular dependency fix with a provider that replaces function names. + """ + self.build() + + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, "Target should be valid") + + bkpt = target.BreakpointCreateBySourceRegex( + "break here", lldb.SBFileSpec(self.source) + ) + self.assertTrue(bkpt.IsValid(), "Breakpoint should be valid") + self.assertEqual(bkpt.GetNumLocations(), 1, "Should have 1 breakpoint location") + + process = target.LaunchSimple(None, None, self.get_process_working_directory()) + self.assertTrue(process, "Process should be valid") + self.assertEqual( + process.GetState(), lldb.eStateStopped, "Process should be stopped" + ) + + thread = process.GetSelectedThread() + self.assertTrue(thread.IsValid(), "Thread should be valid") + + frame0 = thread.GetFrameAtIndex(0) + self.assertIn("bar", frame0.GetFunctionName(), "Should be stopped in bar()") + + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 3, "Should have at least 3 frames: bar, foo, main" + ) + + frame_names = [thread.GetFrameAtIndex(i).GetFunctionName() for i in range(3)] + self.assertEqual(frame_names[0], "bar", "Frame 0 should be bar") + self.assertEqual(frame_names[1], "foo", "Frame 1 should be foo") + self.assertEqual(frame_names[2], "main", "Frame 2 should be main") + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + # Register the frame provider that accesses input_frames. + # Before the fix, this registration would trigger the circular dependency: + # - Thread::GetStackFrameList() creates provider + # - Provider's get_frame_at_index() accesses input_frames[0] + # - Calls frame.GetFunctionName() -> ExecutionContextRef::GetFrameSP() + # - Before fix: Calls Thread::GetStackFrameList() again -> CIRCULAR! + # - After fix: Uses remembered m_frame_list_wp -> Works! + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.ScriptedFrameObjectProvider", + lldb.SBStructuredData(), + error, + ) + + # If we reach here without crashing/hanging, the fix is working! + self.assertTrue( + error.Success(), + f"Should successfully register provider (if this fails, circular dependency!): {error}", + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify the provider is working correctly. + # Frame count should be unchanged (we're replacing frames, not adding). + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count, + "Frame count should be unchanged (replacement, not addition)", + ) + + # Verify that "bar" was replaced with "baz". + frame0_new = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0_new, "Frame 0 should exist") + self.assertEqual( + frame0_new.GetFunctionName(), + "baz", + "Frame 0 function should be replaced: bar -> baz", + ) + + # Verify other frames are unchanged. + frame1_new = thread.GetFrameAtIndex(1) + self.assertEqual( + frame1_new.GetFunctionName(), "foo", "Frame 1 should still be foo" + ) + + frame2_new = thread.GetFrameAtIndex(2) + self.assertEqual( + frame2_new.GetFunctionName(), "main", "Frame 2 should still be main" + ) + + # Verify we can call methods on all frames (no circular dependency!). + for i in range(new_frame_count): + frame = thread.GetFrameAtIndex(i) + self.assertIsNotNone(frame, f"Frame {i} should exist") + # 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") diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py new file mode 100644 index 0000000000000..f27f18cd07b7f --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py @@ -0,0 +1,102 @@ +""" +Frame provider that reproduces the circular dependency issue. + +This provider accesses input_frames and calls methods on them, +which before the fix would cause a circular dependency. +""" + +import lldb +from lldb.plugins.scripted_process import ScriptedFrame +from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider + + +class CustomScriptedFrame(ScriptedFrame): + """Custom scripted frame with full control over frame behavior.""" + + def __init__(self, thread, idx, pc, function_name): + 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 False + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + return None + + +class ScriptedFrameObjectProvider(ScriptedFrameProvider): + """ + Provider that returns ScriptedFrame objects and accesses input_frames. + + This provider demonstrates the circular dependency bug fix: + 1. During get_frame_at_index(), we access input_frames[idx] + 2. We call frame.GetFunctionName() and frame.GetPC() on input frames + 3. Before the fix: These calls would trigger ExecutionContextRef::GetFrameSP() + which would call Thread::GetStackFrameList() -> circular dependency! + 4. After the fix: ExecutionContextRef uses the remembered frame list -> no circular dependency + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + self.replacement_count = 0 + if self.target.process: + baz_symbol_ctx = self.target.FindFunctions("baz") + self.baz_symbol_ctx = None + if len(baz_symbol_ctx) == 1: + self.baz_symbol_ctx = baz_symbol_ctx[0] + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that replaces 'bar' function with 'baz'" + + def get_frame_at_index(self, idx): + """ + Replace frames named 'bar' with custom frames named 'baz'. + + This accesses input_frames and calls methods on them, which would + trigger the circular dependency bug before the fix. + """ + if idx < len(self.input_frames): + # This access and method calls would cause circular dependency before fix! + frame = self.input_frames[idx] + + # Calling GetFunctionName() triggers ExecutionContextRef resolution. + function_name = frame.GetFunctionName() + + if function_name == "bar" and self.baz_symbol_ctx: + # Replace "bar" with "baz". + baz_func = self.baz_symbol_ctx.GetFunction() + new_function_name = baz_func.GetName() + pc = baz_func.GetStartAddress().GetLoadAddress(self.target) + custom_frame = CustomScriptedFrame( + self.thread, idx, pc, new_function_name + ) + self.replacement_count += 1 + return custom_frame + + # Pass through other frames by returning their index. + return idx + + return None diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c new file mode 100644 index 0000000000000..bbd1028236f40 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c @@ -0,0 +1,21 @@ +#include + +int baz() { + printf("baz\n"); + return 666; +} + +int bar() { + printf("bar\n"); + return 42; // break here. +} + +int foo() { + printf("foo\n"); + return bar(); +} + +int main() { + printf("main\n"); + return foo(); +} diff --git a/lldb/test/API/functionalities/scripted_frame_provider/main.cpp b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp new file mode 100644 index 0000000000000..0298e88e4de16 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp @@ -0,0 +1,53 @@ +// Multi-threaded test program for testing frame providers. + +#include +#include +#include +#include + +std::mutex mtx; +std::condition_variable cv; +int ready_count = 0; +constexpr int NUM_THREADS = 2; + +void thread_func(int thread_num) { + std::cout << "Thread " << thread_num << " started\n"; + + { + std::unique_lock lock(mtx); + ready_count++; + if (ready_count == NUM_THREADS + 1) { + cv.notify_all(); + } else { + cv.wait(lock, [] { return ready_count == NUM_THREADS + 1; }); + } + } + + std::cout << "Thread " << thread_num << " at breakpoint\n"; // Break here. +} + +int main(int argc, char **argv) { + std::thread threads[NUM_THREADS]; + + for (int i = 0; i < NUM_THREADS; i++) { + threads[i] = std::thread(thread_func, i); + } + + { + std::unique_lock lock(mtx); + ready_count++; + if (ready_count == NUM_THREADS + 1) { + cv.notify_all(); + } else { + cv.wait(lock, [] { return ready_count == NUM_THREADS + 1; }); + } + } + + std::cout << "Main thread at barrier\n"; + + for (int i = 0; i < NUM_THREADS; i++) + threads[i].join(); + + std::cout << "All threads completed\n"; + return 0; +} 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..b9731fdc0a197 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py @@ -0,0 +1,222 @@ +""" +Test frame providers for scripted frame provider functionality. + +These providers demonstrate various merge strategies: +- Replace: Replace entire stack +- Prepend: Add frames before real stack +- Append: Add frames after real stack + +It also shows the ability to mix a dictionary, a ScriptedFrame or an SBFrame +index to create stackframes +""" + +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, input_frames, args): + super().__init__(input_frames, args) + self.frames = [ + { + "idx": 0, + "pc": 0x1000, + }, + 0, + { + "idx": 2, + "pc": 0x3000, + }, + ] + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Replace entire stack with 3 custom frames" + + def get_frame_at_index(self, index): + if index >= len(self.frames): + return None + return self.frames[index] + + +class PrependFrameProvider(ScriptedFrameProvider): + """Prepend synthetic frames before real stack.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Prepend 2 synthetic frames before real stack" + + def get_frame_at_index(self, index): + if index == 0: + return {"pc": 0x9000} + elif index == 1: + return {"pc": 0xA000} + elif index - 2 < len(self.input_frames): + return index - 2 # Return real frame index. + return None + + +class AppendFrameProvider(ScriptedFrameProvider): + """Append synthetic frames after real stack.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Append 1 synthetic frame after real stack" + + def get_frame_at_index(self, index): + if index < len(self.input_frames): + return index # Return real frame index. + elif index == len(self.input_frames): + return { + "idx": 1, + "pc": 0x10, + } + return None + + +class CustomScriptedFrame(ScriptedFrame): + """Custom scripted frame with full control over frame behavior.""" + + def __init__(self, thread, idx, pc, function_name): + 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 False + + 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, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider returning custom ScriptedFrame objects" + + def get_frame_at_index(self, index): + """Return ScriptedFrame objects or dictionaries based on index.""" + if index == 0: + return CustomScriptedFrame( + self.thread, 0, 0x5000, "custom_scripted_frame_0" + ) + elif index == 1: + return {"pc": 0x6000} + elif index == 2: + return CustomScriptedFrame( + self.thread, 2, 0x7000, "custom_scripted_frame_2" + ) + elif index == 3: + return len(self.input_frames) - 2 # Real frame index. + elif index == 4: + return len(self.input_frames) - 1 # Real frame index. + return None + + +class ThreadFilterFrameProvider(ScriptedFrameProvider): + """Provider that only applies to thread with ID 1.""" + + @staticmethod + def applies_to_thread(thread): + """Only apply to thread with index ID 1.""" + return thread.GetIndexID() == 1 + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that only applies to thread ID 1" + + def get_frame_at_index(self, index): + """Return a single synthetic frame.""" + if index == 0: + return {"pc": 0xFFFF} + return None + + +class CircularDependencyTestProvider(ScriptedFrameProvider): + """ + Provider that tests the circular dependency fix. + + This provider accesses input_frames during __init__ and calls methods + on those frames. Before the fix, this would cause a circular dependency: + - Thread::GetStackFrameList() creates provider + - Provider's __init__ accesses input_frames[0] + - SBFrame::GetPC() tries to resolve ExecutionContextRef + - ExecutionContextRef::GetFrameSP() calls Thread::GetStackFrameList() + - Re-enters initialization -> circular dependency! + + With the fix, ExecutionContextRef remembers the frame list, so it doesn't + re-enter Thread::GetStackFrameList(). + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + # This would cause circular dependency before the fix! + # Accessing frames and calling methods on them during __init__ + self.original_frame_count = len(input_frames) + self.original_pcs = [] + + # Call GetPC() on each input frame - this triggers ExecutionContextRef resolution. + for i in range(min(3, len(input_frames))): + frame = input_frames[i] + if frame.IsValid(): + pc = frame.GetPC() + self.original_pcs.append(pc) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that tests circular dependency fix by accessing frames in __init__" + + def get_frame_at_index(self, index): + """Prepend a synthetic frame, then pass through original frames.""" + if index == 0: + # Synthetic frame at index 0. + return {"pc": 0xDEADBEEF} + elif index - 1 < self.original_frame_count: + # Pass through original frames at indices 1, 2, 3, ... + return index - 1 + return None diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index a63b740d9472f..5694aeeff3e5b 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -136,6 +136,11 @@ lldb_private::python::LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data) { return nullptr; } +void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data) { + return nullptr; +} + void * lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data) { return nullptr;