https://github.com/medismailben created
https://github.com/llvm/llvm-project/pull/172849
This patch allows threads to have multiple SyntheticFrameProviderSP instances
that chain together sequentially. Each provider receives the output of the
previous provider as input, creating a transformation pipeline.
It changes `Thread::m_frame_provider_sp` to a vector, adds provider parameter
to SyntheticStackFrameList to avoid calling back into
`Thread::GetFrameProvider()` during frame fetching, updated
`LoadScriptedFrameProvider()` to chain providers by wrapping each previous
provider's output in a `SyntheticStackFrameList` for the next provider and
finally, loads ALL matching providers in priority order instead of just the
first one.
The chaining works as follows:
```
Real Unwinder Frames
↓
Provider 1 (priority 10) → adds/transforms frames
↓
Provider 2 (priority 20) → transforms Provider 1's output
↓
Provider 3 (priority 30) → transforms Provider 2's output
↓
Final frame list shown to user
```
This patch also adds a test for this (test_chained_frame_providers) to verify
that 3 providers chain correctly: `AddFooFrameProvider`, `AddBarFrameProvider`,
`AddBazFrameProvider`.
>From 80850184ad4e13108b39f458493d711a92437cef Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <[email protected]>
Date: Sun, 14 Dec 2025 07:22:18 +0100
Subject: [PATCH 1/2] [lldb] Add priority support to synthetic frame providers
This patch adds `get_priority()` support to synthetic frame providers
to enable priority-based selection when multiple providers match a thread.
This is the first step toward supporting frame provider chaining for
visualizing coroutines, Swift async tasks, and et al.
Priority ordering follows Unix nice convention where lower numbers
indicate higher priority (0 = highest). Providers without explicit
priority return `std::nullopt`, which maps to UINT32_MAX (lowest priority),
ensuring backward compatibility with existing providers.
The implementation adds `GetPriority()` as a virtual method to
`SyntheticFrameProvider` base class, implements it through the scripting
interface hierarchy (`ScriptedFrameProviderInterface` and
`ScriptedFrameProviderPythonInterface`), and updates
`Thread::GetStackFrameList()`
to sort applicable providers by priority before attempting to load them.
Python frame providers can now specify priority:
```python
@staticmethod
def get_priority():
return 10 # Or return None for default priority
```
Signed-off-by: Med Ismail Bennani <[email protected]>
---
.../templates/scripted_frame_provider.py | 28 ++++++++++++++++++
.../ScriptedFrameProviderInterface.h | 16 ++++++++++
.../lldb/Target/SyntheticFrameProvider.h | 21 ++++++++++++++
.../ScriptedFrameProviderPythonInterface.cpp | 18 ++++++++++++
.../ScriptedFrameProviderPythonInterface.h | 2 ++
.../ScriptedFrameProvider.cpp | 7 +++++
.../ScriptedFrameProvider.h | 2 ++
lldb/source/Target/SyntheticFrameProvider.cpp | 14 +++++++++
lldb/source/Target/Thread.cpp | 29 +++++++++++++++----
9 files changed, 131 insertions(+), 6 deletions(-)
diff --git a/lldb/examples/python/templates/scripted_frame_provider.py
b/lldb/examples/python/templates/scripted_frame_provider.py
index 7a72f1a24c9da..a45ef9427a532 100644
--- a/lldb/examples/python/templates/scripted_frame_provider.py
+++ b/lldb/examples/python/templates/scripted_frame_provider.py
@@ -79,6 +79,34 @@ def get_description(self):
"""
pass
+ @staticmethod
+ def get_priority():
+ """Get the priority of this frame provider.
+
+ This static method is called to determine the evaluation order when
+ multiple frame providers could apply to the same thread. Lower numbers
+ indicate higher priority (like Unix nice values).
+
+ Returns:
+ int or None: Priority value where 0 is highest priority.
+ Return None for default priority (UINT32_MAX - lowest
priority).
+
+ Example:
+
+ .. code-block:: python
+
+ @staticmethod
+ def get_priority():
+ # High priority - runs before most providers
+ return 10
+
+ @staticmethod
+ def get_priority():
+ # Default priority - runs last
+ return None
+ """
+ return None # Default/lowest priority
+
def __init__(self, input_frames, args):
"""Construct a scripted frame provider.
diff --git
a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
index 49b60131399d5..b04af0c817b6e 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h
@@ -39,6 +39,22 @@ class ScriptedFrameProviderInterface : public
ScriptedInterface {
/// empty string if no description is available.
virtual std::string GetDescription(llvm::StringRef class_name) { return {}; }
+ /// Get the priority of this frame provider.
+ ///
+ /// This is called by the descriptor to fetch the priority from the
+ /// scripted implementation. Implementations should call a static method
+ /// on the scripting class to retrieve the priority. Lower numbers indicate
+ /// higher priority (like Unix nice values).
+ ///
+ /// \param class_name The name of the scripting class implementing the
+ /// provider.
+ ///
+ /// \return Priority value where 0 is highest priority, or std::nullopt for
+ /// default priority (UINT32_MAX - lowest priority).
+ virtual std::optional<uint32_t> GetPriority(llvm::StringRef class_name) {
+ return std::nullopt;
+ }
+
virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) {
return {};
}
diff --git a/lldb/include/lldb/Target/SyntheticFrameProvider.h
b/lldb/include/lldb/Target/SyntheticFrameProvider.h
index 2d5330cb03105..bbd52b144412d 100644
--- a/lldb/include/lldb/Target/SyntheticFrameProvider.h
+++ b/lldb/include/lldb/Target/SyntheticFrameProvider.h
@@ -56,6 +56,16 @@ struct ScriptedFrameProviderDescriptor {
/// empty string if no description is available.
std::string GetDescription() const;
+ /// Get the priority of this frame provider.
+ ///
+ /// Priority determines the order in which providers are evaluated when
+ /// multiple providers could apply to the same thread. Lower numbers indicate
+ /// higher priority (like Unix nice values).
+ ///
+ /// \return Priority value where 0 is highest priority, or std::nullopt for
+ /// default priority (UINT32_MAX - lowest priority).
+ std::optional<uint32_t> GetPriority() const;
+
/// Check if this descriptor applies to the given thread.
bool AppliesToThread(Thread &thread) const {
// If no thread specs specified, applies to all threads.
@@ -143,6 +153,17 @@ class SyntheticFrameProvider : public PluginInterface {
virtual std::string GetDescription() const = 0;
+ /// Get the priority of this frame provider.
+ ///
+ /// Priority determines the order in which providers are evaluated when
+ /// multiple providers could apply to the same thread. Lower numbers indicate
+ /// higher priority (like Unix nice values).
+ ///
+ /// \return
+ /// Priority value where 0 is highest priority, or std::nullopt for
+ /// default priority (UINT32_MAX - lowest priority).
+ virtual std::optional<uint32_t> GetPriority() const { return std::nullopt; }
+
/// Get a single stack frame at the specified index.
///
/// This method is called lazily - frames are only created when requested.
diff --git
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
index 3dde5036453f4..318d901ca5eda 100644
---
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
+++
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
@@ -70,6 +70,24 @@ std::string
ScriptedFrameProviderPythonInterface::GetDescription(
return obj->GetStringValue().str();
}
+std::optional<uint32_t> ScriptedFrameProviderPythonInterface::GetPriority(
+ llvm::StringRef class_name) {
+ Status error;
+ StructuredData::ObjectSP obj =
+ CallStaticMethod(class_name, "get_priority", error);
+
+ if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+ error))
+ return std::nullopt;
+
+ // Try to extract as unsigned integer. Return nullopt if Python returned None
+ // or if extraction fails.
+ if (StructuredData::UnsignedInteger *int_obj = obj->GetAsUnsignedInteger())
+ return static_cast<uint32_t>(int_obj->GetValue());
+
+ return std::nullopt;
+}
+
StructuredData::ObjectSP
ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) {
Status error;
diff --git
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
index 97a5cc7c669ea..884b0355a659e 100644
---
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
+++
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h
@@ -43,6 +43,8 @@ class ScriptedFrameProviderPythonInterface
std::string GetDescription(llvm::StringRef class_name) override;
+ std::optional<uint32_t> GetPriority(llvm::StringRef class_name) override;
+
StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) override;
static void Initialize();
diff --git
a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp
b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp
index 739963e6f0c2f..4aad8f2cb628f 100644
---
a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp
+++
b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp
@@ -106,6 +106,13 @@ std::string ScriptedFrameProvider::GetDescription() const {
return m_interface_sp->GetDescription(m_descriptor.GetName());
}
+std::optional<uint32_t> ScriptedFrameProvider::GetPriority() const {
+ if (!m_interface_sp)
+ return std::nullopt;
+
+ return m_interface_sp->GetPriority(m_descriptor.GetName());
+}
+
llvm::Expected<StackFrameSP>
ScriptedFrameProvider::GetFrameAtIndex(uint32_t idx) {
if (!m_interface_sp)
diff --git
a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h
b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h
index 3434bf26ade24..6937f9acbc9a9 100644
---
a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h
+++
b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h
@@ -40,6 +40,8 @@ class ScriptedFrameProvider : public SyntheticFrameProvider {
std::string GetDescription() const override;
+ std::optional<uint32_t> GetPriority() const override;
+
/// Get a single stack frame at the specified index.
llvm::Expected<lldb::StackFrameSP> GetFrameAtIndex(uint32_t idx) override;
diff --git a/lldb/source/Target/SyntheticFrameProvider.cpp
b/lldb/source/Target/SyntheticFrameProvider.cpp
index 97ff42d1ed53e..e799ad23b7512 100644
--- a/lldb/source/Target/SyntheticFrameProvider.cpp
+++ b/lldb/source/Target/SyntheticFrameProvider.cpp
@@ -34,6 +34,13 @@ void ScriptedFrameProviderDescriptor::Dump(Stream *s) const {
if (!description.empty())
s->Printf(" Description: %s\n", description.c_str());
+ // Show priority information.
+ std::optional<uint32_t> priority = GetPriority();
+ if (priority.has_value())
+ s->Printf(" Priority: %u\n", *priority);
+ else
+ s->PutCString(" Priority: Default (no priority specified)\n");
+
// Show thread filter information.
if (thread_specs.empty()) {
s->PutCString(" Thread Filter: (applies to all threads)\n");
@@ -62,6 +69,13 @@ std::string
ScriptedFrameProviderDescriptor::GetDescription() const {
return {};
}
+std::optional<uint32_t> ScriptedFrameProviderDescriptor::GetPriority() const {
+ // If we have an interface, call get_priority() to fetch it.
+ if (interface_sp && scripted_metadata_sp)
+ return interface_sp->GetPriority(scripted_metadata_sp->GetClassName());
+ return std::nullopt;
+}
+
llvm::Expected<SyntheticFrameProviderSP>
SyntheticFrameProvider::CreateInstance(
StackFrameListSP input_frames,
const ScriptedFrameProviderDescriptor &descriptor) {
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index b40e753aca1e9..9c816d6f8d749 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -1455,16 +1455,33 @@ StackFrameListSP Thread::GetStackFrameList() {
Target &target = process_sp->GetTarget();
const auto &descriptors = target.GetScriptedFrameProviderDescriptors();
- // Find first descriptor that applies to this thread.
+ // Collect all descriptors that apply to this thread.
+ std::vector<const ScriptedFrameProviderDescriptor *>
applicable_descriptors;
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).
+ applicable_descriptors.push_back(&descriptor);
+ }
+ }
+
+ // Sort by priority (lower number = higher priority).
+ std::sort(applicable_descriptors.begin(), applicable_descriptors.end(),
+ [](const ScriptedFrameProviderDescriptor *a,
+ const ScriptedFrameProviderDescriptor *b) {
+ // nullopt (no priority) sorts last (UINT32_MAX).
+ uint32_t priority_a = a->GetPriority().value_or(UINT32_MAX);
+ uint32_t priority_b = b->GetPriority().value_or(UINT32_MAX);
+ return priority_a < priority_b;
+ });
+
+ // Load the highest priority provider that successfully instantiates.
+ for (const auto *descriptor : applicable_descriptors) {
+ if (llvm::Error error = LoadScriptedFrameProvider(*descriptor)) {
+ LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error),
+ "Failed to load scripted frame provider: {0}");
+ continue; // Try next provider if this one fails.
}
+ break; // Successfully loaded provider.
}
}
}
>From ea7df9180ca86da923e23e4e459f6c27268da827 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <[email protected]>
Date: Thu, 18 Dec 2025 02:20:52 +0100
Subject: [PATCH 2/2] [lldb] Enable chaining multiple scripted frame providers
per thread
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Allow threads to have multiple SyntheticFrameProviderSP instances that
chain together sequentially. Each provider receives the output of the
previous provider as input, creating a transformation pipeline.
Implementation:
- Changed Thread::m_frame_provider_sp to m_frame_providers vector
- Added provider parameter to SyntheticStackFrameList to avoid calling
back into Thread::GetFrameProvider() during frame fetching
- Updated LoadScriptedFrameProvider() to chain providers by wrapping
each previous provider's output in a SyntheticStackFrameList for the
next provider
- Load ALL matching providers in priority order instead of just the
first one
The chaining works as follows:
Real Unwinder Frames
↓
Provider 1 (priority 10) → adds/transforms frames
↓
Provider 2 (priority 20) → transforms Provider 1's output
↓
Provider 3 (priority 30) → transforms Provider 2's output
↓
Final frame list shown to user
Added test cases:
- test_chained_frame_providers: Verifies 3 providers chain correctly
(AddFooFrameProvider, AddBarFrameProvider, AddBazFrameProvider)
Signed-off-by: Med Ismail Bennani <[email protected]>
---
lldb/include/lldb/Target/StackFrameList.h | 6 +-
lldb/include/lldb/Target/Thread.h | 8 +-
.../ScriptedFrameProviderPythonInterface.cpp | 4 +-
lldb/source/Target/StackFrameList.cpp | 13 +--
lldb/source/Target/Thread.cpp | 61 +++++++-----
.../TestScriptedFrameProvider.py | 92 +++++++++++++++++++
.../test_frame_providers.py | 78 ++++++++++++++++
7 files changed, 224 insertions(+), 38 deletions(-)
diff --git a/lldb/include/lldb/Target/StackFrameList.h
b/lldb/include/lldb/Target/StackFrameList.h
index 539c070ff0f4b..42a0be9b196b9 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -243,7 +243,8 @@ class SyntheticStackFrameList : public StackFrameList {
public:
SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames,
const lldb::StackFrameListSP &prev_frames_sp,
- bool show_inline_frames);
+ bool show_inline_frames,
+ lldb::SyntheticFrameProviderSP provider);
protected:
/// Override FetchFramesUpTo to lazily return frames from the provider
@@ -255,6 +256,9 @@ class SyntheticStackFrameList : public StackFrameList {
/// The input stack frame list that the provider transforms.
/// This could be a real StackFrameList or another SyntheticStackFrameList.
lldb::StackFrameListSP m_input_frames;
+
+ /// The provider that transforms the input frames.
+ lldb::SyntheticFrameProviderSP m_provider;
};
} // namespace lldb_private
diff --git a/lldb/include/lldb/Target/Thread.h
b/lldb/include/lldb/Target/Thread.h
index 46ce192556756..808bb024d4d64 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -1302,8 +1302,8 @@ class Thread : public
std::enable_shared_from_this<Thread>,
void ClearScriptedFrameProvider();
- lldb::SyntheticFrameProviderSP GetFrameProvider() const {
- return m_frame_provider_sp;
+ const std::vector<lldb::SyntheticFrameProviderSP> &GetFrameProviders() const
{
+ return m_frame_providers;
}
protected:
@@ -1409,8 +1409,8 @@ class Thread : public
std::enable_shared_from_this<Thread>,
/// 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;
+ /// The Scripted Frame Providers for this thread.
+ std::vector<lldb::SyntheticFrameProviderSP> m_frame_providers;
private:
bool m_extended_info_fetched; // Have we tried to retrieve the
m_extended_info
diff --git
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
index 318d901ca5eda..2d87c1b10bfec 100644
---
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
+++
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
@@ -70,8 +70,8 @@ std::string
ScriptedFrameProviderPythonInterface::GetDescription(
return obj->GetStringValue().str();
}
-std::optional<uint32_t> ScriptedFrameProviderPythonInterface::GetPriority(
- llvm::StringRef class_name) {
+std::optional<uint32_t>
+ScriptedFrameProviderPythonInterface::GetPriority(llvm::StringRef class_name) {
Status error;
StructuredData::ObjectSP obj =
CallStaticMethod(class_name, "get_priority", error);
diff --git a/lldb/source/Target/StackFrameList.cpp
b/lldb/source/Target/StackFrameList.cpp
index 896a760f61d26..b1af2bb65494a 100644
--- a/lldb/source/Target/StackFrameList.cpp
+++ b/lldb/source/Target/StackFrameList.cpp
@@ -58,23 +58,24 @@ StackFrameList::~StackFrameList() {
SyntheticStackFrameList::SyntheticStackFrameList(
Thread &thread, lldb::StackFrameListSP input_frames,
- const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames)
+ const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames,
+ lldb::SyntheticFrameProviderSP provider)
: StackFrameList(thread, prev_frames_sp, show_inline_frames),
- m_input_frames(std::move(input_frames)) {}
+ m_input_frames(std::move(input_frames)), m_provider(std::move(provider))
{
+}
bool SyntheticStackFrameList::FetchFramesUpTo(
uint32_t end_idx, InterruptionControl allow_interrupt) {
size_t num_synthetic_frames = 0;
- // 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.
+ // Use the provider to generate frames lazily.
+ if (m_provider) {
// 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);
+ auto frame_or_err = m_provider->GetFrameAtIndex(idx);
if (!frame_or_err) {
// Provider returned error - we've reached the end.
LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), frame_or_err.takeError(),
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 9c816d6f8d749..53ce687fdace3 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -262,7 +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_frame_providers.clear();
m_prev_framezero_pc.reset();
}
@@ -1448,15 +1448,16 @@ StackFrameListSP Thread::GetStackFrameList() {
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) {
+ // First, try to load frame providers if we don't have any yet.
+ if (m_frame_providers.empty()) {
ProcessSP process_sp = GetProcess();
if (process_sp) {
Target &target = process_sp->GetTarget();
const auto &descriptors = target.GetScriptedFrameProviderDescriptors();
// Collect all descriptors that apply to this thread.
- std::vector<const ScriptedFrameProviderDescriptor *>
applicable_descriptors;
+ std::vector<const ScriptedFrameProviderDescriptor *>
+ applicable_descriptors;
for (const auto &entry : descriptors) {
const ScriptedFrameProviderDescriptor &descriptor = entry.second;
if (descriptor.IsValid() && descriptor.AppliesToThread(*this)) {
@@ -1474,24 +1475,24 @@ StackFrameListSP Thread::GetStackFrameList() {
return priority_a < priority_b;
});
- // Load the highest priority provider that successfully instantiates.
+ // Load ALL matching providers in priority order.
for (const auto *descriptor : applicable_descriptors) {
if (llvm::Error error = LoadScriptedFrameProvider(*descriptor)) {
LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error),
"Failed to load scripted frame provider: {0}");
continue; // Try next provider if this one fails.
}
- break; // Successfully loaded provider.
}
}
}
- // 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();
+ // Create the frame list based on whether we have providers.
+ if (!m_frame_providers.empty()) {
+ // We have providers - use the last one in the chain.
+ // The last provider has already been chained with all previous providers.
+ StackFrameListSP input_frames = m_frame_providers.back()->GetInputFrames();
m_curr_frames_sp = std::make_shared<SyntheticStackFrameList>(
- *this, input_frames, m_prev_frames_sp, true);
+ *this, input_frames, m_prev_frames_sp, true, m_frame_providers.back());
} else {
// No provider - use normal unwinder frames.
m_curr_frames_sp =
@@ -1505,29 +1506,39 @@ 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);
+ // Create input frames for this provider:
+ // - If no providers exist yet, use real unwinder frames
+ // - If providers exist, wrap the previous provider in a
+ // SyntheticStackFrameList
+ // This creates the chain: each provider's OUTPUT becomes the next
+ // provider's INPUT
+ StackFrameListSP input_frames;
+ if (m_frame_providers.empty()) {
+ // First provider gets real unwinder frames
+ input_frames =
+ std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true);
+ } else {
+ // Subsequent providers get the previous provider's OUTPUT
+ // We create a SyntheticStackFrameList that wraps the previous provider
+ SyntheticFrameProviderSP prev_provider = m_frame_providers.back();
+ StackFrameListSP prev_input = prev_provider->GetInputFrames();
+ input_frames = std::make_shared<SyntheticStackFrameList>(
+ *this, prev_input, m_prev_frames_sp, true, prev_provider);
+ }
auto provider_or_err =
- SyntheticFrameProvider::CreateInstance(temp_frames, descriptor);
+ SyntheticFrameProvider::CreateInstance(input_frames, descriptor);
if (!provider_or_err)
return provider_or_err.takeError();
- ClearScriptedFrameProvider();
- m_frame_provider_sp = *provider_or_err;
+ // Append to the chain
+ m_frame_providers.push_back(*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_frame_providers.clear();
m_curr_frames_sp.reset();
m_prev_frames_sp.reset();
}
@@ -1552,7 +1563,7 @@ void Thread::ClearStackFrames() {
m_prev_frames_sp.swap(m_curr_frames_sp);
m_curr_frames_sp.reset();
- m_frame_provider_sp.reset();
+ m_frame_providers.clear();
m_extended_info.reset();
m_extended_info_fetched = false;
}
diff --git
a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index e2b0f0f9cd50d..08991d42cef47 100644
---
a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++
b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -638,3 +638,95 @@ def test_valid_pc_no_module_frames(self):
frame2 = thread.GetFrameAtIndex(2)
self.assertIsNotNone(frame2)
self.assertIn("thread_func", frame2.GetFunctionName())
+
+ def test_chained_frame_providers(self):
+ """Test that multiple frame providers chain together."""
+ 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()
+ self.assertGreaterEqual(
+ original_frame_count, 2, "Should have at least 2 real frames"
+ )
+
+ # Import the test frame providers.
+ script_path = os.path.join(self.getSourceDir(),
"test_frame_providers.py")
+ self.runCmd("command script import " + script_path)
+
+ # Register 3 providers with different priorities.
+ # Each provider adds 1 frame at the beginning.
+ error = lldb.SBError()
+
+ # Provider 1: Priority 10 - adds "foo" frame
+ provider_id_1 = target.RegisterScriptedFrameProvider(
+ "test_frame_providers.AddFooFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(error.Success(), f"Failed to register foo provider:
{error}")
+
+ # Provider 2: Priority 20 - adds "bar" frame
+ provider_id_2 = target.RegisterScriptedFrameProvider(
+ "test_frame_providers.AddBarFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(error.Success(), f"Failed to register bar provider:
{error}")
+
+ # Provider 3: Priority 30 - adds "baz" frame
+ provider_id_3 = target.RegisterScriptedFrameProvider(
+ "test_frame_providers.AddBazFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(error.Success(), f"Failed to register baz provider:
{error}")
+
+ # Verify we have 3 more frames (one from each provider).
+ new_frame_count = thread.GetNumFrames()
+ self.assertEqual(
+ new_frame_count,
+ original_frame_count + 3,
+ "Should have original frames + 3 chained frames",
+ )
+
+ # Verify the chaining order: baz, bar, foo, then real frames.
+ # Since priority is lower = higher, the order should be:
+ # Provider 1 (priority 10) transforms real frames first -> adds "foo"
+ # Provider 2 (priority 20) transforms Provider 1's output -> adds "bar"
+ # Provider 3 (priority 30) transforms Provider 2's output -> adds "baz"
+ # So final stack is: baz, bar, foo, real frames...
+
+ frame0 = thread.GetFrameAtIndex(0)
+ self.assertIsNotNone(frame0)
+ self.assertEqual(
+ frame0.GetFunctionName(),
+ "baz",
+ "Frame 0 should be 'baz' from last provider in chain",
+ )
+ self.assertEqual(frame0.GetPC(), 0xBAD)
+
+ frame1 = thread.GetFrameAtIndex(1)
+ self.assertIsNotNone(frame1)
+ self.assertEqual(
+ frame1.GetFunctionName(),
+ "bar",
+ "Frame 1 should be 'bar' from second provider in chain",
+ )
+ self.assertEqual(frame1.GetPC(), 0xBAB)
+
+ frame2 = thread.GetFrameAtIndex(2)
+ self.assertIsNotNone(frame2)
+ self.assertEqual(
+ frame2.GetFunctionName(),
+ "foo",
+ "Frame 2 should be 'foo' from first provider in chain",
+ )
+ self.assertEqual(frame2.GetPC(), 0xF00)
+
+ # Frame 3 should be the original real frame 0.
+ frame3 = thread.GetFrameAtIndex(3)
+ self.assertIsNotNone(frame3)
+ self.assertIn("thread_func", frame3.GetFunctionName())
diff --git
a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
index e4367192af50d..e97d11f173045 100644
---
a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
+++
b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
@@ -380,3 +380,81 @@ def get_frame_at_index(self, index):
# Pass through original frames
return index - 2
return None
+
+
+class AddFooFrameProvider(ScriptedFrameProvider):
+ """Add a single 'foo' frame at the beginning."""
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ @staticmethod
+ def get_description():
+ """Return a description of this provider."""
+ return "Add 'foo' frame at beginning"
+
+ @staticmethod
+ def get_priority():
+ """Return priority 10 (runs first in chain)."""
+ return 10
+
+ def get_frame_at_index(self, index):
+ if index == 0:
+ # Return synthetic "foo" frame
+ return CustomScriptedFrame(self.thread, 0, 0xF00, "foo")
+ elif index - 1 < len(self.input_frames):
+ # Pass through input frames (shifted by 1)
+ return index - 1
+ return None
+
+
+class AddBarFrameProvider(ScriptedFrameProvider):
+ """Add a single 'bar' frame at the beginning."""
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ @staticmethod
+ def get_description():
+ """Return a description of this provider."""
+ return "Add 'bar' frame at beginning"
+
+ @staticmethod
+ def get_priority():
+ """Return priority 20 (runs second in chain)."""
+ return 20
+
+ def get_frame_at_index(self, index):
+ if index == 0:
+ # Return synthetic "bar" frame
+ return CustomScriptedFrame(self.thread, 0, 0xBAB, "bar")
+ elif index - 1 < len(self.input_frames):
+ # Pass through input frames (shifted by 1)
+ return index - 1
+ return None
+
+
+class AddBazFrameProvider(ScriptedFrameProvider):
+ """Add a single 'baz' frame at the beginning."""
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ @staticmethod
+ def get_description():
+ """Return a description of this provider."""
+ return "Add 'baz' frame at beginning"
+
+ @staticmethod
+ def get_priority():
+ """Return priority 30 (runs last in chain)."""
+ return 30
+
+ def get_frame_at_index(self, index):
+ if index == 0:
+ # Return synthetic "baz" frame
+ return CustomScriptedFrame(self.thread, 0, 0xBAD, "baz")
+ elif index - 1 < len(self.input_frames):
+ # Pass through input frames (shifted by 1)
+ return index - 1
+ return None
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits