mirror of
https://github.com/intel/llvm.git
synced 2026-01-12 10:17:28 +08:00
This patch re-lands #161870 with fixes to the previous test failures. rdar://161834688 Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
This commit is contained in:
committed by
GitHub
parent
879dddf2b4
commit
c50802cbee
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@ class ScriptedThread(metaclass=ABCMeta):
|
||||
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 @@ class ScriptedThread(metaclass=ABCMeta):
|
||||
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 @@ class ScriptedThread(metaclass=ABCMeta):
|
||||
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 @@ class ScriptedFrame(metaclass=ABCMeta):
|
||||
"""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 @@ class ScriptedFrame(metaclass=ABCMeta):
|
||||
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 @@ class ScriptedFrame(metaclass=ABCMeta):
|
||||
|
||||
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 @@ class PassthroughScriptedThread(ScriptedThread):
|
||||
|
||||
# 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:
|
||||
|
||||
@@ -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 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
@@ -256,6 +256,7 @@ private:
|
||||
friend class SBThreadPlan;
|
||||
friend class SBTrace;
|
||||
|
||||
friend class lldb_private::ScriptInterpreter;
|
||||
friend class lldb_private::python::SWIGBridge;
|
||||
|
||||
SBThread(const lldb::ThreadSP &lldb_object_sp);
|
||||
|
||||
@@ -46,6 +46,7 @@ protected:
|
||||
void SetOpaque(const lldb::ThreadCollectionSP &threads);
|
||||
|
||||
private:
|
||||
friend class SBTarget;
|
||||
friend class SBProcess;
|
||||
friend class SBThread;
|
||||
friend class SBSaveCoreOptions;
|
||||
|
||||
@@ -16,11 +16,29 @@
|
||||
namespace lldb_private {
|
||||
class ScriptedFrameProviderInterface : public ScriptedInterface {
|
||||
public:
|
||||
virtual bool AppliesToThread(llvm::StringRef class_name,
|
||||
lldb::ThreadSP thread_sp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual llvm::Expected<StructuredData::GenericSP>
|
||||
CreatePluginObject(llvm::StringRef class_name,
|
||||
lldb::StackFrameListSP input_frames,
|
||||
StructuredData::DictionarySP args_sp) = 0;
|
||||
|
||||
/// Get a description string for the frame provider.
|
||||
///
|
||||
/// This is called by the descriptor to fetch a description from the
|
||||
/// scripted implementation. Implementations should call a static method
|
||||
/// on the scripting class to retrieve the description.
|
||||
///
|
||||
/// \param class_name The name of the scripting class implementing the
|
||||
/// provider.
|
||||
///
|
||||
/// \return A string describing what this frame provider does, or an
|
||||
/// empty string if no description is available.
|
||||
virtual std::string GetDescription(llvm::StringRef class_name) { return {}; }
|
||||
|
||||
virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -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 @@ public:
|
||||
|
||||
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
|
||||
|
||||
@@ -451,7 +451,7 @@ public:
|
||||
/// 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 @@ public:
|
||||
protected:
|
||||
friend class BorrowedStackFrame;
|
||||
friend class StackFrameList;
|
||||
friend class SyntheticStackFrameList;
|
||||
|
||||
void SetSymbolContextScope(SymbolContextScope *symbol_scope);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
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 @@ public:
|
||||
|
||||
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 @@ protected:
|
||||
/// 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<std::shared_mutex> &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 @@ private:
|
||||
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
|
||||
|
||||
@@ -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<ThreadSpec> 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<ThreadSpec> &specs)
|
||||
ScriptedFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp,
|
||||
const std::vector<ThreadSpec> &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 @@ public:
|
||||
/// otherwise an \a llvm::Error.
|
||||
static llvm::Expected<lldb::SyntheticFrameProviderSP>
|
||||
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 @@ public:
|
||||
|
||||
~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.
|
||||
|
||||
@@ -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 @@ public:
|
||||
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<uint32_t> 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<uint32_t, ScriptedFrameProviderDescriptor> &
|
||||
GetScriptedFrameProviderDescriptors() const;
|
||||
|
||||
// This part handles the breakpoints.
|
||||
|
||||
BreakpointList &GetBreakpointList(bool internal = false);
|
||||
@@ -1744,6 +1775,13 @@ protected:
|
||||
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<uint32_t, ScriptedFrameProviderDescriptor>
|
||||
m_frame_provider_descriptors;
|
||||
mutable std::recursive_mutex m_frame_provider_descriptors_mutex;
|
||||
|
||||
typedef std::map<lldb::LanguageType, lldb::REPLSP> REPLMap;
|
||||
REPLMap m_repl_map;
|
||||
|
||||
|
||||
@@ -1297,6 +1297,15 @@ public:
|
||||
|
||||
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 @@ protected:
|
||||
/// 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?
|
||||
|
||||
@@ -34,6 +34,8 @@ class ThreadSpec {
|
||||
public:
|
||||
ThreadSpec();
|
||||
|
||||
ThreadSpec(Thread &thread);
|
||||
|
||||
static std::unique_ptr<ThreadSpec>
|
||||
CreateFromStructuredData(const StructuredData::Dictionary &data_dict,
|
||||
Status &error);
|
||||
|
||||
@@ -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 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
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<uint32_t>(hash);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_class_name;
|
||||
StructuredData::DictionarySP m_args_sp;
|
||||
|
||||
@@ -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<Architecture> (*ArchitectureCreateInstance)(
|
||||
@@ -91,7 +91,7 @@ typedef lldb::ScriptInterpreterSP (*ScriptInterpreterCreateInstance)(
|
||||
typedef llvm::Expected<lldb::SyntheticFrameProviderSP> (
|
||||
*ScriptedFrameProviderCreateInstance)(
|
||||
lldb::StackFrameListSP input_frames,
|
||||
const lldb_private::SyntheticFrameProviderDescriptor &descriptor);
|
||||
const lldb_private::ScriptedFrameProviderDescriptor &descriptor);
|
||||
typedef llvm::Expected<lldb::SyntheticFrameProviderSP> (
|
||||
*SyntheticFrameProviderCreateInstance)(
|
||||
lldb::StackFrameListSP input_frames,
|
||||
|
||||
@@ -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<StructuredData::Dictionary>(obj_sp);
|
||||
}
|
||||
|
||||
// Create the ScriptedMetadata.
|
||||
ScriptedMetadataSP metadata_sp =
|
||||
std::make_shared<ScriptedMetadata>(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<uint32_t> 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 {};
|
||||
}
|
||||
|
||||
@@ -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 @@ public:
|
||||
~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<std::string> 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<ScriptedMetadata>(
|
||||
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 <provider-id>",
|
||||
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<uint32_t> 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 [<sub-command-options>] ") {
|
||||
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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<std::shared_ptr<ScriptedFrame>>
|
||||
ScriptedFrame::Create(ScriptedThread &thread,
|
||||
ScriptedFrame::Create(ThreadSP thread_sp,
|
||||
ScriptedThreadInterfaceSP scripted_thread_interface_sp,
|
||||
StructuredData::DictionarySP args_sp,
|
||||
StructuredData::Generic *script_object) {
|
||||
if (!thread.IsValid())
|
||||
return llvm::createStringError("Invalid scripted thread.");
|
||||
if (!thread_sp || !thread_sp->IsValid())
|
||||
return llvm::createStringError("invalid thread");
|
||||
|
||||
thread.CheckInterpreterAndScriptObject();
|
||||
ProcessSP process_sp = thread_sp->GetProcess();
|
||||
if (!process_sp || !process_sp->IsValid())
|
||||
return llvm::createStringError("invalid process");
|
||||
|
||||
auto scripted_frame_interface =
|
||||
thread.GetInterface()->CreateScriptedFrameInterface();
|
||||
ScriptInterpreter *script_interp =
|
||||
process_sp->GetTarget().GetDebugger().GetScriptInterpreter();
|
||||
if (!script_interp)
|
||||
return llvm::createStringError("no script interpreter");
|
||||
|
||||
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<std::string> class_name =
|
||||
thread.GetInterface()->GetScriptedFramePluginName();
|
||||
if (!class_name || class_name->empty())
|
||||
// If no script object is provided and we have a scripted thread interface,
|
||||
// try to get the frame class name from it.
|
||||
if (scripted_thread_interface_sp) {
|
||||
std::optional<std::string> class_name =
|
||||
scripted_thread_interface_sp->GetScriptedFramePluginName();
|
||||
if (!class_name || class_name->empty())
|
||||
return llvm::createStringError(
|
||||
"failed to get scripted frame class name");
|
||||
frame_class_name = *class_name;
|
||||
} else {
|
||||
return llvm::createStringError(
|
||||
"failed to get scripted thread class name");
|
||||
frame_class_name = *class_name;
|
||||
"no script object provided and no scripted thread interface");
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionContext exe_ctx(thread);
|
||||
ExecutionContext exe_ctx(thread_sp);
|
||||
auto obj_or_err = scripted_frame_interface->CreatePluginObject(
|
||||
frame_class_name, exe_ctx, args_sp, script_object);
|
||||
|
||||
@@ -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<DynamicRegisterInfo> register_info_sp =
|
||||
DynamicRegisterInfo::Create(
|
||||
*reg_info, thread.GetProcess()->GetTarget().GetArchitecture());
|
||||
DynamicRegisterInfo::Create(*reg_info,
|
||||
process_sp->GetTarget().GetArchitecture());
|
||||
|
||||
lldb::RegisterContextSP reg_ctx_sp;
|
||||
|
||||
@@ -98,32 +126,35 @@ ScriptedFrame::Create(ScriptedThread &thread,
|
||||
|
||||
std::shared_ptr<RegisterContextMemory> reg_ctx_memory =
|
||||
std::make_shared<RegisterContextMemory>(
|
||||
thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS);
|
||||
*thread_sp, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS);
|
||||
if (!reg_ctx_memory)
|
||||
return llvm::createStringError("failed to create a register context.");
|
||||
return llvm::createStringError("failed to create a register context");
|
||||
|
||||
reg_ctx_memory->SetAllRegisterData(data_sp);
|
||||
reg_ctx_sp = reg_ctx_memory;
|
||||
}
|
||||
|
||||
return std::make_shared<ScriptedFrame>(
|
||||
thread, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
|
||||
thread_sp, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
|
||||
register_info_sp, owned_script_object_sp);
|
||||
}
|
||||
|
||||
ScriptedFrame::ScriptedFrame(ScriptedThread &thread,
|
||||
ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
|
||||
ScriptedFrameInterfaceSP interface_sp,
|
||||
lldb::user_id_t id, lldb::addr_t pc,
|
||||
SymbolContext &sym_ctx,
|
||||
lldb::RegisterContextSP reg_ctx_sp,
|
||||
std::shared_ptr<DynamicRegisterInfo> reg_info_sp,
|
||||
StructuredData::GenericSP script_object_sp)
|
||||
: StackFrame(thread.shared_from_this(), /*frame_idx=*/id,
|
||||
: StackFrame(thread_sp, /*frame_idx=*/id,
|
||||
/*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp,
|
||||
/*cfa=*/0, /*pc=*/pc,
|
||||
/*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx),
|
||||
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<DynamicRegisterInfo> ScriptedFrame::GetDynamicRegisterInfo() {
|
||||
if (!reg_info)
|
||||
return ScriptedInterface::ErrorWithMessage<
|
||||
std::shared_ptr<DynamicRegisterInfo>>(
|
||||
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<DynamicRegisterInfo> ScriptedFrame::GetDynamicRegisterInfo() {
|
||||
return ScriptedInterface::ErrorWithMessage<
|
||||
std::shared_ptr<DynamicRegisterInfo>>(
|
||||
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<DynamicRegisterInfo> ScriptedFrame::GetDynamicRegisterInfo() {
|
||||
return ScriptedInterface::ErrorWithMessage<
|
||||
std::shared_ptr<DynamicRegisterInfo>>(
|
||||
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());
|
||||
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
namespace lldb_private {
|
||||
class ScriptedThread;
|
||||
}
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
class ScriptedFrame : public lldb_private::StackFrame {
|
||||
|
||||
public:
|
||||
ScriptedFrame(ScriptedThread &thread,
|
||||
ScriptedFrame(lldb::ThreadSP thread_sp,
|
||||
lldb::ScriptedFrameInterfaceSP interface_sp,
|
||||
lldb::user_id_t frame_idx, lldb::addr_t pc,
|
||||
SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp,
|
||||
@@ -33,8 +31,29 @@ public:
|
||||
|
||||
~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<std::shared_ptr<ScriptedFrame>>
|
||||
Create(ScriptedThread &thread, StructuredData::DictionarySP args_sp,
|
||||
Create(lldb::ThreadSP thread_sp,
|
||||
lldb::ScriptedThreadInterfaceSP scripted_thread_interface_sp,
|
||||
StructuredData::DictionarySP args_sp,
|
||||
StructuredData::Generic *script_object = nullptr);
|
||||
|
||||
bool IsInlined() override;
|
||||
|
||||
@@ -210,7 +210,7 @@ bool ScriptedThread::LoadArtificialStackFrames() {
|
||||
SymbolContext sc;
|
||||
symbol_addr.CalculateSymbolContext(&sc);
|
||||
|
||||
return std::make_shared<StackFrame>(this->shared_from_this(), idx, idx, cfa,
|
||||
return std::make_shared<StackFrame>(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<bool>(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<StructuredData::GenericSP>
|
||||
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<llvm::StringRef> ci_usages = {
|
||||
"target frame-provider register -C <script-name> [-k key -v value ...]",
|
||||
"target frame-provider list",
|
||||
"target frame-provider remove <provider-name>",
|
||||
"target frame-provider clear"};
|
||||
const std::vector<llvm::StringRef> 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
|
||||
|
||||
@@ -14,17 +14,22 @@
|
||||
#if LLDB_ENABLE_PYTHON
|
||||
|
||||
#include "ScriptedPythonInterface.h"
|
||||
#include "lldb/Core/PluginInterface.h"
|
||||
#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
|
||||
#include <optional>
|
||||
|
||||
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<StructuredData::GenericSP>
|
||||
CreatePluginObject(llvm::StringRef class_name,
|
||||
lldb::StackFrameListSP input_frames,
|
||||
@@ -33,10 +38,24 @@ public:
|
||||
llvm::SmallVector<AbstractMethodRequirement>
|
||||
GetAbstractMethodRequirements() const override {
|
||||
return llvm::SmallVector<AbstractMethodRequirement>(
|
||||
{{"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
|
||||
|
||||
|
||||
@@ -93,6 +93,19 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <>
|
||||
lldb::ThreadSP
|
||||
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>(
|
||||
python::PythonObject &p, Status &error) {
|
||||
if (lldb::SBThread *sb_thread = reinterpret_cast<lldb::SBThread *>(
|
||||
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<SymbolContext>(
|
||||
|
||||
@@ -387,6 +387,112 @@ public:
|
||||
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 <typename T = StructuredData::ObjectSP, typename... Args>
|
||||
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<T>(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<python::PythonDictionary>(
|
||||
m_interpreter.GetDictionaryName());
|
||||
if (!dict.IsAllocated())
|
||||
return ErrorWithMessage<T>(
|
||||
caller_signature,
|
||||
llvm::formatv("could not find interpreter dictionary: {0}",
|
||||
m_interpreter.GetDictionaryName())
|
||||
.str(),
|
||||
error);
|
||||
|
||||
// Resolve the class.
|
||||
auto class_obj =
|
||||
PythonObject::ResolveNameWithDictionary<python::PythonCallable>(
|
||||
class_name, dict);
|
||||
if (!class_obj.IsAllocated())
|
||||
return ErrorWithMessage<T>(
|
||||
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<T>(
|
||||
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<PythonCallable>();
|
||||
if (!method.IsAllocated())
|
||||
return ErrorWithMessage<T>(caller_signature,
|
||||
llvm::formatv("method {0}.{1} is not callable",
|
||||
class_name, method_name)
|
||||
.str(),
|
||||
error);
|
||||
|
||||
// Transform the arguments.
|
||||
std::tuple<Args...> original_args = std::forward_as_tuple(args...);
|
||||
auto transformed_args = TransformArgs(original_args);
|
||||
|
||||
// Call the static method.
|
||||
llvm::Expected<PythonObject> expected_return_object =
|
||||
llvm::make_error<llvm::StringError>("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<T>(
|
||||
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<T>(
|
||||
caller_signature,
|
||||
"couldn't re-assign reference and pointer arguments", error);
|
||||
|
||||
// Extract value from Python object (handles unallocated case).
|
||||
return ExtractValueFromPythonObject<T>(py_return, error);
|
||||
}
|
||||
|
||||
protected:
|
||||
template <typename T = StructuredData::ObjectSP>
|
||||
T ExtractValueFromPythonObject(python::PythonObject &p, Status &error) {
|
||||
@@ -403,7 +509,7 @@ protected:
|
||||
llvm::Twine(method_name) + llvm::Twine(")"))
|
||||
.str();
|
||||
if (!m_object_instance_sp)
|
||||
return ErrorWithMessage<T>(caller_signature, "Python object ill-formed",
|
||||
return ErrorWithMessage<T>(caller_signature, "python object ill-formed",
|
||||
error);
|
||||
|
||||
Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
|
||||
@@ -415,7 +521,7 @@ protected:
|
||||
if (!implementor.IsAllocated())
|
||||
return llvm::is_contained(GetAbstractMethods(), method_name)
|
||||
? ErrorWithMessage<T>(caller_signature,
|
||||
"Python implementor not allocated.",
|
||||
"python implementor not allocated",
|
||||
error)
|
||||
: T{};
|
||||
|
||||
@@ -436,20 +542,20 @@ protected:
|
||||
if (llvm::Error e = expected_return_object.takeError()) {
|
||||
error = Status::FromError(std::move(e));
|
||||
return ErrorWithMessage<T>(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<T>(
|
||||
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<lldb::StreamSP>(
|
||||
python::PythonObject &p, Status &error);
|
||||
|
||||
template <>
|
||||
lldb::ThreadSP
|
||||
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>(
|
||||
python::PythonObject &p, Status &error);
|
||||
|
||||
template <>
|
||||
lldb::StackFrameSP
|
||||
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
add_subdirectory(ScriptedFrameProvider)
|
||||
@@ -0,0 +1,12 @@
|
||||
add_lldb_library(lldbPluginScriptedFrameProvider PLUGIN
|
||||
ScriptedFrameProvider.cpp
|
||||
|
||||
LINK_COMPONENTS
|
||||
Support
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
lldbInterpreter
|
||||
lldbTarget
|
||||
lldbUtility
|
||||
)
|
||||
@@ -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 <cstdint>
|
||||
|
||||
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<lldb::SyntheticFrameProviderSP>
|
||||
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<ScriptedFrameProvider>(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<StackFrameSP>
|
||||
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<StackFrameSP> {
|
||||
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<StackFrame>(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<StackFrameSP> {
|
||||
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<bool>(
|
||||
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<BorrowedStackFrame>(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
|
||||
@@ -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<lldb::SyntheticFrameProviderSP>
|
||||
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<lldb::StackFrameSP> 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<SyntheticFrameProviderSP> 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");
|
||||
|
||||
@@ -3720,6 +3720,61 @@ Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
|
||||
return error;
|
||||
}
|
||||
|
||||
llvm::Expected<uint32_t> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<uint32_t, ScriptedFrameProviderDescriptor> &
|
||||
Target::GetScriptedFrameProviderDescriptors() const {
|
||||
std::lock_guard<std::recursive_mutex> guard(
|
||||
m_frame_provider_descriptors_mutex);
|
||||
return m_frame_provider_descriptors;
|
||||
}
|
||||
|
||||
void Target::FinalizeFileActions(ProcessLaunchInfo &info) {
|
||||
Log *log = GetLog(LLDBLog::Process);
|
||||
|
||||
|
||||
@@ -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<std::recursive_mutex> 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<std::recursive_mutex> 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<SyntheticStackFrameList>(
|
||||
*this, input_frames, m_prev_frames_sp, true);
|
||||
} else {
|
||||
// No provider - use normal unwinder frames.
|
||||
m_curr_frames_sp =
|
||||
std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true);
|
||||
}
|
||||
|
||||
return m_curr_frames_sp;
|
||||
}
|
||||
|
||||
llvm::Error Thread::LoadScriptedFrameProvider(
|
||||
const ScriptedFrameProviderDescriptor &descriptor) {
|
||||
std::lock_guard<std::recursive_mutex> 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<StackFrameList>(*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<std::recursive_mutex> guard(m_frame_mutex);
|
||||
m_frame_provider_sp.reset();
|
||||
m_curr_frames_sp.reset();
|
||||
m_prev_frames_sp.reset();
|
||||
}
|
||||
|
||||
std::optional<addr_t> 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;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ const char *ThreadSpec::g_option_names[static_cast<uint32_t>(
|
||||
|
||||
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> ThreadSpec::CreateFromStructuredData(
|
||||
const StructuredData::Dictionary &spec_dict, Status &error) {
|
||||
uint32_t index = UINT32_MAX;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Multi-threaded test program for testing frame providers.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user