Reland "[lldb] [Process] Watch for fork/vfork notifications" for FreeBSD

The original commit was reverted because of the problems it introduced
on Linux.  However, FreeBSD should not be affected, so restore that part
and we will address Linux separately.

While at it, remove the dbreg hack as the underlying issue has been
fixed in the FreeBSD kernel and the problem is unlikely to happen
in real life use anyway.

Differential Revision: https://reviews.llvm.org/D98822

GitOrigin-RevId: 63d75641054afb0fe43928e9430a2bb92f09e121
diff --git a/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp b/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
index 5961ff4..67a7b73 100644
--- a/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
+++ b/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
@@ -247,6 +247,19 @@
     return;
   }
 
+  if (info.pl_flags & PL_FLAG_FORKED) {
+    MonitorClone(info.pl_child_pid);
+    return;
+  }
+
+  if (info.pl_flags & PL_FLAG_VFORK_DONE) {
+    Status error =
+        PtraceWrapper(PT_CONTINUE, pid, reinterpret_cast<void *>(1), 0);
+    if (error.Fail())
+      SetState(StateType::eStateInvalid);
+    return;
+  }
+
   if (info.pl_lwpid > 0) {
     for (const auto &t : m_threads) {
       if (t->GetID() == static_cast<lldb::tid_t>(info.pl_lwpid))
@@ -705,17 +718,17 @@
 
 void NativeProcessFreeBSD::SigchldHandler() {
   Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS));
-  // Process all pending waitpid notifications.
   int status;
   ::pid_t wait_pid =
       llvm::sys::RetryAfterSignal(-1, waitpid, GetID(), &status, WNOHANG);
 
   if (wait_pid == 0)
-    return; // We are done.
+    return;
 
   if (wait_pid == -1) {
     Status error(errno, eErrorTypePOSIX);
     LLDB_LOG(log, "waitpid ({0}, &status, _) failed: {1}", GetID(), error);
+    return;
   }
 
   WaitStatus wait_status = WaitStatus::Decode(status);
@@ -885,7 +898,7 @@
       PtraceWrapper(PT_GET_EVENT_MASK, GetID(), &events, sizeof(events));
   if (status.Fail())
     return status;
-  events |= PTRACE_LWP;
+  events |= PTRACE_LWP | PTRACE_FORK | PTRACE_VFORK;
   status = PtraceWrapper(PT_SET_EVENT_MASK, GetID(), &events, sizeof(events));
   if (status.Fail())
     return status;
@@ -919,3 +932,39 @@
 bool NativeProcessFreeBSD::SupportHardwareSingleStepping() const {
   return !m_arch.IsMIPS();
 }
+
+void NativeProcessFreeBSD::MonitorClone(::pid_t child_pid) {
+  Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS));
+  LLDB_LOG(log, "fork, child_pid={0}", child_pid);
+
+  int status;
+  ::pid_t wait_pid =
+      llvm::sys::RetryAfterSignal(-1, ::waitpid, child_pid, &status, 0);
+  if (wait_pid != child_pid) {
+    LLDB_LOG(log,
+             "waiting for pid {0} failed. Assuming the pid has "
+             "disappeared in the meantime",
+             child_pid);
+    return;
+  }
+  if (WIFEXITED(status)) {
+    LLDB_LOG(log,
+             "waiting for pid {0} returned an 'exited' event. Not "
+             "tracking it.",
+             child_pid);
+    return;
+  }
+
+  MainLoop unused_loop;
+  NativeProcessFreeBSD child_process{static_cast<::pid_t>(child_pid),
+                                     m_terminal_fd, m_delegate, m_arch,
+                                     unused_loop};
+  child_process.Detach();
+  Status pt_error =
+      PtraceWrapper(PT_CONTINUE, GetID(), reinterpret_cast<void *>(1), 0);
+  if (pt_error.Fail()) {
+    LLDB_LOG_ERROR(log, pt_error.ToError(),
+                   "unable to resume parent process {1}: {0}", GetID());
+    SetState(StateType::eStateInvalid);
+  }
+}
diff --git a/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h b/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
index ceffc37..3ed5f74 100644
--- a/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
+++ b/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
@@ -113,6 +113,7 @@
   void MonitorSIGSTOP(lldb::pid_t pid);
   void MonitorSIGTRAP(lldb::pid_t pid);
   void MonitorSignal(lldb::pid_t pid, int signal);
