[Lldb-commits] [lldb] [lldb] Add target.process.always-run-thread-names setting (PR #192870)

via lldb-commits lldb-commits at lists.llvm.org
Sun Apr 19 13:48:59 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

<details>
<summary>Changes</summary>

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, potentially 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

---
Full diff: https://github.com/llvm/llvm-project/pull/192870.diff


7 Files Affected:

- (modified) lldb/include/lldb/Target/Process.h (+1) 
- (modified) lldb/source/Target/Process.cpp (+7) 
- (modified) lldb/source/Target/TargetProperties.td (+6) 
- (modified) lldb/source/Target/ThreadList.cpp (+25-3) 
- (added) lldb/test/API/functionalities/always-run-threads/Makefile (+4) 
- (added) lldb/test/API/functionalities/always-run-threads/TestAlwaysRunThreadNames.py (+71) 
- (added) lldb/test/API/functionalities/always-run-threads/main.cpp (+44) 


``````````diff
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;
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/192870


More information about the lldb-commits mailing list