[Lldb-commits] [lldb] a345419 - [lldb] [Process] Watch for fork/vfork notifications

Michał Górny via lldb-commits lldb-commits at lists.llvm.org
Thu Apr 8 09:49:56 PDT 2021


Author: Michał Górny
Date: 2021-04-08T18:49:50+02:00
New Revision: a345419ee03095c8cdfbe1c2728467c4da8fa0a4

URL: https://github.com/llvm/llvm-project/commit/a345419ee03095c8cdfbe1c2728467c4da8fa0a4
DIFF: https://github.com/llvm/llvm-project/commit/a345419ee03095c8cdfbe1c2728467c4da8fa0a4.diff

LOG: [lldb] [Process] Watch for fork/vfork notifications

Watch for fork(2)/vfork(2) (also fork/vfork-style clone(2) on Linux)
notifications and explicitly detach the forked child process, and add
initial tests for these cases.  The code covers FreeBSD, Linux
and NetBSD process plugins.  There is no new user-visible functionality
provided -- this change lays foundations over subsequent work on fork
support.

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

Added: 
    lldb/include/lldb/Host/linux/Host.h
    lldb/test/Shell/Subprocess/Inputs/fork.cpp
    lldb/test/Shell/Subprocess/clone-follow-parent-wp.test
    lldb/test/Shell/Subprocess/clone-follow-parent.test
    lldb/test/Shell/Subprocess/fork-follow-parent-wp.test
    lldb/test/Shell/Subprocess/fork-follow-parent.test
    lldb/test/Shell/Subprocess/vfork-follow-parent-wp.test
    lldb/test/Shell/Subprocess/vfork-follow-parent.test

Modified: 
    lldb/source/Host/linux/Host.cpp
    lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
    lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
    lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h
    lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp
    lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h
    lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp
    lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h
    lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
    lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
    lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp
    lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h
    lldb/test/API/functionalities/gdb_remote_client/TestMultiprocess.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Host/linux/Host.h b/lldb/include/lldb/Host/linux/Host.h
new file mode 100644
index 000000000000..409a9fc9b322
--- /dev/null
+++ b/lldb/include/lldb/Host/linux/Host.h
@@ -0,0 +1,22 @@
+//===-- Host.h --------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_HOST_LINUX_HOST_H
+#define LLDB_HOST_LINUX_HOST_H
+
+#include "lldb/lldb-types.h"
+#include "llvm/ADT/Optional.h"
+
+namespace lldb_private {
+
+// Get PID (i.e. the primary thread ID) corresponding to the specified TID.
+llvm::Optional<lldb::pid_t> getPIDForTID(lldb::pid_t tid);
+
+} // namespace lldb_private
+
+#endif // #ifndef LLDB_HOST_LINUX_HOST_H

diff  --git a/lldb/source/Host/linux/Host.cpp b/lldb/source/Host/linux/Host.cpp
index 520a00df35f6..334634a357f5 100644
--- a/lldb/source/Host/linux/Host.cpp
+++ b/lldb/source/Host/linux/Host.cpp
@@ -27,6 +27,7 @@
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Host/Host.h"
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Host/linux/Host.h"
 #include "lldb/Host/linux/Support.h"
 #include "lldb/Utility/DataExtractor.h"
 
@@ -53,7 +54,8 @@ class ProcessLaunchInfo;
 }
 
 static bool GetStatusInfo(::pid_t Pid, ProcessInstanceInfo &ProcessInfo,
-                          ProcessState &State, ::pid_t &TracerPid) {
+                          ProcessState &State, ::pid_t &TracerPid,
+                          ::pid_t &Tgid) {
   Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
 
   auto BufferOrError = getProcFile(Pid, "status");
@@ -107,6 +109,9 @@ static bool GetStatusInfo(::pid_t Pid, ProcessInstanceInfo &ProcessInfo,
     } else if (Line.consume_front("TracerPid:")) {
       Line = Line.ltrim();
       Line.consumeInteger(10, TracerPid);
+    } else if (Line.consume_front("Tgid:")) {
+      Line = Line.ltrim();
+      Line.consumeInteger(10, Tgid);
     }
   }
   return true;
@@ -204,6 +209,7 @@ static void GetProcessEnviron(::pid_t pid, ProcessInstanceInfo &process_info) {
 static bool GetProcessAndStatInfo(::pid_t pid,
                                   ProcessInstanceInfo &process_info,
                                   ProcessState &State, ::pid_t &tracerpid) {
+  ::pid_t tgid;
   tracerpid = 0;
   process_info.Clear();
 
@@ -214,7 +220,7 @@ static bool GetProcessAndStatInfo(::pid_t pid,
   GetProcessEnviron(pid, process_info);
 
   // Get User and Group IDs and get tracer pid.
-  if (!GetStatusInfo(pid, process_info, State, tracerpid))
+  if (!GetStatusInfo(pid, process_info, State, tracerpid, tgid))
     return false;
 
   return true;
@@ -308,3 +314,14 @@ Environment Host::GetEnvironment() { return Environment(environ); }
 Status Host::ShellExpandArguments(ProcessLaunchInfo &launch_info) {
   return Status("unimplemented");
 }
+
+llvm::Optional<lldb::pid_t> lldb_private::getPIDForTID(lldb::pid_t tid) {
+  ::pid_t tracerpid, tgid = LLDB_INVALID_PROCESS_ID;
+  ProcessInstanceInfo process_info;
+  ProcessState state;
+
+  if (!GetStatusInfo(tid, process_info, state, tracerpid, tgid) ||
+      tgid == LLDB_INVALID_PROCESS_ID)
+    return llvm::None;
+  return tgid;
+}

diff  --git a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
index 5961ff4439db..c55a66474e66 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
@@ -247,6 +247,19 @@ void NativeProcessFreeBSD::MonitorSIGTRAP(lldb::pid_t pid) {
     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 @@ NativeProcessFreeBSD::GetFileLoadAddress(const llvm::StringRef &file_name,
 
 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 @@ Status NativeProcessFreeBSD::SetupTrace() {
       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,53 @@ Status NativeProcessFreeBSD::ReinitializeThreads() {
 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_delegates[0], m_arch,
+                                     unused_loop};
+  child_process.ReinitializeThreads();
+  auto *child_thread =
+      static_cast<NativeThreadFreeBSD *>(child_process.GetCurrentThread());
+  assert(child_thread);
+  // new processes inherit dbregs, so we need to clear them
+  llvm::Error error = child_thread->GetRegisterContext().ClearDBRegs();
+  if (error) {
+    LLDB_LOG_ERROR(log, std::move(error),
+                   "failed to clear dbregs in forked process {1}: {0}",
+                   child_pid);
+    SetState(StateType::eStateInvalid);
+    return;
+  }
+
+  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/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
index ceffc370ca33..3ed5f743deba 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
@@ -113,6 +113,7 @@ class NativeProcessFreeBSD : public NativeProcessELF,
   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/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h
index 0000484beac9..cf2afcfa98d4 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h
@@ -32,6 +32,8 @@ class NativeRegisterContextFreeBSD
   virtual llvm::Error
   CopyHardwareWatchpointsFrom(NativeRegisterContextFreeBSD &source) = 0;
 
+  virtual llvm::Error ClearDBRegs() { return llvm::Error::success(); }
+
 protected:
   virtual NativeProcessFreeBSD &GetProcess();
   virtual ::pid_t GetProcessPid();

diff  --git a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp
index e98e0a8a0caa..74abf24a847c 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp
@@ -285,4 +285,19 @@ NativeRegisterContextFreeBSD_arm64::WriteHardwareDebugRegs(DREGType) {
 #endif
 }
 
+llvm::Error NativeRegisterContextFreeBSD_arm64::ClearDBRegs() {
+#ifdef LLDB_HAS_FREEBSD_WATCHPOINT
+  if (llvm::Error error = ReadHardwareDebugInfo())
+    return error;
+
+  for (uint32_t i = 0; i < m_max_hbp_supported; i++)
+    m_hbp_regs[i].control = 0;
+  for (uint32_t i = 0; i < m_max_hwp_supported; i++)
+    m_hwp_regs[i].control = 0;
+  return WriteHardwareDebugRegs(eDREGTypeWATCH);
+#else
+  return llvm::error::success();
+#endif
+}
+
 #endif // defined (__aarch64__)

diff  --git a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h
index a230f8fed48a..ae6f5741639a 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h
@@ -58,6 +58,8 @@ class NativeRegisterContextFreeBSD_arm64
   llvm::Error
   CopyHardwareWatchpointsFrom(NativeRegisterContextFreeBSD &source) override;
 
+  llvm::Error ClearDBRegs() override;
+
 private:
   // Due to alignment, FreeBSD reg/fpreg are a few bytes larger than
   // LLDB's GPR/FPU structs.  However, all fields have matching offsets

diff  --git a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp
index 9328d606ad26..5415b49b32c9 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp
@@ -653,4 +653,10 @@ NativeRegisterContextFreeBSD_x86_64::GetYMMSplitReg(uint32_t reg) {
   return YMMSplitPtr{&fpreg->sv_xmm[reg_index], &ymmreg[reg_index]};
 }
 
+llvm::Error NativeRegisterContextFreeBSD_x86_64::ClearDBRegs() {
+  uint64_t zero = 0;
+  RegisterValue dr7{zero};
+  return WriteRegister(GetDR(7), dr7).ToError();
+}
+
 #endif // defined(__x86_64__)

diff  --git a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h
index efd0f91f77b9..e4932c9afe49 100644
--- a/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h
+++ b/lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h
@@ -55,6 +55,8 @@ class NativeRegisterContextFreeBSD_x86_64
   llvm::Error
   CopyHardwareWatchpointsFrom(NativeRegisterContextFreeBSD &source) override;
 
+  llvm::Error ClearDBRegs() override;
+
 private:
   // Private member types.
   enum RegSetKind {

diff  --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
index 5a876ce5befe..418a7a39be2a 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
@@ -30,6 +30,7 @@
 #include "lldb/Host/PseudoTerminal.h"
 #include "lldb/Host/ThreadLauncher.h"
 #include "lldb/Host/common/NativeRegisterContext.h"
+#include "lldb/Host/linux/Host.h"
 #include "lldb/Host/linux/Ptrace.h"
 #include "lldb/Host/linux/Uio.h"
 #include "lldb/Host/posix/ProcessLauncherPosixFork.h"
@@ -383,14 +384,22 @@ Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
   ptrace_opts |= PTRACE_O_TRACEEXIT;
 
   // Have the tracer trace threads which spawn in the inferior process.
-  // TODO: if we want to support tracing the inferiors' child, add the
-  // appropriate ptrace flags here (PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK)
   ptrace_opts |= PTRACE_O_TRACECLONE;
 
   // Have the tracer notify us before execve returns (needed to disable legacy
   // SIGTRAP generation)
   ptrace_opts |= PTRACE_O_TRACEEXEC;
 
+  // Have the tracer trace forked children.
+  ptrace_opts |= PTRACE_O_TRACEFORK;
+
+  // Have the tracer trace vforks.
+  ptrace_opts |= PTRACE_O_TRACEVFORK;
+
+  // Have the tracer trace vfork-done in order to restore breakpoints after
+  // the child finishes sharing memory.
+  ptrace_opts |= PTRACE_O_TRACEVFORKDONE;
+
   return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts);
 }
 
@@ -444,8 +453,7 @@ void NativeProcessLinux::MonitorCallback(lldb::pid_t pid, bool exited,
     LLDB_LOG(log, "tid {0}, si_code: {1}, si_pid: {2}", pid, info.si_code,
              info.si_pid);
 
-    NativeThreadLinux &thread = AddThread(pid, /*resume*/ true);
-    ThreadWasCreated(thread);
+    MonitorClone(pid, llvm::None);
     return;
   }
 
@@ -509,29 +517,24 @@ void NativeProcessLinux::MonitorCallback(lldb::pid_t pid, bool exited,
   }
 }
 
-void NativeProcessLinux::WaitForNewThread(::pid_t tid) {
+void NativeProcessLinux::WaitForCloneNotification(::pid_t pid) {
   Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS));
 
-  if (GetThreadByID(tid)) {
-    // We are already tracking the thread - we got the event on the new thread
-    // (see MonitorSignal) before this one. We are done.
-    return;
-  }
-
-  // The thread is not tracked yet, let's wait for it to appear.
+  // The PID is not tracked yet, let's wait for it to appear.
   int status = -1;
   LLDB_LOG(log,
-           "received thread creation event for tid {0}. tid not tracked "
-           "yet, waiting for thread to appear...",
-           tid);
-  ::pid_t wait_pid = llvm::sys::RetryAfterSignal(-1, ::waitpid, tid, &status, __WALL);
-  // Since we are waiting on a specific tid, this must be the creation event.
+           "received clone event for pid {0}. pid not tracked yet, "
+           "waiting for it to appear...",
+           pid);
+  ::pid_t wait_pid =
+      llvm::sys::RetryAfterSignal(-1, ::waitpid, pid, &status, __WALL);
+  // Since we are waiting on a specific pid, this must be the creation event.
   // But let's do some checks just in case.
-  if (wait_pid != tid) {
+  if (wait_pid != pid) {
     LLDB_LOG(log,
-             "waiting for tid {0} failed. Assuming the thread has "
+             "waiting for pid {0} failed. Assuming the pid has "
              "disappeared in the meantime",
-             tid);
+             pid);
     // The only way I know of this could happen is if the whole process was
     // SIGKILLed in the mean time. In any case, we can't do anything about that
     // now.
@@ -539,17 +542,15 @@ void NativeProcessLinux::WaitForNewThread(::pid_t tid) {
   }
   if (WIFEXITED(status)) {
     LLDB_LOG(log,
-             "waiting for tid {0} returned an 'exited' event. Not "
-             "tracking the thread.",
-             tid);
+             "waiting for pid {0} returned an 'exited' event. Not "
+             "tracking it.",
+             pid);
     // Also a very improbable event.
+    m_pending_pid_map.erase(pid);
     return;
   }
 
-  LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid);
-  NativeThreadLinux &new_thread = AddThread(tid, /*resume*/ true);
-
-  ThreadWasCreated(new_thread);
+  MonitorClone(pid, llvm::None);
 }
 
 void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
@@ -560,26 +561,26 @@ void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
   assert(info.si_signo == SIGTRAP && "Unexpected child signal!");
 
   switch (info.si_code) {
-  // TODO: these two cases are required if we want to support tracing of the
-  // inferiors' children.  We'd need this to debug a monitor. case (SIGTRAP |
-  // (PTRACE_EVENT_FORK << 8)): case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)):
-
+  case (SIGTRAP | (PTRACE_EVENT_FORK << 8)):
+  case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)):
   case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): {
-    // This is the notification on the parent thread which informs us of new
-    // thread creation. We don't want to do anything with the parent thread so
-    // we just resume it. In case we want to implement "break on thread
-    // creation" functionality, we would need to stop here.
+    // This can either mean a new thread or a new process spawned via
+    // clone(2) without SIGCHLD or CLONE_VFORK flag.  Note that clone(2)
+    // can also cause PTRACE_EVENT_FORK and PTRACE_EVENT_VFORK if one
+    // of these flags are passed.
 
     unsigned long event_message = 0;
     if (GetEventMessage(thread.GetID(), &event_message).Fail()) {
       LLDB_LOG(log,
-               "pid {0} received thread creation event but "
-               "GetEventMessage failed so we don't know the new tid",
+               "pid {0} received clone() event but GetEventMessage failed "
+               "so we don't know the new pid/tid",
                thread.GetID());
-    } else
-      WaitForNewThread(event_message);
+      ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER);
+    } else {
+      if (!MonitorClone(event_message, {{(info.si_code >> 8), thread.GetID()}}))
+        WaitForCloneNotification(event_message);
+    }
 
-    ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER);
     break;
   }
 
@@ -645,6 +646,11 @@ void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
     break;
   }
 
+  case (SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8)): {
+    ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER);
+    break;
+  }
+
   case 0:
   case TRAP_TRACE:  // We receive this on single stepping.
   case TRAP_HWBKPT: // We receive this on watchpoint hit
@@ -854,6 +860,79 @@ void NativeProcessLinux::MonitorSignal(const siginfo_t &info,
   StopRunningThreads(thread.GetID());
 }
 
+bool NativeProcessLinux::MonitorClone(
+    lldb::pid_t child_pid,
+    llvm::Optional<NativeProcessLinux::CloneInfo> clone_info) {
+  Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS));
+  LLDB_LOG(log, "clone, child_pid={0}, clone info?={1}", child_pid,
+           clone_info.hasValue());
+
+  auto find_it = m_pending_pid_map.find(child_pid);
+  if (find_it == m_pending_pid_map.end()) {
+    // not in the map, so this is the first signal for the PID
+    m_pending_pid_map.insert({child_pid, clone_info});
+    return false;
+  }
+  m_pending_pid_map.erase(find_it);
+
+  // second signal for the pid
+  assert(clone_info.hasValue() != find_it->second.hasValue());
+  if (!clone_info) {
+    // child signal does not indicate the event, so grab the one stored
+    // earlier
+    clone_info = find_it->second;
+  }
+
+  LLDB_LOG(log, "second signal for child_pid={0}, parent_tid={1}, event={2}",
+           child_pid, clone_info->parent_tid, clone_info->event);
+
+  auto *parent_thread = GetThreadByID(clone_info->parent_tid);
+  assert(parent_thread);
+
+  switch (clone_info->event) {
+  case PTRACE_EVENT_CLONE: {
+    // PTRACE_EVENT_CLONE can either mean a new thread or a new process.
+    // Try to grab the new process' PGID to figure out which one it is.
+    // If PGID is the same as the PID, then it's a new process.  Otherwise,
+    // it's a thread.
+    auto tgid_ret = getPIDForTID(child_pid);
+    if (tgid_ret != child_pid) {
+      // A new thread should have PGID matching our process' PID.
+      assert(!tgid_ret || tgid_ret.getValue() == GetID());
+
+      NativeThreadLinux &child_thread = AddThread(child_pid, /*resume*/ true);
+      // Resume the newly created thread.
+      ResumeThread(child_thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER);
+      ThreadWasCreated(child_thread);
+
+      // Resume the parent.
+      ResumeThread(*parent_thread, parent_thread->GetState(),
+                   LLDB_INVALID_SIGNAL_NUMBER);
+      break;
+    }
+  }
+    LLVM_FALLTHROUGH;
+  case PTRACE_EVENT_FORK:
+  case PTRACE_EVENT_VFORK: {
+    MainLoop unused_loop;
+    NativeProcessLinux child_process{static_cast<::pid_t>(child_pid),
+                                     m_terminal_fd,
+                                     *m_delegates[0],
+                                     m_arch,
+                                     unused_loop,
+                                     {static_cast<::pid_t>(child_pid)}};
+    child_process.Detach();
+    ResumeThread(*parent_thread, parent_thread->GetState(),
+                 LLDB_INVALID_SIGNAL_NUMBER);
+    break;
+  }
+  default:
+    llvm_unreachable("unknown clone_info.event");
+  }
+
+  return true;
+}
+
 bool NativeProcessLinux::SupportHardwareSingleStepping() const {
   if (m_arch.GetMachine() == llvm::Triple::arm || m_arch.IsMIPS())
     return false;

diff  --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
index 77905a193c2b..86f6a1c27e5a 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
+++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
@@ -157,7 +157,7 @@ class NativeProcessLinux : public NativeProcessELF,
 
   void MonitorCallback(lldb::pid_t pid, bool exited, WaitStatus status);
 
-  void WaitForNewThread(::pid_t tid);
+  void WaitForCloneNotification(::pid_t pid);
 
   void MonitorSIGTRAP(const siginfo_t &info, NativeThreadLinux &thread);
 
@@ -234,6 +234,21 @@ class NativeProcessLinux : public NativeProcessELF,
 
   /// Manages Intel PT process and thread traces.
   IntelPTManager m_intel_pt_manager;
+
+  struct CloneInfo {
+    int event;
+    lldb::tid_t parent_tid;
+  };
+
+  // Map of child processes that have been signaled once, and we are
+  // waiting for the second signal.
+  llvm::DenseMap<lldb::pid_t, llvm::Optional<CloneInfo>> m_pending_pid_map;
+
+  // Handle a clone()-like event.  If received by parent, clone_info contains
+  // additional info.  Returns true if the event is handled, or false if it
+  // is pending second notification.
+  bool MonitorClone(lldb::pid_t child_pid,
+                    llvm::Optional<CloneInfo> clone_info);
 };
 
 } // namespace process_linux

