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: 6 additions & 0 deletions lldb/include/lldb/Target/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,12 @@ class Target : public std::enable_shared_from_this<Target>,
const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
GetScriptedFrameProviderDescriptors() const;

protected:
/// Invalidate all potentially cached frame providers for all threads
/// and trigger a stack changed event for all threads.
void InvalidateThreadFrameProviders();

public:
// This part handles the breakpoints.

BreakpointList &GetBreakpointList(bool internal = false);
Expand Down
59 changes: 36 additions & 23 deletions lldb/source/Target/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3726,47 +3726,45 @@ llvm::Expected<uint32_t> Target::AddScriptedFrameProviderDescriptor(
if (!descriptor.IsValid())
return llvm::createStringError("invalid frame provider descriptor");

uint32_t descriptor_id = descriptor.GetID();

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;
{
std::unique_lock<std::recursive_mutex> guard(
m_frame_provider_descriptors_mutex);
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();
InvalidateThreadFrameProviders();

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);
bool removed = false;
{
std::lock_guard<std::recursive_mutex> guard(
m_frame_provider_descriptors_mutex);
removed = m_frame_provider_descriptors.erase(id);
}

if (removed)
if (ProcessSP process_sp = GetProcessSP())
for (ThreadSP thread_sp : process_sp->Threads())
thread_sp->ClearScriptedFrameProvider();

InvalidateThreadFrameProviders();
return removed;
}

void Target::ClearScriptedFrameProviderDescriptors() {
std::lock_guard<std::recursive_mutex> guard(
m_frame_provider_descriptors_mutex);

m_frame_provider_descriptors.clear();
{
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();
InvalidateThreadFrameProviders();
}

const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
Expand All @@ -3776,6 +3774,21 @@ Target::GetScriptedFrameProviderDescriptors() const {
return m_frame_provider_descriptors;
}

void Target::InvalidateThreadFrameProviders() {
ProcessSP process_sp = GetProcessSP();
if (!process_sp)
return;
for (ThreadSP thread_sp : process_sp->Threads()) {
// Clear frame providers on existing threads so they reload with new config.
thread_sp->ClearScriptedFrameProvider();
// Notify threads that the stack traces might have changed.
if (thread_sp->EventTypeHasListeners(Thread::eBroadcastBitStackChanged)) {
auto data_sp = std::make_shared<Thread::ThreadEventData>(thread_sp);
thread_sp->BroadcastEvent(Thread::eBroadcastBitStackChanged, data_sp);
}
}
}

void Target::FinalizeFileActions(ProcessLaunchInfo &info) {
Log *log = GetLog(LLDBLog::Process);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1110,3 +1110,80 @@ def test_provider_lifecycle_with_frame_validity(self):
_ = saved_frames[1].IsValid()
except Exception as e:
self.fail(f"Accessing invalidated frames should not crash: {e}")

def test_event_broadcasting(self):
"""Test that adding/removing frame providers broadcasts eBroadcastBitStackChanged."""
self.build()

listener = lldb.SBListener("stack_changed_listener")
listener.StartListeningForEventClass(
self.dbg,
lldb.SBThread.GetBroadcasterClassName(),
lldb.SBThread.eBroadcastBitStackChanged,
)

target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
)

expected_thread_ids = {
process.GetThreadAtIndex(i).GetIndexID()
for i in range(process.GetNumThreads())
}

def collect_stack_changed_thread_ids(count):
event = lldb.SBEvent()
thread_ids = set()
for _ in range(count):
if not listener.WaitForEvent(5, event):
break
self.assertEqual(
event.GetType(),
lldb.SBThread.eBroadcastBitStackChanged,
"Event should be stack changed",
)
thread_ids.add(lldb.SBThread.GetThreadFromEvent(event).GetIndexID())
return thread_ids

# Import the test frame provider.
script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
self.runCmd("command script import " + script_path)

# 1. Test registration.
error = lldb.SBError()
provider_id = target.RegisterScriptedFrameProvider(
"test_frame_providers.ReplaceFrameProvider",
lldb.SBStructuredData(),
error,
)
self.assertSuccess(error, f"Failed to register provider: {error}")
self.assertEqual(
collect_stack_changed_thread_ids(len(expected_thread_ids)),
expected_thread_ids,
"All threads should broadcast eBroadcastBitStackChanged on registration",
)

# 2. Test removal.
result = target.RemoveScriptedFrameProvider(provider_id)
self.assertSuccess(result, f"Failed to remove provider: {result}")
self.assertEqual(
collect_stack_changed_thread_ids(len(expected_thread_ids)),
expected_thread_ids,
"All threads should broadcast eBroadcastBitStackChanged on removal",
)

# 3. Test clear.
target.RegisterScriptedFrameProvider(
"test_frame_providers.ReplaceFrameProvider",
lldb.SBStructuredData(),
error,
)
# Consume registration
collect_stack_changed_thread_ids(len(expected_thread_ids))

self.runCmd("target frame-provider clear")
self.assertEqual(
collect_stack_changed_thread_ids(len(expected_thread_ids)),
expected_thread_ids,
"All threads should broadcast eBroadcastBitStackChanged on clear",
)
Loading