https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/195765
>From 928c969d9363f36894ff5a15d251280c68c76589 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 20:43:53 -0700 Subject: [PATCH 1/8] [lldb] Add Policy infrastructure Add a generic thread-local policy stack and a Policy struct that describes what view of the process a thread should see (private reality vs public illusion) and what operations it is allowed to perform. This is the infrastructure for replacing ad-hoc host thread identity checks (CurrentThreadIsPrivateStateThread, IsOnThread, etc.) with a unified, composable mechanism. No behavioral changes yet -- adoption will follow in subsequent patches. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/Policy.h | 95 +++++++++++++++++++ .../lldb/Utility/ThreadLocalPolicyStack.h | 58 +++++++++++ 2 files changed, 153 insertions(+) create mode 100644 lldb/include/lldb/Target/Policy.h create mode 100644 lldb/include/lldb/Utility/ThreadLocalPolicyStack.h diff --git a/lldb/include/lldb/Target/Policy.h b/lldb/include/lldb/Target/Policy.h new file mode 100644 index 0000000000000..ee4f1bb708e6f --- /dev/null +++ b/lldb/include/lldb/Target/Policy.h @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// 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_TARGET_POLICY_H +#define LLDB_TARGET_POLICY_H + +#include "lldb/Utility/ThreadLocalPolicyStack.h" + +namespace lldb_private { + +/// Describes what view of the process a thread should see and what +/// operations it is allowed to perform. +/// +/// Frame providers are a public illusion layered on top of the private +/// reality (the unwinder stack). The private state thread manages the +/// stop of that private reality, so the correct view for its logic IS the +/// private reality. The public illusion is only applied once the process +/// has settled and clients query the stopped state. +/// +/// This struct is a pure data store -- no business logic. It is pushed +/// onto a per-thread stack (PolicyStack) so that code +/// can consult the current policy instead of comparing host thread +/// identities. +struct Policy { + /// What view of the process this thread sees. + enum class View { + Public, // Provider-augmented frames, public state, public run lock. + Private, // Parent (unwinder) frames, private state, private run lock. + }; + + /// What operations this thread is allowed to perform. + /// Enforced at specific callsites, not by the policy itself. + struct Capabilities { + bool can_evaluate_expressions : 1; + bool stop_others_only : 1; + bool can_try_all_threads : 1; + bool can_run_breakpoint_actions : 1; + bool can_load_frame_providers : 1; + bool can_run_frame_recognizers : 1; + }; + + View view = View::Public; + Capabilities capabilities = { + .can_evaluate_expressions = true, + .stop_others_only = false, + .can_try_all_threads = true, + .can_run_breakpoint_actions = true, + .can_load_frame_providers = true, + .can_run_frame_recognizers = true, + }; + + static Policy PublicState() { return {}; } + + static Policy PrivateState() { + return { + .view = View::Private, + .capabilities = + { + .can_evaluate_expressions = true, + .stop_others_only = false, + .can_try_all_threads = true, + .can_run_breakpoint_actions = true, + .can_load_frame_providers = false, + .can_run_frame_recognizers = false, + }, + }; + } + + static Policy PublicStateRunningExpression() { + return { + .view = View::Public, + .capabilities = + { + .can_evaluate_expressions = true, + .stop_others_only = false, + .can_try_all_threads = true, + .can_run_breakpoint_actions = false, + .can_load_frame_providers = true, + .can_run_frame_recognizers = true, + }, + }; + } +}; + +using PolicyStack = + ThreadLocalPolicyStack<Policy>; + +} // namespace lldb_private + +#endif // LLDB_TARGET_POLICY_H diff --git a/lldb/include/lldb/Utility/ThreadLocalPolicyStack.h b/lldb/include/lldb/Utility/ThreadLocalPolicyStack.h new file mode 100644 index 0000000000000..63ca15609cfad --- /dev/null +++ b/lldb/include/lldb/Utility/ThreadLocalPolicyStack.h @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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_UTILITY_THREADLOCALPOLICYSTACK_H +#define LLDB_UTILITY_THREADLOCALPOLICYSTACK_H + +#include <vector> + +namespace lldb_private { + +/// Generic per-thread policy stack. +/// +/// The stack lives in thread_local storage. Each thread has its own stack, +/// initialized with a default-constructed base entry that is never popped. +/// RAII guards (Guard) push and pop policies. +/// +/// For thread pool workers that don't inherit thread_local storage, the +/// policy must be passed into the lambda and pushed onto the worker +/// thread's stack when the task starts. +template <typename Policy> +class ThreadLocalPolicyStack { +public: + static ThreadLocalPolicyStack &GetForCurrentThread() { + static thread_local ThreadLocalPolicyStack s_stack; + return s_stack; + } + + const Policy &Current() const { return m_stack.back(); } + + void Push(Policy policy) { m_stack.push_back(policy); } + + void Pop() { + if (m_stack.size() > 1) + m_stack.pop_back(); + } + + /// RAII guard that pushes a policy on construction and pops on destruction. + class Guard { + public: + Guard(Policy policy) { GetForCurrentThread().Push(policy); } + ~Guard() { GetForCurrentThread().Pop(); } + + Guard(const Guard &) = delete; + Guard &operator=(const Guard &) = delete; + }; + +private: + std::vector<Policy> m_stack = {Policy{}}; +}; + +} // namespace lldb_private + +#endif // LLDB_UTILITY_THREADLOCALPOLICYSTACK_H >From ad272ee3c7979be922053a9fde5459a1d76b86dc Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 21:04:51 -0700 Subject: [PATCH 2/8] [lldb] Adopt Policy for private state thread identification Replace the ad-hoc PrivateStateThreadGuard (a thread_local bool with RAII wrapper) with the new Policy infrastructure. RunThreadPlan and RunPrivateStateThread now push Policy::PrivateState() onto the per-thread policy stack. GetStackFrameList checks the policy's view field instead of PrivateStateThreadGuard::IsPrivateStateThread(). This is a strict NFC refactor -- the same decisions are made, the same code paths are taken. The PrivateStateThreadGuard struct is removed from Process.h. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/Process.h | 22 ---------------------- lldb/source/Target/Process.cpp | 13 ++++++------- lldb/source/Target/Thread.cpp | 16 +++++++++------- 3 files changed, 15 insertions(+), 36 deletions(-) diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 67a9a883cd36c..eb6de7876ed42 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -3719,28 +3719,6 @@ class UtilityFunctionScope { } }; -/// RAII guard that marks the current thread as a private state thread. -/// -/// When RunThreadPlan detects it is running on the private state thread, it -/// spins up an override thread and reassigns m_current_private_state_thread_sp -/// to it. The original PST continues processing events via DoOnRemoval -/// callbacks, but CurrentThreadIsPrivateStateThread() no longer recognizes it. -/// This guard sets a thread_local flag so that GetStackFrameList can identify -/// the original PST and return parent frames instead of provider-augmented -/// frames. -struct PrivateStateThreadGuard { - PrivateStateThreadGuard() { g_is_private_state_thread = true; } - ~PrivateStateThreadGuard() { g_is_private_state_thread = false; } - static bool IsPrivateStateThread() { return g_is_private_state_thread; } - - // Non-copyable, non-movable. - PrivateStateThreadGuard(const PrivateStateThreadGuard &) = delete; - PrivateStateThreadGuard &operator=(const PrivateStateThreadGuard &) = delete; - -private: - static thread_local bool g_is_private_state_thread; -}; - } // namespace lldb_private #endif // LLDB_TARGET_PROCESS_H diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 114bbd7355f0c..c3633b4aed55c 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -53,6 +53,7 @@ #include "lldb/Target/OperatingSystem.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Process.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/StructuredDataPlugin.h" @@ -4320,14 +4321,12 @@ Status Process::HaltPrivate() { return error; } -thread_local bool PrivateStateThreadGuard::g_is_private_state_thread = false; - thread_result_t Process::RunPrivateStateThread(bool is_override) { // Override PSTs exist solely to service RunThreadPlan expression evaluation. // They must see parent frames, not provider-augmented frames. - std::optional<PrivateStateThreadGuard> pst_guard; + std::optional<PolicyStack::Guard> policy_guard; if (is_override) - pst_guard.emplace(); + policy_guard.emplace(Policy::PrivateState()); bool control_only = true; @@ -5525,9 +5524,9 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx, // If we spawned an override PST, mark the current (original) PST so // GetStackFrameList returns parent frames during event processing. - std::optional<PrivateStateThreadGuard> private_state_thread_guard; + std::optional<PolicyStack::Guard> policy_guard; if (backup_private_state_thread) - private_state_thread_guard.emplace(); + policy_guard.emplace(Policy::PrivateState()); while (true) { // We usually want to resume the process if we get to the top of the @@ -5929,7 +5928,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx, } } // END WAIT LOOP - private_state_thread_guard.reset(); + policy_guard.reset(); // If we had to start up a temporary private state thread to run this // thread plan, shut it down now. diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 70053b8822bdc..3bc6ed1659ef6 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -23,6 +23,7 @@ #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" @@ -1533,7 +1534,7 @@ StackFrameListSP Thread::GetStackFrameList() { // thread that is already mid-construction. // // 2. Current thread is a private state thread that should see the - // private reality (PrivateStateThreadGuard::IsPrivateStateThread). + // private reality (Policy::View::Private). // // For case 1, if a provider is active we return its input (parent) // frames. For case (2), we return/create the unwinder frame list @@ -1559,12 +1560,13 @@ StackFrameListSP Thread::GetStackFrameList() { return m_curr_frames_sp; // For case 2, PST must see the private reality, not the public illusion. - // PrivateStateThreadGuard is a thread_local flag set by RunThreadPlan - // (for the original PST) and RunPrivateStateThread (for override PSTs). - // We cannot use CurrentThreadIsPrivateStateThread() here because - // RunThreadPlan reassigns m_current_private_state_thread_sp to the - // override, so the original PST is no longer recognized. - if (PrivateStateThreadGuard::IsPrivateStateThread()) { + // Policy is pushed by RunThreadPlan (for the original PST) + // and RunPrivateStateThread (for override PSTs). We cannot use + // CurrentThreadIsPrivateStateThread() here because RunThreadPlan reassigns + // m_current_private_state_thread_sp to the override, so the original PST + // is no longer recognized. + auto &policy = PolicyStack::GetForCurrentThread().Current(); + if (policy.view == Policy::View::Private) { if (!m_unwinder_frames_sp) m_unwinder_frames_sp = std::make_shared<StackFrameList>( *this, m_prev_frames_sp, true, /*provider_id=*/0); >From 53a02e0778b1b72725c239af31918132c60b910c Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 22:15:50 -0700 Subject: [PATCH 3/8] [lldb] Push PrivateState policy during breakpoint and watchpoint callbacks Push Policy::PrivateState() at the entry of breakpoint and watchpoint callback dispatch (ShouldStopSynchronous and PerformAction). This ensures that any code running during a callback -- including expression evaluation, frame provider access, and SB API calls -- sees the private reality rather than the public illusion. This covers four dispatch sites: - StopInfoBreakpoint::ShouldStopSynchronous (sync callbacks, WasHit) - StopInfoBreakpoint::PerformAction (async callbacks, conditions) - StopInfoWatchpoint::ShouldStopSynchronous - StopInfoWatchpoint::PerformAction Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/source/Target/StopInfo.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp index 2a7ea2808d87f..ba2bf385d39c4 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -16,6 +16,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Expression/UserExpression.h" #include "lldb/Symbol/Block.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/Process.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" @@ -153,6 +154,11 @@ class StopInfoBreakpoint : public StopInfo { StopReason GetStopReason() const override { return eStopReasonBreakpoint; } bool ShouldStopSynchronous(Event *event_ptr) override { + // Breakpoint callbacks run on the PST during stop processing. Push + // private state context so callback code sees the private reality. + PolicyStack::Guard policy_guard( + Policy::PrivateState()); + ThreadSP thread_sp(m_thread_wp.lock()); if (thread_sp) { if (!m_should_stop_is_valid) { @@ -322,6 +328,11 @@ class StopInfoBreakpoint : public StopInfo { m_should_perform_action = false; bool all_stopping_locs_internal = true; + // Breakpoint callbacks run on the PST during stop processing. Push + // private state context so callback code sees the private reality. + PolicyStack::Guard policy_guard( + Policy::PrivateState()); + ThreadSP thread_sp(m_thread_wp.lock()); if (thread_sp) { @@ -857,6 +868,11 @@ class StopInfoWatchpoint : public StopInfo { }; bool ShouldStopSynchronous(Event *event_ptr) override { + // Watchpoint callbacks run on the PST during stop processing. Push + // private state context so callback code sees the private reality. + PolicyStack::Guard policy_guard( + Policy::PrivateState()); + // If we are running our step-over the watchpoint plan, stop if it's done // and continue if it's not: if (m_should_stop_is_valid) @@ -964,6 +980,11 @@ class StopInfoWatchpoint : public StopInfo { } void PerformAction(Event *event_ptr) override { + // Watchpoint callbacks run on the PST during stop processing. Push + // private state context so callback code sees the private reality. + PolicyStack::Guard policy_guard( + Policy::PrivateState()); + Log *log = GetLog(LLDBLog::Watchpoints); // We're going to calculate if we should stop or not in some way during the // course of this code. Also by default we're going to stop, so set that >From c0c997f8e98d47ab8b2f2d8e0776d7f4ae194d94 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 22:20:36 -0700 Subject: [PATCH 4/8] [lldb] Consult Policy for remaining view checks Add Policy checks alongside the existing host thread identity checks in: - Process::GetState() -- return private vs public state - Target::GetAPIMutex() -- return private vs public mutex - PrivateStateThread::GetRunLock() -- return private vs public run lock - StackFrameList::SelectMostRelevantFrame() -- skip frame recognizers The policy check takes precedence. The existing identity checks (CurrentThreadIsPrivateStateThread, CurrentThreadPosesAsPrivateState, IsOnThread) remain as a fallback until full migration is complete. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/Process.h | 8 ++++++-- lldb/source/Target/Process.cpp | 8 ++++++-- lldb/source/Target/StackFrameList.cpp | 5 +++++ lldb/source/Target/Target.cpp | 9 +++++++-- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index eb6de7876ed42..3e732eddbfab6 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -37,6 +37,7 @@ #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/SaveCoreOptions.h" #include "lldb/Target/CoreFileMemoryRanges.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/InstrumentationRuntime.h" #include "lldb/Target/Memory.h" @@ -3334,10 +3335,13 @@ void PruneThreadPlans(); } ProcessRunLock &GetRunLock() { + auto &policy = + PolicyStack::GetForCurrentThread().Current(); + if (policy.view == Policy::View::Private) + return m_private_run_lock; if (IsOnThread(Host::GetCurrentThread())) return m_private_run_lock; - else - return m_public_run_lock; + return m_public_run_lock; } Process &m_process; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index c3633b4aed55c..2a7c4a5c3f82e 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1278,10 +1278,14 @@ StateType Process::GetState() { if (!m_current_private_state_thread_sp) return eStateUnloaded; + auto &policy = PolicyStack::GetForCurrentThread().Current(); + if (policy.view == Policy::View::Private) + return GetPrivateState(); + if (CurrentThreadPosesAsPrivateStateThread()) return GetPrivateState(); - else - return GetPublicState(); + + return GetPublicState(); } void Process::SetPublicState(StateType new_state, bool restarted) { diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 5423785c2fd51..d8558c78262d8 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -15,6 +15,7 @@ #include "lldb/Symbol/Block.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StackFrame.h" @@ -806,6 +807,10 @@ void StackFrameList::SelectMostRelevantFrame() { // Don't call into the frame recognizers on the private state thread as // they can cause code to run in the target, and that can cause deadlocks // when fetching stop events for the expression. + auto &policy = PolicyStack::GetForCurrentThread().Current(); + if (policy.view == Policy::View::Private) + return; + if (m_thread.GetProcess()->CurrentThreadPosesAsPrivateStateThread()) return; diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index e2bae8fae1a26..e5a2c976ccfbb 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -49,6 +49,7 @@ #include "lldb/Symbol/Symbol.h" #include "lldb/Target/ABI.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/Language.h" #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" @@ -5886,10 +5887,14 @@ Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) { } std::recursive_mutex &Target::GetAPIMutex() { + auto &policy = PolicyStack::GetForCurrentThread().Current(); + if (policy.view == Policy::View::Private) + return m_private_mutex; + if (GetProcessSP() && GetProcessSP()->CurrentThreadIsPrivateStateThread()) return m_private_mutex; - else - return m_mutex; + + return m_mutex; } /// Get metrics associated with this target in JSON format. >From a9604fdc666c202715bb2aaab2c59573592a4985 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 22:54:43 -0700 Subject: [PATCH 5/8] [lldb] Consult Policy for breakpoint action capability Check !can_run_breakpoint_actions from the thread-local policy in StopInfoBreakpoint::PerformAction alongside the existing IsRunningExpression() guard. This prevents running async breakpoint commands and conditions when the policy says they shouldn't run. The IsRunningExpression() fallback is kept for now since the PublicStateRunningExpression() policy isn't pushed at all expression eval sites yet. The fork/vfork IsRunningExpression() checks are unrelated (they detect forks during expression eval) and are left unchanged. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/source/Target/StopInfo.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp index ba2bf385d39c4..13a205e2ba301 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -404,7 +404,10 @@ class StopInfoBreakpoint : public StopInfo { ExecutionContext exe_ctx(thread_sp->GetStackFrameAtIndex(0)); Process *process = exe_ctx.GetProcessPtr(); - if (process->GetModIDRef().IsRunningExpression()) { + auto &policy = + PolicyStack::GetForCurrentThread().Current(); + if (!policy.capabilities.can_run_breakpoint_actions || + process->GetModIDRef().IsRunningExpression()) { // If we are in the middle of evaluating an expression, don't run // asynchronous breakpoint commands or expressions. That could // lead to infinite recursion if the command or condition re-calls >From 8496ad2100eeda9923047bb9a24853b205ac4d36 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 23:14:12 -0700 Subject: [PATCH 6/8] [lldb] Use Policy for ProcessRunLock re-entrancy When SB API code enters a frame provider's Python callback and the callback calls back into the SB API, the inner call tries to re-acquire the same ProcessRunLock read lock. On a write-preferring rwlock, a pending writer (e.g. the override PST calling SetStopped) blocks the re-entrant reader, causing a deadlock. Fix this by having ProcessRunLocker::TryLock push a policy with holds_run_lock=true after acquiring the real lock. Re-entrant ReadTryLock calls check this flag and skip the rwlock entirely. ProcessRunLocker::Unlock pops the policy before releasing the lock. This replaces the previous thread_local unordered_map workaround with the Policy framework. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Host/ProcessRunLock.h | 25 +--- lldb/include/lldb/Target/Policy.h | 4 + lldb/source/Host/common/ProcessRunLock.cpp | 38 ++++++ .../runlock_reentrant_deadlock/Makefile | 3 + .../TestRunLockReentrantDeadlock.py | 127 ++++++++++++++++++ .../bkpt_resolver.py | 47 +++++++ .../frame_provider.py | 38 ++++++ .../runlock_reentrant_deadlock/main.c | 18 +++ 8 files changed, 279 insertions(+), 21 deletions(-) create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/Makefile create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/TestRunLockReentrantDeadlock.py create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/bkpt_resolver.py create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/frame_provider.py create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/main.c diff --git a/lldb/include/lldb/Host/ProcessRunLock.h b/lldb/include/lldb/Host/ProcessRunLock.h index 27d10942ddb4c..049eb36deea27 100644 --- a/lldb/include/lldb/Host/ProcessRunLock.h +++ b/lldb/include/lldb/Host/ProcessRunLock.h @@ -58,29 +58,12 @@ class ProcessRunLock { bool IsLocked() const { return m_lock; } // Try to lock the read lock, but only do so if there are no writers. - bool TryLock(ProcessRunLock *lock) { - if (m_lock) { - if (m_lock == lock) - return true; // We already have this lock locked - else - Unlock(); - } - if (lock) { - if (lock->ReadTryLock()) { - m_lock = lock; - return true; - } - } - return false; - } + // Pushes a policy with holds_run_lock=true so that re-entrant + // ReadTryLock calls from the same thread skip the real lock. + bool TryLock(ProcessRunLock *lock); protected: - void Unlock() { - if (m_lock) { - m_lock->ReadUnlock(); - m_lock = nullptr; - } - } + void Unlock(); ProcessRunLock *m_lock = nullptr; diff --git a/lldb/include/lldb/Target/Policy.h b/lldb/include/lldb/Target/Policy.h index ee4f1bb708e6f..90e8e02bf68f0 100644 --- a/lldb/include/lldb/Target/Policy.h +++ b/lldb/include/lldb/Target/Policy.h @@ -42,6 +42,7 @@ struct Policy { bool can_run_breakpoint_actions : 1; bool can_load_frame_providers : 1; bool can_run_frame_recognizers : 1; + bool holds_run_lock : 1; }; View view = View::Public; @@ -52,6 +53,7 @@ struct Policy { .can_run_breakpoint_actions = true, .can_load_frame_providers = true, .can_run_frame_recognizers = true, + .holds_run_lock = false, }; static Policy PublicState() { return {}; } @@ -67,6 +69,7 @@ struct Policy { .can_run_breakpoint_actions = true, .can_load_frame_providers = false, .can_run_frame_recognizers = false, + .holds_run_lock = false, }, }; } @@ -82,6 +85,7 @@ struct Policy { .can_run_breakpoint_actions = false, .can_load_frame_providers = true, .can_run_frame_recognizers = true, + .holds_run_lock = false, }, }; } diff --git a/lldb/source/Host/common/ProcessRunLock.cpp b/lldb/source/Host/common/ProcessRunLock.cpp index 8e7ef45e1e350..a7078f335c957 100644 --- a/lldb/source/Host/common/ProcessRunLock.cpp +++ b/lldb/source/Host/common/ProcessRunLock.cpp @@ -8,6 +8,7 @@ #ifndef _WIN32 #include "lldb/Host/ProcessRunLock.h" +#include "lldb/Target/Policy.h" namespace lldb_private { @@ -22,6 +23,10 @@ ProcessRunLock::~ProcessRunLock() { } bool ProcessRunLock::ReadTryLock() { + auto &policy = PolicyStack::GetForCurrentThread().Current(); + if (policy.capabilities.holds_run_lock) + return !m_running; + ::pthread_rwlock_rdlock(&m_rwlock); if (!m_running) { // coverity[missing_unlock] @@ -32,9 +37,42 @@ bool ProcessRunLock::ReadTryLock() { } bool ProcessRunLock::ReadUnlock() { + auto &policy = PolicyStack::GetForCurrentThread().Current(); + if (policy.capabilities.holds_run_lock) + return true; + return ::pthread_rwlock_unlock(&m_rwlock) == 0; } +bool ProcessRunLock::ProcessRunLocker::TryLock(ProcessRunLock *lock) { + if (m_lock) { + if (m_lock == lock) + return true; + Unlock(); + } + if (lock) { + if (lock->ReadTryLock()) { + m_lock = lock; + // Push a policy so re-entrant ReadTryLock calls from the same + // thread (e.g. provider Python calling back into SB API) skip + // the real lock and avoid deadlocking with a pending writer. + auto policy = PolicyStack::GetForCurrentThread().Current(); + policy.capabilities.holds_run_lock = true; + PolicyStack::GetForCurrentThread().Push(policy); + return true; + } + } + return false; +} + +void ProcessRunLock::ProcessRunLocker::Unlock() { + if (m_lock) { + PolicyStack::GetForCurrentThread().Pop(); + m_lock->ReadUnlock(); + m_lock = nullptr; + } +} + bool ProcessRunLock::SetRunning() { ::pthread_rwlock_wrlock(&m_rwlock); bool was_stopped = !m_running; diff --git a/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/Makefile new file mode 100644 index 0000000000000..0b710c6e298ae --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -std=c99 +include Makefile.rules diff --git a/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/TestRunLockReentrantDeadlock.py b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/TestRunLockReentrantDeadlock.py new file mode 100644 index 0000000000000..17d7dd1dbf17d --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/TestRunLockReentrantDeadlock.py @@ -0,0 +1,127 @@ +""" +Test that ProcessRunLock re-entrant read locks don't deadlock when a frame +provider's get_frame_at_index calls SB API methods. + +This reproduces the deadlock seen in lldb-rpc-server where: + + - An RPC client thread holds a ProcessRunLock read lock (from the outer + SB API call) and enters a provider's get_frame_at_index, which calls + SBFrame.IsValid -> GetStoppedExecutionContext -> ReadTryLock (re-entrant). + + - The override PST is exiting RunPrivateStateThread and calls SetStopped + -> pthread_rwlock_wrlock (blocked by client thread's read lock). + + - The client thread's re-entrant ReadTryLock blocks because the pending + writer prevents new readers on a write-preferring rwlock. + + - The original PST is blocked joining the override thread. +""" + +import os +import threading +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestRunLockReentrantDeadlock(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528") + def test_runlock_reentrant_no_deadlock(self): + """ + Test that a frame provider calling SBFrame.IsValid from + get_frame_at_index does not deadlock with the override PST's + SetStopped when another thread triggers EvaluateExpression. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_name_breakpoint( + self, "main" + ) + + resolver_path = os.path.join(self.getSourceDir(), "bkpt_resolver.py") + provider_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + resolver_path) + self.runCmd("command script import " + provider_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.SBAPIAccessInGetFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), + f"Should register frame provider: {error}", + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + extra_args = lldb.SBStructuredData() + stream = lldb.SBStream() + stream.Print('{"symbol": "target_func"}') + extra_args.SetFromJSON(stream) + + bkpt = target.BreakpointCreateFromScript( + "bkpt_resolver.ExprEvalResolver", + extra_args, + lldb.SBFileSpecList(), + lldb.SBFileSpecList(), + ) + self.assertTrue(bkpt.IsValid(), "Scripted breakpoint should be valid") + self.assertGreater( + bkpt.GetNumLocations(), 0, "Breakpoint should have locations" + ) + + # Spawn a thread that repeatedly accesses frames through the provider. + # This simulates an RPC client thread that holds the ProcessRunLock + # read lock and enters get_frame_at_index -> SBFrame.IsValid. + stop_frame_access = threading.Event() + frame_access_error = [None] + + def access_frames(): + try: + while not stop_frame_access.is_set(): + t = process.GetSelectedThread() + if t.IsValid(): + for i in range(t.GetNumFrames()): + f = t.GetFrameAtIndex(i) + f.IsValid() + except Exception as e: + frame_access_error[0] = str(e) + + frame_thread = threading.Thread(target=access_frames, daemon=True) + frame_thread.start() + + # Continue into the breakpoint. was_hit calls EvaluateExpression on + # the PST, which triggers RunThreadPlan -> override PST. + # The frame access thread is concurrently calling GetFrameAtIndex -> + # provider get_frame_at_index -> SBFrame.IsValid. + # Without the fix, this deadlocks. + process.Continue() + self.assertState(process.GetState(), lldb.eStateStopped) + + stop_frame_access.set() + frame_thread.join(timeout=5) + self.assertFalse(frame_thread.is_alive(), "Frame access thread should exit") + self.assertIsNone( + frame_access_error[0], + f"Frame access thread hit an error: {frame_access_error[0]}", + ) + + thread = process.GetSelectedThread() + self.assertTrue(thread.IsValid(), "Thread should be valid") + self.assertEqual( + thread.GetStopReason(), + lldb.eStopReasonBreakpoint, + "Should stop at breakpoint", + ) + + g_value = target.FindFirstGlobalVariable("g_value") + self.assertTrue(g_value.IsValid(), "Should find g_value") + self.assertGreater( + g_value.GetValueAsUnsigned(), + 0, + "g_value should have been incremented by the was_hit callback", + ) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/bkpt_resolver.py b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/bkpt_resolver.py new file mode 100644 index 0000000000000..7fb7f8675f2a6 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/bkpt_resolver.py @@ -0,0 +1,47 @@ +""" +Scripted breakpoint resolver whose was_hit callback calls EvaluateExpression. + +This reproduces the deadlock seen in the sample where: +1. BreakpointResolverScripted::WasHit runs on the private state thread +2. was_hit calls SBFrame.EvaluateExpression -> RunThreadPlan -> Halt -> + WaitForProcessToStop (holds a mutex, waits for state event) +3. The override private state thread handles the stop and loads a scripted + frame provider +4. The provider's __init__ calls SBThread.__bool__ -> GetStoppedExecutionContext + -> tries to acquire the same mutex -> DEADLOCK +""" + +import lldb + + +class ExprEvalResolver: + """Scripted breakpoint resolver that evaluates an expression in was_hit.""" + + def __init__(self, bkpt, extra_args, dict): + self.bkpt = bkpt + sym_name = extra_args.GetValueForKey("symbol").GetStringValue(100) + self.sym_name = sym_name + self.facade_loc = None + + def __callback__(self, sym_ctx): + sym = sym_ctx.module.FindSymbol(self.sym_name, lldb.eSymbolTypeCode) + if sym.IsValid(): + self.bkpt.AddLocation(sym.GetStartAddress()) + self.facade_loc = self.bkpt.AddFacadeLocation() + + def get_short_help(self): + return f"ExprEvalResolver for {self.sym_name}" + + def was_hit(self, frame, bp_loc): + # This runs on the private state thread. Calling EvaluateExpression + # here triggers RunThreadPlan -> Halt -> WaitForProcessToStop, which + # holds a mutex and waits for a state change event. + options = lldb.SBExpressionOptions() + options.SetStopOthers(True) + options.SetTryAllThreads(False) + + result = frame.EvaluateExpression("increment()", options) + if not result.error.success: + return lldb.LLDB_INVALID_BREAK_ID + + return self.facade_loc diff --git a/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/frame_provider.py new file mode 100644 index 0000000000000..413484c264513 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/frame_provider.py @@ -0,0 +1,38 @@ +""" +Frame provider whose get_frame_at_index calls SBFrame::IsValid on input frames. + +When a client thread accesses frames through this provider while the PST is +mid-expression-eval (RunThreadPlan), the following deadlock occurs: + + - Client thread: holds ProcessRunLock read lock (from outer SB API call), + enters get_frame_at_index, calls SBFrame.IsValid -> + GetStoppedExecutionContext -> ReadTryLock (re-entrant, blocked by + pending writer) + + - Override PST: finishing RunPrivateStateThread, calls SetStopped -> + pthread_rwlock_wrlock (blocked by client thread's read lock) + +The re-entrant read lock blocks because macOS uses a write-preferring rwlock. +""" + +import lldb +from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider + + +class SBAPIAccessInGetFrameProvider(ScriptedFrameProvider): + """Provider that calls SBFrame.IsValid from get_frame_at_index.""" + + @staticmethod + def get_description(): + return "Provider that accesses SB API in get_frame_at_index" + + def get_frame_at_index(self, idx): + if idx < len(self.input_frames): + frame = self.input_frames.GetFrameAtIndex(idx) + # This call triggers GetStoppedExecutionContext -> + # ProcessRunLock::ReadTryLock. If the current thread already + # holds the read lock (from the outer SB API entry point), + # and a writer is pending, this re-entrant read lock blocks. + frame.IsValid() + return idx + return None diff --git a/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/main.c b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/main.c new file mode 100644 index 0000000000000..9ebeaf3380ed9 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/runlock_reentrant_deadlock/main.c @@ -0,0 +1,18 @@ +#include <stdio.h> + +int g_value = 0; + +int increment() { return ++g_value; } + +int target_func() { + printf("target_func: %d\n", g_value); + return g_value; +} + +int main() { + for (int i = 0; i < 10; i++) { + target_func(); + } + increment(); + return 0; +} >From 929b070e673dbcf3e0ba210b7eb148b44110cee3 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 24 Apr 2026 23:26:35 -0700 Subject: [PATCH 7/8] [lldb] Push PublicStateRunningExpression policy and remove identity check fallbacks Push Policy::PublicStateRunningExpression() at all three expression evaluation entry points (LLVMUserExpression::DoExecute, FunctionCaller::ExecuteFunction, IRInterpreter). This policy sets can_run_breakpoint_actions=false, preventing recursive breakpoint callback execution during expression eval. With all policy push sites now in place, remove the old host thread identity check fallbacks: - CurrentThreadPosesAsPrivateStateThread() in Process::GetState() - CurrentThreadIsPrivateStateThread() in Target::GetAPIMutex() - IsOnThread() in PrivateStateThread::GetRunLock() - CurrentThreadPosesAsPrivateStateThread() in SelectMostRelevantFrame() - IsRunningExpression() in StopInfoBreakpoint::PerformAction() All view and capability decisions now go through the Policy stack exclusively. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/Process.h | 2 -- lldb/source/Expression/FunctionCaller.cpp | 4 ++++ lldb/source/Expression/IRInterpreter.cpp | 4 ++++ lldb/source/Expression/LLVMUserExpression.cpp | 4 ++++ lldb/source/Target/Process.cpp | 3 --- lldb/source/Target/StackFrameList.cpp | 3 --- lldb/source/Target/StopInfo.cpp | 3 +-- lldb/source/Target/Target.cpp | 3 --- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 3e732eddbfab6..43c2f8a817643 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -3339,8 +3339,6 @@ void PruneThreadPlans(); PolicyStack::GetForCurrentThread().Current(); if (policy.view == Policy::View::Private) return m_private_run_lock; - if (IsOnThread(Host::GetCurrentThread())) - return m_private_run_lock; return m_public_run_lock; } diff --git a/lldb/source/Expression/FunctionCaller.cpp b/lldb/source/Expression/FunctionCaller.cpp index 6ea3faee98688..5b6958039241d 100644 --- a/lldb/source/Expression/FunctionCaller.cpp +++ b/lldb/source/Expression/FunctionCaller.cpp @@ -15,6 +15,7 @@ #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Type.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/Target.h" @@ -384,6 +385,9 @@ lldb::ExpressionResults FunctionCaller::ExecuteFunction( if (exe_ctx.GetProcessPtr()) exe_ctx.GetProcessPtr()->SetRunningUserExpression(true); + PolicyStack::Guard expr_policy_guard( + Policy::PublicStateRunningExpression()); + return_value = exe_ctx.GetProcessRef().RunThreadPlan( exe_ctx, call_plan_sp, real_options, diagnostic_manager); diff --git a/lldb/source/Expression/IRInterpreter.cpp b/lldb/source/Expression/IRInterpreter.cpp index 69e7d0b327803..7c0ab05c2bfa6 100644 --- a/lldb/source/Expression/IRInterpreter.cpp +++ b/lldb/source/Expression/IRInterpreter.cpp @@ -25,6 +25,7 @@ #include "lldb/Target/ABI.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadPlan.h" @@ -1581,6 +1582,9 @@ bool IRInterpreter::Interpret(llvm::Module &module, llvm::Function &function, process->SetRunningUserExpression(true); + lldb_private::PolicyStack::Guard expr_policy_guard( + lldb_private::Policy::PublicStateRunningExpression()); + // Execute the actual function call thread plan lldb::ExpressionResults res = process->RunThreadPlan(exe_ctx, call_plan_sp, options, diagnostics); diff --git a/lldb/source/Expression/LLVMUserExpression.cpp b/lldb/source/Expression/LLVMUserExpression.cpp index 2d59194027b57..5f5a5f9d66f52 100644 --- a/lldb/source/Expression/LLVMUserExpression.cpp +++ b/lldb/source/Expression/LLVMUserExpression.cpp @@ -22,6 +22,7 @@ #include "lldb/Symbol/VariableList.h" #include "lldb/Target/ABI.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Policy.h" #include "lldb/Target/Process.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/Target.h" @@ -174,6 +175,9 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager, if (exe_ctx.GetProcessPtr()) exe_ctx.GetProcessPtr()->SetRunningUserExpression(true); + PolicyStack::Guard expr_policy_guard( + Policy::PublicStateRunningExpression()); + lldb::ExpressionResults execution_result = exe_ctx.GetProcessRef().RunThreadPlan(exe_ctx, call_plan_sp, options, diagnostic_manager); diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 2a7c4a5c3f82e..7cf4b4f5f8c62 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1282,9 +1282,6 @@ StateType Process::GetState() { if (policy.view == Policy::View::Private) return GetPrivateState(); - if (CurrentThreadPosesAsPrivateStateThread()) - return GetPrivateState(); - return GetPublicState(); } diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index d8558c78262d8..ef39ef7be94f2 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -811,9 +811,6 @@ void StackFrameList::SelectMostRelevantFrame() { if (policy.view == Policy::View::Private) return; - if (m_thread.GetProcess()->CurrentThreadPosesAsPrivateStateThread()) - return; - Log *log = GetLog(LLDBLog::Thread); // Only the top frame should be recognized. diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp index 13a205e2ba301..78f50e58495d5 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -406,8 +406,7 @@ class StopInfoBreakpoint : public StopInfo { Process *process = exe_ctx.GetProcessPtr(); auto &policy = PolicyStack::GetForCurrentThread().Current(); - if (!policy.capabilities.can_run_breakpoint_actions || - process->GetModIDRef().IsRunningExpression()) { + if (!policy.capabilities.can_run_breakpoint_actions) { // If we are in the middle of evaluating an expression, don't run // asynchronous breakpoint commands or expressions. That could // lead to infinite recursion if the command or condition re-calls diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index e5a2c976ccfbb..4ac4b6ddb838b 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -5891,9 +5891,6 @@ std::recursive_mutex &Target::GetAPIMutex() { if (policy.view == Policy::View::Private) return m_private_mutex; - if (GetProcessSP() && GetProcessSP()->CurrentThreadIsPrivateStateThread()) - return m_private_mutex; - return m_mutex; } >From a847390e01398c39880457856c9e690420808a3c Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Mon, 4 May 2026 17:52:04 -0700 Subject: [PATCH 8/8] [lldb] Push PrivateState policy for all PSTs and remove remaining fallbacks Push Policy::PrivateState() unconditionally for all PSTs in RunPrivateStateThread -- not just overrides. This gives every PST the private view (private state, private run lock, private mutex) while keeping frame providers and recognizers enabled for normal stop processing. Override PSTs use Policy::PrivateStateRunningExpression() which additionally disables providers and recognizers since they exist solely to service RunThreadPlan expression evaluation. With all PSTs now covered by the policy, the remaining identity check fallbacks are no longer needed. SelectMostRelevantFrame now checks !can_run_frame_recognizers instead of View::Private, so frame recognizers still run during normal PST stop processing but are skipped during expression evaluation. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/Policy.h | 54 +++++++++++---------------- lldb/source/Target/Process.cpp | 13 ++++--- lldb/source/Target/StackFrameList.cpp | 2 +- 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/lldb/include/lldb/Target/Policy.h b/lldb/include/lldb/Target/Policy.h index 90e8e02bf68f0..a4db5dc5461d9 100644 --- a/lldb/include/lldb/Target/Policy.h +++ b/lldb/include/lldb/Target/Policy.h @@ -47,47 +47,35 @@ struct Policy { View view = View::Public; Capabilities capabilities = { - .can_evaluate_expressions = true, - .stop_others_only = false, - .can_try_all_threads = true, - .can_run_breakpoint_actions = true, - .can_load_frame_providers = true, - .can_run_frame_recognizers = true, - .holds_run_lock = false, + true, // can_evaluate_expressions + false, // stop_others_only + true, // can_try_all_threads + true, // can_run_breakpoint_actions + true, // can_load_frame_providers + true, // can_run_frame_recognizers + false, // holds_run_lock }; static Policy PublicState() { return {}; } + static Policy PrivateStateRunningExpression() { + Policy p; + p.view = View::Private; + p.capabilities.can_load_frame_providers = false; + p.capabilities.can_run_frame_recognizers = false; + return p; + } + static Policy PrivateState() { - return { - .view = View::Private, - .capabilities = - { - .can_evaluate_expressions = true, - .stop_others_only = false, - .can_try_all_threads = true, - .can_run_breakpoint_actions = true, - .can_load_frame_providers = false, - .can_run_frame_recognizers = false, - .holds_run_lock = false, - }, - }; + Policy p; + p.view = View::Private; + return p; } static Policy PublicStateRunningExpression() { - return { - .view = View::Public, - .capabilities = - { - .can_evaluate_expressions = true, - .stop_others_only = false, - .can_try_all_threads = true, - .can_run_breakpoint_actions = false, - .can_load_frame_providers = true, - .can_run_frame_recognizers = true, - .holds_run_lock = false, - }, - }; + Policy p; + p.capabilities.can_run_breakpoint_actions = false; + return p; } }; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 7cf4b4f5f8c62..22bfa80f83632 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -4323,11 +4323,12 @@ Status Process::HaltPrivate() { } thread_result_t Process::RunPrivateStateThread(bool is_override) { - // Override PSTs exist solely to service RunThreadPlan expression evaluation. - // They must see parent frames, not provider-augmented frames. - std::optional<PolicyStack::Guard> policy_guard; - if (is_override) - policy_guard.emplace(Policy::PrivateState()); + // All PSTs see the private reality (private state, private run lock). + // Override PSTs additionally skip frame providers and recognizers since + // they exist solely to service RunThreadPlan expression evaluation. + PolicyStack::Guard policy_guard(is_override + ? Policy::PrivateStateRunningExpression() + : Policy::PrivateState()); bool control_only = true; @@ -5527,7 +5528,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx, // GetStackFrameList returns parent frames during event processing. std::optional<PolicyStack::Guard> policy_guard; if (backup_private_state_thread) - policy_guard.emplace(Policy::PrivateState()); + policy_guard.emplace(Policy::PrivateStateRunningExpression()); while (true) { // We usually want to resume the process if we get to the top of the diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index ef39ef7be94f2..453961caaa774 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -808,7 +808,7 @@ void StackFrameList::SelectMostRelevantFrame() { // they can cause code to run in the target, and that can cause deadlocks // when fetching stop events for the expression. auto &policy = PolicyStack::GetForCurrentThread().Current(); - if (policy.view == Policy::View::Private) + if (!policy.capabilities.can_run_frame_recognizers) return; Log *log = GetLog(LLDBLog::Thread); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
