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