[Lldb-commits] [lldb] [lldb] Add target.process.always-run-thread-names setting (PR #192870)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Sun Apr 19 17:55:02 PDT 2026
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/192870
>From 9939e10519f8cdb67c9ddf63649eb52ca9642ec8 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sun, 19 Apr 2026 13:42:29 -0700
Subject: [PATCH 1/2] [lldb] Add target.process.always-run-thread-names setting
Add a process setting that keeps named threads running during
single-stepping operations. Some programs install in-process Mach
exception handler threads that must continue running to forward
exceptions. When lldb single-steps and suspends all other threads, these
handler threads stall, causing a deadlock if the stepped instruction
raises an exception.
The new `target.process.always-run-thread-names` setting accepts a list
of thread name strings. In ThreadList::WillResume, threads whose names
match an entry in this list are resumed with eStateRunning instead of
eStateSuspended, ensuring they appear in vCont packets even during
single-thread stepping.
rdar://175038920
---
lldb/include/lldb/Target/Process.h | 1 +
lldb/source/Target/Process.cpp | 7 ++
lldb/source/Target/TargetProperties.td | 6 ++
lldb/source/Target/ThreadList.cpp | 28 +++++++-
.../always-run-threads/Makefile | 4 ++
.../TestAlwaysRunThreadNames.py | 71 +++++++++++++++++++
.../always-run-threads/main.cpp | 44 ++++++++++++
7 files changed, 158 insertions(+), 3 deletions(-)
create mode 100644 lldb/test/API/functionalities/always-run-threads/Makefile
create mode 100644 lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py
create mode 100644 lldb/test/API/functionalities/always-run-threads/main.cpp
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 19b5ae3041826..f12d873133f9c 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -111,6 +111,7 @@ class ProcessProperties : public Properties {
bool GetOSPluginReportsAllThreads() const;
void SetOSPluginReportsAllThreads(bool does_report);
bool GetSteppingRunsAllThreads() const;
+ Args GetAlwaysRunThreadNames() const;
FollowForkMode GetFollowForkMode() const;
bool TrackMemoryCacheChanges() const;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 05ef27c628fea..34e15e19b82bd 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -342,6 +342,13 @@ bool ProcessProperties::GetSteppingRunsAllThreads() const {
idx, g_process_properties[idx].default_uint_value != 0);
}
+Args ProcessProperties::GetAlwaysRunThreadNames() const {
+ Args args;
+ const uint32_t idx = ePropertyAlwaysRunThreadNames;
+ m_collection_sp->GetPropertyAtIndexAsArgs(idx, args);
+ return args;
+}
+
bool ProcessProperties::GetOSPluginReportsAllThreads() const {
const bool fail_value = true;
const Property *exp_property =
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index e36d233fc2469..937ff349c9d5c 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -310,6 +310,12 @@ let Definition = "process", Path = "target.process" in {
def TrackMemoryCacheChanges: Property<"track-memory-cache-changes", "Boolean">,
DefaultTrue,
Desc<"If true, memory cache modifications (which happen often during expressions evaluation) will bump process state ID (and invalidate all synthetic children). Disabling this option helps to avoid synthetic children reevaluation when pretty printers heavily use expressions. The downside of disabled setting is that convenience variables won't reevaluate synthetic children automatically.">;
+ def AlwaysRunThreadNames
+ : Property<"always-run-thread-names", "Array">,
+ ElementType<"String">,
+ Desc<"A list of thread names. Threads with any of these names will "
+ "always be resumed when the process resumes, even when other "
+ "threads are suspended during single-stepping operations.">;
}
let Definition = "platform", Path = "platform" in {
diff --git a/lldb/source/Target/ThreadList.cpp b/lldb/source/Target/ThreadList.cpp
index b47f848b36998..6f7c9e2e3bea3 100644
--- a/lldb/source/Target/ThreadList.cpp
+++ b/lldb/source/Target/ThreadList.cpp
@@ -704,6 +704,28 @@ bool ThreadList::WillResume(RunDirection &direction) {
bool need_to_resume = true;
+ // Check if any threads should always be allowed to run based on their name.
+ Args always_run_names = m_process.GetAlwaysRunThreadNames();
+ auto resume_state_for_thread = [&](const ThreadSP &thread_sp) -> StateType {
+ if (always_run_names.GetArgumentCount() == 0)
+ return eStateSuspended;
+ const char *name = thread_sp->GetName();
+ if (!name)
+ return eStateSuspended;
+ llvm::StringRef name_str(name);
+ Log *log = GetLog(LLDBLog::Step);
+ for (size_t i = 0; i < always_run_names.GetArgumentCount(); ++i) {
+ if (name_str == always_run_names.GetArgumentAtIndex(i)) {
+ LLDB_LOG(log,
+ "Thread \"{0}\" (tid={1:x}) will continue due to "
+ "always-run-thread-names setting",
+ name, thread_sp->GetID());
+ return eStateRunning;
+ }
+ }
+ return eStateSuspended;
+ };
+
if (!batched_step_threads.empty()) {
// Batched stepping: all threads in the batch step together,
// all other threads stay suspended.
@@ -717,8 +739,8 @@ bool ThreadList::WillResume(RunDirection &direction) {
if (!thread_sp->ShouldResume(thread_sp->GetCurrentPlan()->RunState()))
need_to_resume = false;
} else {
- // Suspend it since it's not in the batch.
- thread_sp->ShouldResume(eStateSuspended);
+ // Suspend it since it's not in the batch, unless it should always run.
+ thread_sp->ShouldResume(resume_state_for_thread(thread_sp));
}
}
} else if (thread_to_run == nullptr) {
@@ -754,7 +776,7 @@ bool ThreadList::WillResume(RunDirection &direction) {
if (!thread_sp->ShouldResume(thread_sp->GetCurrentPlan()->RunState()))
need_to_resume = false;
} else
- thread_sp->ShouldResume(eStateSuspended);
+ thread_sp->ShouldResume(resume_state_for_thread(thread_sp));
}
}
diff --git a/lldb/test/API/functionalities/always-run-threads/Makefile b/lldb/test/API/functionalities/always-run-threads/Makefile
new file mode 100644
index 0000000000000..c46619c662348
--- /dev/null
+++ b/lldb/test/API/functionalities/always-run-threads/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+ENABLE_THREADS := YES
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py b/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py
new file mode 100644
index 0000000000000..de95c5bce3020
--- /dev/null
+++ b/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py
@@ -0,0 +1,71 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class AlwaysRunThreadNamesTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ @skipIfWindows
+ def test_always_run_thread_resumes_during_step(self):
+ """Test that a thread named in always-run-thread-names continues
+ running when another thread single-steps."""
+ self.build()
+ (target, _, thread, _) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.cpp")
+ )
+
+ # Configure the setting to keep our helper thread running.
+ self.runCmd(
+ "settings set target.process.always-run-thread-names always-run-helper"
+ )
+
+ # Record the helper thread's counter before stepping.
+ counter_before = target.FindFirstGlobalVariable("g_helper_count")
+ self.assertTrue(counter_before.IsValid())
+ val_before = counter_before.GetValueAsUnsigned()
+
+ # Step over -- this normally suspends all other threads.
+ thread.StepOver(lldb.eOnlyThisThread)
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonPlanComplete)
+
+ # The helper thread should have been allowed to run, so its counter
+ # should have advanced.
+ counter_after = target.FindFirstGlobalVariable("g_helper_count")
+ val_after = counter_after.GetValueAsUnsigned()
+ self.assertGreater(
+ val_after,
+ val_before,
+ "Helper thread counter did not advance during step-over "
+ "(expected it to run because of always-run-thread-names).",
+ )
+
+ @skipIfWindows
+ def test_without_setting_thread_is_suspended(self):
+ """Test that without the setting, the helper thread is suspended
+ during single-stepping (baseline)."""
+ self.build()
+ (target, _, thread, _) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.cpp")
+ )
+
+ # Do NOT set always-run-thread-names.
+ counter_before = target.FindFirstGlobalVariable("g_helper_count")
+ self.assertTrue(counter_before.IsValid())
+ val_before = counter_before.GetValueAsUnsigned()
+
+ # Step over with only-this-thread mode.
+ thread.StepOver(lldb.eOnlyThisThread)
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonPlanComplete)
+
+ # The helper thread should have been suspended, so its counter should
+ # not have changed.
+ counter_after = target.FindFirstGlobalVariable("g_helper_count")
+ val_after = counter_after.GetValueAsUnsigned()
+ self.assertEqual(
+ val_after,
+ val_before,
+ "Helper thread counter advanced during step-over "
+ "(expected it to be suspended without always-run-thread-names).",
+ )
diff --git a/lldb/test/API/functionalities/always-run-threads/main.cpp b/lldb/test/API/functionalities/always-run-threads/main.cpp
new file mode 100644
index 0000000000000..a876c83387897
--- /dev/null
+++ b/lldb/test/API/functionalities/always-run-threads/main.cpp
@@ -0,0 +1,44 @@
+#include <chrono>
+#include <pthread.h>
+#include <thread>
+
+static void set_thread_name(const char *name) {
+#if defined(__APPLE__)
+ ::pthread_setname_np(name);
+#elif defined(__FreeBSD__) || defined(__linux__)
+ ::pthread_setname_np(::pthread_self(), name);
+#elif defined(__NetBSD__)
+ ::pthread_setname_np(::pthread_self(), "%s", const_cast<char *>(name));
+#endif
+}
+
+volatile int g_helper_count = 0;
+volatile bool g_stop = false;
+
+void helper_thread_func() {
+ set_thread_name("always-run-helper");
+ while (!g_stop) {
+ g_helper_count++;
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+int step_over_me() {
+ int result = 0;
+ for (int i = 0; i < 1000; i++)
+ result += i;
+ return result;
+}
+
+int main() {
+ std::thread helper(helper_thread_func);
+ // Give the helper thread time to start and set its name.
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ int val = step_over_me(); // break here
+ val += step_over_me(); // after step
+
+ g_stop = true;
+ helper.join();
+ return val > 0 ? 0 : 1;
+}
>From 7a8175d85555ce5a37806c3238c2d507e5e9a3c8 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sun, 19 Apr 2026 17:54:45 -0700
Subject: [PATCH 2/2] Account for Linux's 15-character thread name limit
---
.../always-run-threads/TestAlwaysRunThreadNames.py | 2 +-
lldb/test/API/functionalities/always-run-threads/main.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py b/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py
index de95c5bce3020..c41cd90720d66 100644
--- a/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py
+++ b/lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py
@@ -18,7 +18,7 @@ def test_always_run_thread_resumes_during_step(self):
# Configure the setting to keep our helper thread running.
self.runCmd(
- "settings set target.process.always-run-thread-names always-run-helper"
+ "settings set target.process.always-run-thread-names always-run"
)
# Record the helper thread's counter before stepping.
diff --git a/lldb/test/API/functionalities/always-run-threads/main.cpp b/lldb/test/API/functionalities/always-run-threads/main.cpp
index a876c83387897..a218033879d8b 100644
--- a/lldb/test/API/functionalities/always-run-threads/main.cpp
+++ b/lldb/test/API/functionalities/always-run-threads/main.cpp
@@ -16,7 +16,7 @@ volatile int g_helper_count = 0;
volatile bool g_stop = false;
void helper_thread_func() {
- set_thread_name("always-run-helper");
+ set_thread_name("always-run");
while (!g_stop) {
g_helper_count++;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
More information about the lldb-commits
mailing list