Reland "[lldb] Introduce ScriptedFrameProvider for real threads (#161870)" (#170236)

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:
Med Ismail Bennani
2025-12-02 10:59:40 -08:00
committed by GitHub
parent 879dddf2b4
commit c50802cbee
47 changed files with 2289 additions and 76 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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:

View File

@@ -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;

View File

@@ -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);

View File

@@ -46,6 +46,7 @@ protected:
void SetOpaque(const lldb::ThreadCollectionSP &threads);
private:
friend class SBTarget;
friend class SBProcess;
friend class SBThread;
friend class SBSaveCoreOptions;

View File

@@ -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 {};
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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.

View File

@@ -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;

View File

@@ -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?

View File

@@ -34,6 +34,8 @@ class ThreadSpec {
public:
ThreadSpec();
ThreadSpec(Thread &thread);
static std::unique_ptr<ThreadSpec>
CreateFromStructuredData(const StructuredData::Dictionary &data_dict,
Status &error);

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {};
}

View File

@@ -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 &current_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",

View File

@@ -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)

View File

@@ -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)

View File

@@ -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());

View File

@@ -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;

View File

@@ -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>(

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>(

View File

@@ -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>(

View File

@@ -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);

View File

@@ -0,0 +1 @@
add_subdirectory(ScriptedFrameProvider)

View File

@@ -0,0 +1,12 @@
add_lldb_library(lldbPluginScriptedFrameProvider PLUGIN
ScriptedFrameProvider.cpp
LINK_COMPONENTS
Support
LINK_LIBS
lldbCore
lldbInterpreter
lldbTarget
lldbUtility
)

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -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")

View File

@@ -0,0 +1,3 @@
C_SOURCES := main.c
include Makefile.rules

View File

@@ -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")

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;