+  void MonitorClone(::pid_t child_pid);
 
   Status PopulateMemoryRegionCache();
   void SigchldHandler();
diff --git a/test/Shell/Subprocess/Inputs/fork.cpp b/test/Shell/Subprocess/Inputs/fork.cpp
new file mode 100644
index 0000000..4424293
--- /dev/null
+++ b/test/Shell/Subprocess/Inputs/fork.cpp
@@ -0,0 +1,50 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+#if defined(TEST_CLONE)
+#include <sched.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int g_val = 0;
+
+void parent_func() {
+  g_val = 1;
+  printf("function run in parent\n");
+}
+
+int child_func(void *unused) {
+  // we need to avoid memory modifications for vfork(), yet we want
+  // to be able to test watchpoints, so do the next best thing
+  // and restore the original value
+  g_val = 2;
+  g_val = 0;
+  return 0;
+}
+
+int main() {
+  alignas(uintmax_t) char stack[4096];
+
+#if defined(TEST_CLONE)
+  pid_t pid = clone(child_func, &stack[sizeof(stack)], 0, NULL);
+#elif defined(TEST_FORK)
+  pid_t pid = TEST_FORK();
+  if (pid == 0)
+    _exit(child_func(NULL));
+#endif
+  assert(pid != -1);
+
+  parent_func();
+  int status, wait_flags = 0;
+#if defined(TEST_CLONE)
+  wait_flags = __WALL;
+#endif
+  pid_t waited = waitpid(pid, &status, wait_flags);
+  assert(waited == pid);
+  assert(WIFEXITED(status));
+  printf("child exited: %d\n", WEXITSTATUS(status));
+
+  return 0;
+}
diff --git a/test/Shell/Subprocess/fork-follow-parent-wp.test b/test/Shell/Subprocess/fork-follow-parent-wp.test
new file mode 100644
index 0000000..cde5b41
--- /dev/null
+++ b/test/Shell/Subprocess/fork-follow-parent-wp.test
@@ -0,0 +1,13 @@
+# REQUIRES: native && dbregs-set
+# UNSUPPORTED: system-windows
+# RUN: %clangxx_host -g %p/Inputs/fork.cpp -DTEST_FORK=fork -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+process launch -s
+watchpoint set variable -w write g_val
+# CHECK: Watchpoint created:
+continue
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
diff --git a/test/Shell/Subprocess/fork-follow-parent.test b/test/Shell/Subprocess/fork-follow-parent.test
new file mode 100644
index 0000000..1ffc2cb
--- /dev/null
+++ b/test/Shell/Subprocess/fork-follow-parent.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
+b parent_func
+process launch
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = breakpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
diff --git a/test/Shell/Subprocess/lit.local.cfg b/test/Shell/Subprocess/lit.local.cfg
new file mode 100644
index 0000000..c9b378b
--- /dev/null
+++ b/test/Shell/Subprocess/lit.local.cfg
@@ -0,0 +1,2 @@
+if 'lldb-repro' in config.available_features:
+  config.unsupported = True
diff --git a/test/Shell/Subprocess/vfork-follow-parent-wp.test b/test/Shell/Subprocess/vfork-follow-parent-wp.test
new file mode 100644
index 0000000..8c74e28
--- /dev/null
+++ b/test/Shell/Subprocess/vfork-follow-parent-wp.test
@@ -0,0 +1,14 @@
+# REQUIRES: native && dbregs-set
+# UNSUPPORTED: system-windows
+# UNSUPPORTED: system-darwin
+# RUN: %clangxx_host -g %p/Inputs/fork.cpp -DTEST_FORK=vfork -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+process launch -s
+watchpoint set variable -w write g_val
+# CHECK: Watchpoint created:
+continue
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
diff --git a/test/Shell/Subprocess/vfork-follow-parent.test b/test/Shell/Subprocess/vfork-follow-parent.test
new file mode 100644
index 0000000..6d9850a
--- /dev/null
+++ b/test/Shell/Subprocess/vfork-follow-parent.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
+b parent_func
+process launch
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = breakpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0