[Lldb-commits] [lldb] [lldb] Push PrivateState policy during breakpoint and watchpoint callbacks (PR #195766)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Mon May 4 18:22:33 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/195766
>From eef23e2c2095572fd20ff4ddb88ddea99bba0878 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 4 May 2026 18:19:57 -0700
Subject: [PATCH 1/3] [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.
rdar://176223894
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/include/lldb/Target/Policy.h | 78 +++++++++++++++++++
.../lldb/Utility/ThreadLocalPolicyStack.h | 57 ++++++++++++++
2 files changed, 135 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..93bd5c39380f3
--- /dev/null
+++ b/lldb/include/lldb/Target/Policy.h
@@ -0,0 +1,78 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 = {
+ 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
+ };
+
+ static Policy PublicState() { return {}; }
+
+ static Policy PrivateState() {
+ Policy p;
+ p.view = View::Private;
+ p.capabilities.can_load_frame_providers = false;
+ p.capabilities.can_run_frame_recognizers = false;
+ return p;
+ }
+
+ static Policy PublicStateRunningExpression() {
+ Policy p;
+ p.capabilities.can_run_breakpoint_actions = false;
+ return p;
+ }
+};
+
+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..d24b23a34a583
--- /dev/null
+++ b/lldb/include/lldb/Utility/ThreadLocalPolicyStack.h
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 0bd756b2c1084d43ce33359beab8517db67ab2bd Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 4 May 2026 18:20:32 -0700
Subject: [PATCH 2/3] [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.
rdar://176223894
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/include/lldb/Target/Policy.h | 12 ++++++------
lldb/include/lldb/Target/Process.h | 22 ----------------------
lldb/source/Target/Process.cpp | 13 ++++++-------
lldb/source/Target/Thread.cpp | 16 +++++++++-------
4 files changed, 21 insertions(+), 42 deletions(-)
diff --git a/lldb/include/lldb/Target/Policy.h b/lldb/include/lldb/Target/Policy.h
index 93bd5c39380f3..6a9664fa5b085 100644
--- a/lldb/include/lldb/Target/Policy.h
+++ b/lldb/include/lldb/Target/Policy.h
@@ -46,12 +46,12 @@ struct Policy {
View view = View::Public;
Capabilities capabilities = {
- 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
+ /*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 {}; }
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..8e12a0db321ba 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -52,6 +52,7 @@
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/OperatingSystem.h"
#include "lldb/Target/Platform.h"
+#include "lldb/Target/Policy.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/StopInfo.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..b37c5c9313ef2 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -24,6 +24,7 @@
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/LanguageRuntime.h"
+#include "lldb/Target/Policy.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/ScriptedThreadPlan.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 34cf4018158bd10f205a3303a900af186033ad73 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 4 May 2026 18:22:07 -0700
Subject: [PATCH 3/3] [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
rdar://176223894
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/source/Target/StopInfo.cpp | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp
index 2a7ea2808d87f..4c4be90be6000 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,10 @@ 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 +327,10 @@ 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 +866,10 @@ 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 +977,10 @@ 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
More information about the lldb-commits
mailing list