diff  --git a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp
index 57f0eb3cceb6..957682a2d266 100644
--- a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp
+++ b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp
@@ -256,6 +256,24 @@ void NativeProcessNetBSD::MonitorSIGTRAP(lldb::pid_t pid) {
     SetState(StateType::eStateStopped, true);
     return;
   }
+  case TRAP_CHLD: {
+    ptrace_state_t pst;
+    Status error = PtraceWrapper(PT_GET_PROCESS_STATE, pid, &pst, sizeof(pst));
+    if (error.Fail()) {
+      SetState(StateType::eStateInvalid);
+      return;
+    }
+
+    if (pst.pe_report_event == PTRACE_VFORK_DONE) {
+      Status error =
+          PtraceWrapper(PT_CONTINUE, pid, reinterpret_cast<void *>(1), 0);
+      if (error.Fail())
+        SetState(StateType::eStateInvalid);
+      return;
+    } else
+      MonitorClone(pst.pe_other_pid);
+    return;
+  }
   case TRAP_LWP: {
     ptrace_state_t pst;
     Status error = PtraceWrapper(PT_GET_PROCESS_STATE, pid, &pst, sizeof(pst));
@@ -510,7 +528,7 @@ Status NativeProcessNetBSD::Detach() {
   if (GetID() == LLDB_INVALID_PROCESS_ID)
     return error;
 
-  return PtraceWrapper(PT_DETACH, GetID());
+  return PtraceWrapper(PT_DETACH, GetID(), reinterpret_cast<void *>(1));
 }
 
 Status NativeProcessNetBSD::Signal(int signo) {
@@ -738,17 +756,17 @@ Status NativeProcessNetBSD::GetFileLoadAddress(const llvm::StringRef &file_name,
 
 void NativeProcessNetBSD::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,
                                                  WALLSIG | 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);
@@ -936,8 +954,9 @@ Status NativeProcessNetBSD::SetupTrace() {
       PtraceWrapper(PT_GET_EVENT_MASK, GetID(), &events, sizeof(events));
   if (status.Fail())
     return status;
-  // TODO: PTRACE_FORK | PTRACE_VFORK | PTRACE_POSIX_SPAWN?
-  events.pe_set_event |= PTRACE_LWP_CREATE | PTRACE_LWP_EXIT;
+  // TODO: PTRACE_POSIX_SPAWN?
+  events.pe_set_event |= PTRACE_LWP_CREATE | PTRACE_LWP_EXIT | PTRACE_FORK |
+                         PTRACE_VFORK | PTRACE_VFORK_DONE;
   status = PtraceWrapper(PT_SET_EVENT_MASK, GetID(), &events, sizeof(events));
   if (status.Fail())
     return status;
@@ -974,3 +993,39 @@ Status NativeProcessNetBSD::ReinitializeThreads() {
 
   return error;
 }
+
+void NativeProcessNetBSD::MonitorClone(::pid_t child_pid) {
+  Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS));
+  LLDB_LOG(log, "clone, 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;
+  NativeProcessNetBSD child_process{static_cast<::pid_t>(child_pid),
+                                    m_terminal_fd, *m_delegates[0], 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, std::move(pt_error.ToError()),
+                   "unable to resume parent process {1}: {0}", GetID());
+    SetState(StateType::eStateInvalid);
+  }
+}

diff  --git a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h
index 3d59a4f72e94..3384078a0d6d 100644
--- a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h
+++ b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h
@@ -106,6 +106,7 @@ class NativeProcessNetBSD : public NativeProcessELF {
   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/lldb/test/API/functionalities/gdb_remote_client/TestMultiprocess.py b/lldb/test/API/functionalities/gdb_remote_client/TestMultiprocess.py
index baf9f9cddedf..551bf7234283 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestMultiprocess.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestMultiprocess.py
@@ -1,6 +1,5 @@
 from __future__ import print_function
 import lldb
-import unittest
 from lldbsuite.test.lldbtest import *
 from lldbsuite.test.decorators import *
 from gdbclientutils import *

diff  --git a/lldb/test/Shell/Subprocess/Inputs/fork.cpp b/lldb/test/Shell/Subprocess/Inputs/fork.cpp
new file mode 100644
index 000000000000..44242934bd3e
--- /dev/null
+++ b/lldb/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/lldb/test/Shell/Subprocess/clone-follow-parent-wp.test b/lldb/test/Shell/Subprocess/clone-follow-parent-wp.test
new file mode 100644
index 000000000000..64ae20d7e363
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/clone-follow-parent-wp.test
@@ -0,0 +1,12 @@
+# REQUIRES: native && (system-linux || system-netbsd) && dbregs-set
+# RUN: %clangxx_host -g %p/Inputs/fork.cpp -DTEST_CLONE -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/lldb/test/Shell/Subprocess/clone-follow-parent.test b/lldb/test/Shell/Subprocess/clone-follow-parent.test
new file mode 100644
index 000000000000..863531af9a4d
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/clone-follow-parent.test
@@ -0,0 +1,10 @@
+# REQUIRES: native && (system-linux || system-netbsd)
+# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_CLONE -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/lldb/test/Shell/Subprocess/fork-follow-parent-wp.test b/lldb/test/Shell/Subprocess/fork-follow-parent-wp.test
new file mode 100644
index 000000000000..cde5b41f228b
--- /dev/null
+++ b/lldb/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/lldb/test/Shell/Subprocess/fork-follow-parent.test b/lldb/test/Shell/Subprocess/fork-follow-parent.test
new file mode 100644
index 000000000000..1ffc2cbc1f99
--- /dev/null
+++ b/lldb/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/lldb/test/Shell/Subprocess/vfork-follow-parent-wp.test b/lldb/test/Shell/Subprocess/vfork-follow-parent-wp.test
new file mode 100644
index 000000000000..d73b854b6647
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/vfork-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=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/lldb/test/Shell/Subprocess/vfork-follow-parent.test b/lldb/test/Shell/Subprocess/vfork-follow-parent.test
new file mode 100644
index 000000000000..6d9850a706e6
--- /dev/null
+++ b/lldb/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


        


More information about the lldb-commits mailing list