[Lldb-commits] [lldb] [lldb] Add stop-on-fork and stop-on-vfork settings (PR #188710)
Sergei Druzhkov via lldb-commits
lldb-commits at lists.llvm.org
Wed Apr 29 13:04:29 PDT 2026
https://github.com/DrSergei updated https://github.com/llvm/llvm-project/pull/188710
>From 26c94184fa393f6c05f9836b16d6716b609613a7 Mon Sep 17 00:00:00 2001
From: Sergei Druzhkov <serzhdruzhok at gmail.com>
Date: Thu, 26 Mar 2026 12:07:49 +0300
Subject: [PATCH 1/4] [lldb] Add stop-on-fork and stop-on-vfork settings
---
lldb/include/lldb/Target/Process.h | 2 +
lldb/source/Target/Process.cpp | 12 ++++++
lldb/source/Target/StopInfo.cpp | 10 +++--
lldb/source/Target/TargetProperties.td | 8 ++++
.../API/functionalities/fork/stop/Makefile | 3 ++
.../fork/stop/TestStopOnForkAndVFork.py | 43 +++++++++++++++++++
.../test/API/functionalities/fork/stop/main.c | 14 ++++++
lldb/test/Shell/Subprocess/stop-on-fork.test | 11 +++++
lldb/test/Shell/Subprocess/stop-on-vfork.test | 11 +++++
9 files changed, 110 insertions(+), 4 deletions(-)
create mode 100644 lldb/test/API/functionalities/fork/stop/Makefile
create mode 100644 lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
create mode 100644 lldb/test/API/functionalities/fork/stop/main.c
create mode 100644 lldb/test/Shell/Subprocess/stop-on-fork.test
create mode 100644 lldb/test/Shell/Subprocess/stop-on-vfork.test
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index e0ea6379e967e..c0d5e607e2a29 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -106,6 +106,8 @@ class ProcessProperties : public Properties {
bool GetWarningsOptimization() const;
bool GetWarningsUnsupportedLanguage() const;
bool GetStopOnExec() const;
+ bool GetStopOnFork() const;
+ bool GetStopOnVFork() const;
std::chrono::seconds GetUtilityExpressionTimeout() const;
std::chrono::seconds GetInterruptTimeout() const;
bool GetOSPluginReportsAllThreads() const;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 86ce11522ab69..6e9951ffc878f 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -322,6 +322,18 @@ bool ProcessProperties::GetStopOnExec() const {
idx, g_process_properties[idx].default_uint_value != 0);
}
+bool ProcessProperties::GetStopOnFork() const {
+ const uint32_t idx = ePropertyStopOnFork;
+ return GetPropertyAtIndexAs<bool>(
+ idx, g_process_properties[idx].default_uint_value != 0);
+}
+
+bool ProcessProperties::GetStopOnVFork() const {
+ const uint32_t idx = ePropertyStopOnVFork;
+ return GetPropertyAtIndexAs<bool>(
+ idx, g_process_properties[idx].default_uint_value != 0);
+}
+
std::chrono::seconds ProcessProperties::GetUtilityExpressionTimeout() const {
const uint32_t idx = ePropertyUtilityExpressionTimeout;
uint64_t value = GetPropertyAtIndexAs<uint64_t>(
diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp
index 2a7ea2808d87f..3da965cdb772b 100644
--- a/lldb/source/Target/StopInfo.cpp
+++ b/lldb/source/Target/StopInfo.cpp
@@ -1489,8 +1489,9 @@ class StopInfoFork : public StopInfo {
ThreadSP thread_sp(m_thread_wp.lock());
if (thread_sp) {
ProcessSP process_sp = thread_sp->GetProcess();
- if (process_sp && process_sp->GetModIDRef().IsRunningExpression() &&
- thread_sp->IsRunningCallFunctionPlan())
+ if (process_sp && ((process_sp->GetModIDRef().IsRunningExpression() &&
+ thread_sp->IsRunningCallFunctionPlan()) ||
+ process_sp->GetStopOnFork()))
return true;
}
return false;
@@ -1545,8 +1546,9 @@ class StopInfoVFork : public StopInfo {
ThreadSP thread_sp(m_thread_wp.lock());
if (thread_sp) {
ProcessSP process_sp = thread_sp->GetProcess();
- if (process_sp && process_sp->GetModIDRef().IsRunningExpression() &&
- thread_sp->IsRunningCallFunctionPlan())
+ if (process_sp && ((process_sp->GetModIDRef().IsRunningExpression() &&
+ thread_sp->IsRunningCallFunctionPlan()) ||
+ process_sp->GetStopOnVFork()))
return true;
}
return false;
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index 223a12e059258..c9538c3e19ab6 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -283,6 +283,14 @@ let Definition = "process", Path = "target.process" in {
Global,
DefaultTrue,
Desc<"If true, stop when the inferior exec's.">;
+ def StopOnFork: Property<"stop-on-fork", "Boolean">,
+ Global,
+ DefaultFalse,
+ Desc<"If true, stop when the inferior fork's.">;
+ def StopOnVFork: Property<"stop-on-vfork", "Boolean">,
+ Global,
+ DefaultFalse,
+ Desc<"If true, stop when the inferior vfork's.">;
def UtilityExpressionTimeout: Property<"utility-expression-timeout", "UInt64">,
#ifdef LLDB_SANITIZED
DefaultUnsignedValue<60>,
diff --git a/lldb/test/API/functionalities/fork/stop/Makefile b/lldb/test/API/functionalities/fork/stop/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/fork/stop/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
new file mode 100644
index 0000000000000..14a8daa66e232
--- /dev/null
+++ b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
@@ -0,0 +1,43 @@
+"""
+Make sure that we stop on fork and follow mode works.
+"""
+
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+import os
+
+
+class TestForkResumesChild(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def do_test(self, mode):
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+
+ (target, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.c")
+ )
+ self.runCmd("settings set target.process.stop-on-fork true")
+ self.runCmd(f"settings set target.process.follow-fork-mode {mode}")
+
+ process.Continue()
+ self.assertState(
+ process.GetState(), lldb.eStateStopped, f"Process should be stopped at fork"
+ )
+ threads = lldbutil.get_stopped_threads(process, lldb.eStopReasonFork)
+ self.assertEqual(len(threads), 1, f"We got a thread stopped for fork.")
+
+ self.expect(
+ "continue",
+ substrs=[f"exited with status = {0 if mode == "parent" else 47}"],
+ )
+
+ @skipIfWindows
+ def test_stop_on_fork_and_follow_parent(self):
+ self.do_test("parent")
+
+ @skipIfWindows
+ def test_stop_on_fork_and_follow_child(self):
+ self.do_test("child")
diff --git a/lldb/test/API/functionalities/fork/stop/main.c b/lldb/test/API/functionalities/fork/stop/main.c
new file mode 100644
index 0000000000000..c1fb6e8b313e7
--- /dev/null
+++ b/lldb/test/API/functionalities/fork/stop/main.c
@@ -0,0 +1,14 @@
+#include <assert.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+int main() {
+ // break here
+ pid_t pid = fork();
+ if (pid == 0) {
+ // child
+ _exit(47);
+ }
+ // parent
+ _exit(0);
+}
diff --git a/lldb/test/Shell/Subprocess/stop-on-fork.test b/lldb/test/Shell/Subprocess/stop-on-fork.test
new file mode 100644
index 0000000000000..a83dd613620d7
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/stop-on-fork.test
@@ -0,0 +1,11 @@
+# REQUIRES: native
+# UNSUPPORTED: system-windows
+# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_FORK=fork -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+settings set target.process.stop-on-fork true
+process launch
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = fork
+continue
+# CHECK: function run in parent
+# CHECK: function run in exec'd child
diff --git a/lldb/test/Shell/Subprocess/stop-on-vfork.test b/lldb/test/Shell/Subprocess/stop-on-vfork.test
new file mode 100644
index 0000000000000..728858170273a
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/stop-on-vfork.test
@@ -0,0 +1,11 @@
+# REQUIRES: native
+# UNSUPPORTED: system-windows
+# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_FORK=vfork -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+settings set target.process.stop-on-vfork true
+process launch
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = vfork
+continue
+# CHECK: function run in parent
+# CHECK: function run in exec'd child
>From 54975b784eac0bf4e110e0fd3dc9df887d2b96a8 Mon Sep 17 00:00:00 2001
From: Sergei Druzhkov <serzhdruzhok at gmail.com>
Date: Thu, 26 Mar 2026 12:40:22 +0300
Subject: [PATCH 2/4] Fix python formatting
---
.../API/functionalities/fork/stop/TestStopOnForkAndVFork.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
index 14a8daa66e232..09822c5875d70 100644
--- a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
+++ b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
@@ -31,7 +31,7 @@ def do_test(self, mode):
self.expect(
"continue",
- substrs=[f"exited with status = {0 if mode == "parent" else 47}"],
+ substrs=[f"exited with status = {0 if mode == 'parent' else 47}"],
)
@skipIfWindows
>From 901ca3c5857ac63b1d078f58e7f558a321e309a9 Mon Sep 17 00:00:00 2001
From: Sergei Druzhkov <serzhdruzhok at gmail.com>
Date: Wed, 29 Apr 2026 23:00:43 +0300
Subject: [PATCH 3/4] Fix review comments
---
lldb/source/Target/StopInfo.cpp | 4 +--
lldb/source/Target/TargetProperties.td | 4 +--
.../fork/stop/TestStopOnForkAndVFork.py | 36 ++++++++++++-------
.../test/API/functionalities/fork/stop/main.c | 8 +++--
4 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp
index 3da965cdb772b..fc55ee60fb28a 100644
--- a/lldb/source/Target/StopInfo.cpp
+++ b/lldb/source/Target/StopInfo.cpp
@@ -1480,8 +1480,8 @@ class StopInfoFork : public StopInfo {
bool ShouldStop(Event *event_ptr) override {
// During expression evaluation, return true so that the fork event
// reaches RunThreadPlan as a real stop (not auto-restarted by
- // DoOnRemoval). RunThreadPlan decides whether to stop or continue
- // based on the stop-on-fork option.
+ // DoOnRemoval) or target.process.stop-on-fork is true. RunThreadPlan
+ // decides whether to stop or continue based on the stop-on-fork option.
//
// We check per-thread (not just process-wide IsRunningExpression)
// because other threads may fork concurrently after the
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index c9538c3e19ab6..1f08295016381 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -286,11 +286,11 @@ let Definition = "process", Path = "target.process" in {
def StopOnFork: Property<"stop-on-fork", "Boolean">,
Global,
DefaultFalse,
- Desc<"If true, stop when the inferior fork's.">;
+ Desc<"If true, stop when the inferior forks.">;
def StopOnVFork: Property<"stop-on-vfork", "Boolean">,
Global,
DefaultFalse,
- Desc<"If true, stop when the inferior vfork's.">;
+ Desc<"If true, stop when the inferior vforks.">;
def UtilityExpressionTimeout: Property<"utility-expression-timeout", "UInt64">,
#ifdef LLDB_SANITIZED
DefaultUnsignedValue<60>,
diff --git a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
index 09822c5875d70..bbd7bc7e6682d 100644
--- a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
+++ b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
@@ -6,28 +6,32 @@
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
-import os
-class TestForkResumesChild(TestBase):
+class TestStopOnForkAndVFork(TestBase):
NO_DEBUG_INFO_TESTCASE = True
- def do_test(self, mode):
+ def do_test(self, mode, fork):
self.build()
- exe = self.getBuildArtifact("a.out")
- (target, process, _, _) = lldbutil.run_to_source_breakpoint(
- self, "// break here", lldb.SBFileSpec("main.c")
+ args = [fork]
+ launch_info = lldb.SBLaunchInfo(args)
+ launch_info.SetWorkingDirectory(self.getBuildDir())
+
+ (_, process, _, _) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.c"), launch_info
)
- self.runCmd("settings set target.process.stop-on-fork true")
+ self.runCmd(f"settings set target.process.stop-on-{fork} true")
self.runCmd(f"settings set target.process.follow-fork-mode {mode}")
process.Continue()
self.assertState(
- process.GetState(), lldb.eStateStopped, f"Process should be stopped at fork"
+ process.GetState(), lldb.eStateStopped, f"Process should be stopped at {fork}"
+ )
+ threads = lldbutil.get_stopped_threads(
+ process, lldb.eStopReasonVFork if fork == "vfork" else lldb.eStopReasonFork
)
- threads = lldbutil.get_stopped_threads(process, lldb.eStopReasonFork)
- self.assertEqual(len(threads), 1, f"We got a thread stopped for fork.")
+ self.assertEqual(len(threads), 1, f"We got a thread stopped for {fork}.")
self.expect(
"continue",
@@ -36,8 +40,16 @@ def do_test(self, mode):
@skipIfWindows
def test_stop_on_fork_and_follow_parent(self):
- self.do_test("parent")
+ self.do_test("parent", "fork")
@skipIfWindows
def test_stop_on_fork_and_follow_child(self):
- self.do_test("child")
+ self.do_test("child", "fork")
+
+ @skipIfWindows
+ def test_stop_on_vfork_and_follow_parent(self):
+ self.do_test("parent", "vfork")
+
+ @skipIfWindows
+ def test_stop_on_vfork_and_follow_child(self):
+ self.do_test("child", "vfork")
diff --git a/lldb/test/API/functionalities/fork/stop/main.c b/lldb/test/API/functionalities/fork/stop/main.c
index c1fb6e8b313e7..3849e2fc24a6c 100644
--- a/lldb/test/API/functionalities/fork/stop/main.c
+++ b/lldb/test/API/functionalities/fork/stop/main.c
@@ -1,10 +1,14 @@
#include <assert.h>
+#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
-int main() {
+int main(int argc, char *argv[]) {
+ if (argc != 2) {
+ _exit(1);
+ }
// break here
- pid_t pid = fork();
+ pid_t pid = strcmp(argv[1], "fork") == 0 ? fork() : vfork();
if (pid == 0) {
// child
_exit(47);
>From e163ab0c53e1f89da5ce3ff632b6262e7c195494 Mon Sep 17 00:00:00 2001
From: Sergei Druzhkov <serzhdruzhok at gmail.com>
Date: Wed, 29 Apr 2026 23:03:23 +0300
Subject: [PATCH 4/4] Fix python formatting
---
.../API/functionalities/fork/stop/TestStopOnForkAndVFork.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
index bbd7bc7e6682d..075d75fb66517 100644
--- a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
+++ b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py
@@ -26,7 +26,9 @@ def do_test(self, mode, fork):
process.Continue()
self.assertState(
- process.GetState(), lldb.eStateStopped, f"Process should be stopped at {fork}"
+ process.GetState(),
+ lldb.eStateStopped,
+ f"Process should be stopped at {fork}",
)
threads = lldbutil.get_stopped_threads(
process, lldb.eStopReasonVFork if fork == "vfork" else lldb.eStopReasonFork
More information about the lldb-commits
mailing list