Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lldb/include/lldb/Target/StackFrameList.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_sp);

protected:
/// Override FetchFramesUpTo to lazily return frames from the provider
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions lldb/include/lldb/Target/Thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -1302,8 +1302,9 @@ class Thread : public std::enable_shared_from_this<Thread>,

void ClearScriptedFrameProvider();

lldb::SyntheticFrameProviderSP GetFrameProvider() const {
return m_frame_provider_sp;
const llvm::SmallVector<lldb::SyntheticFrameProviderSP, 0> &
GetFrameProviders() const {
return m_frame_providers;
}

protected:
Expand Down Expand Up @@ -1409,8 +1410,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.
llvm::SmallVector<lldb::SyntheticFrameProviderSP, 0> m_frame_providers;

private:
bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info
Expand Down
13 changes: 7 additions & 6 deletions lldb/source/Target/StackFrameList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_sp)
: 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_sp)) {}

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(),
Expand Down
60 changes: 35 additions & 25 deletions lldb/source/Target/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -1457,8 +1457,8 @@ 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();
Expand All @@ -1483,24 +1483,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 =
Expand All @@ -1514,29 +1514,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 new_provider_input_frames;
if (m_frame_providers.empty()) {
// First provider gets real unwinder frames.
new_provider_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_provider_frames = prev_provider->GetInputFrames();
new_provider_input_frames = std::make_shared<SyntheticStackFrameList>(
*this, prev_provider_frames, m_prev_frames_sp, true, prev_provider);
}

auto provider_or_err =
SyntheticFrameProvider::CreateInstance(temp_frames, descriptor);
auto provider_or_err = SyntheticFrameProvider::CreateInstance(
new_provider_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();
}
Expand All @@ -1561,7 +1571,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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading