https://github.com/DrSergei updated https://github.com/llvm/llvm-project/pull/188710
>From d089e878ac99296d57093d59ae320b4c17eb44ab Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov <[email protected]> Date: Thu, 26 Mar 2026 12:07:49 +0300 Subject: [PATCH 1/5] [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 8432c326d3281..e94b12b1d70f7 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -107,6 +107,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 ac1357f7d00a1..7fb5e1335e057 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -341,6 +341,18 @@ bool ProcessProperties::GetUseDelayedBreakpoints() 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 f438d368c9ba8..5db0809317cbd 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -1554,8 +1554,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; @@ -1610,8 +1611,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 d63bc741e66ed..1802271cf940e 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -282,6 +282,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 412c87d43ab6e0f792ceca2069938d3a62c70ba4 Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov <[email protected]> Date: Thu, 26 Mar 2026 12:40:22 +0300 Subject: [PATCH 2/5] 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 74c5ea587c354bc58c03c43fa40809be08105b5d Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov <[email protected]> Date: Wed, 29 Apr 2026 23:00:43 +0300 Subject: [PATCH 3/5] 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 5db0809317cbd..910a97f7740a1 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -1545,8 +1545,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 1802271cf940e..5a9a85a363d6e 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -285,11 +285,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 5136618e784427a3823997a73016a226f4cf8ab2 Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov <[email protected]> Date: Wed, 29 Apr 2026 23:03:23 +0300 Subject: [PATCH 4/5] 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 >From f5a1f6bc4b95522dec122af774ae22fccf471783 Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov <[email protected]> Date: Tue, 30 Jun 2026 12:40:35 +0300 Subject: [PATCH 5/5] Fix description and tests --- lldb/source/Target/TargetProperties.td | 4 ++-- .../fork/stop/TestStopOnForkAndVFork.py | 22 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td index 5a9a85a363d6e..bf016b52afb83 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -285,11 +285,11 @@ let Definition = "process", Path = "target.process" in { def StopOnFork: Property<"stop-on-fork", "Boolean">, Global, DefaultFalse, - Desc<"If true, stop when the inferior forks.">; + Desc<"If true, stop when the inferior forks. The stop location depends on target.process.follow-fork-mode: 'child' stops in the child process, while 'parent' stops in the original process.">; def StopOnVFork: Property<"stop-on-vfork", "Boolean">, Global, DefaultFalse, - Desc<"If true, stop when the inferior vforks.">; + Desc<"If true, stop when the inferior vforks. The stop location depends on target.process.follow-fork-mode: 'child' stops in the child process, while 'parent' stops in the original process.">; 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 075d75fb66517..9fc85020e66f4 100644 --- a/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py +++ b/lldb/test/API/functionalities/fork/stop/TestStopOnForkAndVFork.py @@ -24,6 +24,8 @@ def do_test(self, mode, fork): self.runCmd(f"settings set target.process.stop-on-{fork} true") self.runCmd(f"settings set target.process.follow-fork-mode {mode}") + pid = process.GetProcessID() + process.Continue() self.assertState( process.GetState(), @@ -35,10 +37,22 @@ def do_test(self, mode, fork): ) 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}"], - ) + if mode == "parent": + self.assertEqual( + self.dbg.GetSelectedTarget().GetProcess().GetProcessID(), pid + ) + self.expect( + "continue", + substrs=[f"exited with status = 0"], + ) + else: # child + self.assertNotEqual( + self.dbg.GetSelectedTarget().GetProcess().GetProcessID(), pid + ) + self.expect( + "continue", + substrs=[f"exited with status = 47"], + ) @skipIfWindows def test_stop_on_fork_and_follow_parent(self): _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
