mgorny updated this revision to Diff 333064.
mgorny retitled this revision from "[lldb] [Process/Linux] Watch for fork 
notifications" to "[lldb] [Process/Linux] Watch for fork/vfork notifications".
mgorny edited the summary of this revision.
mgorny added a comment.

Included `vfork()`. Still have to look into other `clone()` variants, then 
other platforms.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D98822/new/

https://reviews.llvm.org/D98822

Files:
  lldb/include/lldb/Host/common/NativeProcessProtocol.h
  lldb/source/Host/common/NativeProcessProtocol.cpp
  lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
  lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
  lldb/test/Shell/Subprocess/Inputs/fork.c
  lldb/test/Shell/Subprocess/Inputs/vfork.c
  lldb/test/Shell/Subprocess/fork-follow-parent-softbp.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-softbp.test
  lldb/test/Shell/Subprocess/vfork-follow-parent.test

Index: lldb/test/Shell/Subprocess/vfork-follow-parent.test
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/vfork-follow-parent.test
@@ -0,0 +1,10 @@
+# REQUIRES: native
+# RUN: %clang_host %p/Inputs/vfork.c -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
Index: lldb/test/Shell/Subprocess/vfork-follow-parent-softbp.test
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/vfork-follow-parent-softbp.test
@@ -0,0 +1,11 @@
+# REQUIRES: native
+# RUN: %clang_host %p/Inputs/vfork.c -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+b parent_func
+b child_func
+process launch
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = breakpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
Index: lldb/test/Shell/Subprocess/fork-follow-parent.test
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/fork-follow-parent.test
@@ -0,0 +1,11 @@
+# REQUIRES: native
+# RUN: %clang_host %p/Inputs/fork.c -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+b parent_func
+process launch
+# CHECK: function run in child
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = breakpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
Index: lldb/test/Shell/Subprocess/fork-follow-parent-wp.test
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/fork-follow-parent-wp.test
@@ -0,0 +1,13 @@
+# REQUIRES: native && (target-x86 || target-x86_64 || target-aarch64) && dbregs-set
+# RUN: %clang_host -g %p/Inputs/fork.c -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: function run in child
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
Index: lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test
@@ -0,0 +1,12 @@
+# REQUIRES: native
+# RUN: %clang_host %p/Inputs/fork.c -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+b parent_func
+b child_func
+process launch
+# CHECK: function run in child
+# CHECK-NOT: function run in parent
+# CHECK: stop reason = breakpoint
+continue
+# CHECK: function run in parent
+# CHECK: child exited: 0
Index: lldb/test/Shell/Subprocess/Inputs/vfork.c
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/Inputs/vfork.c
@@ -0,0 +1,37 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int g_val = 0;
+
+void parent_func() {
+  g_val = 1;
+  printf("function run in parent\n");
+}
+
+void child_func() {
+  // do something relatively safe
+  volatile int val = 0;
+  ++val;
+}
+
+int main() {
+  pid_t pid = vfork();
+  assert(pid != -1);
+
+  if (pid == 0) {
+    child_func();
+    _exit(0);
+  }
+
+  parent_func();
+  int status;
+  pid_t waited = waitpid(pid, &status, 0);
+  assert(waited == pid);
+  assert(WIFEXITED(status));
+  printf("child exited: %d\n", WEXITSTATUS(status));
+
+  return 0;
+}
Index: lldb/test/Shell/Subprocess/Inputs/fork.c
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/Inputs/fork.c
@@ -0,0 +1,36 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int g_val = 0;
+
+void parent_func() {
+  g_val = 1;
+  printf("function run in parent\n");
+}
+
+void child_func() {
+  g_val = 2;
+  printf("function run in child\n");
+}
+
+int main() {
+  pid_t pid = fork();
+  assert(pid != -1);
+
+  if (pid == 0) {
+    child_func();
+    _exit(0);
+  }
+
+  parent_func();
+  int status;
+  pid_t waited = waitpid(pid, &status, 0);
+  assert(waited == pid);
+  assert(WIFEXITED(status));
+  printf("child exited: %d\n", WEXITSTATUS(status));
+
+  return 0;
+}
Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
===================================================================
--- lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
+++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
@@ -158,7 +158,7 @@
 
   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);
 
@@ -248,6 +248,24 @@
 
   lldb::user_id_t m_pt_proces_trace_id = LLDB_INVALID_UID;
   TraceOptions m_pt_process_trace_config;
+
+  struct CloneInfo {
+    uint32_t 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;
+
+  // Software breakpoints saved for the duration of vfork().
+  llvm::SmallVector<std::pair<lldb::addr_t, size_t>, 8> m_saved_breakpoints;
+
+  // 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
Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
===================================================================
--- lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
+++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
@@ -383,14 +383,22 @@
   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,11 +452,7 @@
     LLDB_LOG(log, "tid {0}, si_code: {1}, si_pid: {2}", pid, info.si_code,
              info.si_pid);
 
-    NativeThreadLinux &thread = AddThread(pid);
-
-    // Resume the newly created thread.
-    ResumeThread(thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER);
-    ThreadWasCreated(thread);
+    MonitorClone(pid, llvm::None);
     return;
   }
 
@@ -512,29 +516,24 @@
   }
 }
 
-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.
@@ -542,18 +541,15 @@
   }
   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);
-
-  ResumeThread(new_thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER);
-  ThreadWasCreated(new_thread);
+  MonitorClone(pid, llvm::None);
 }
 
 void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
@@ -580,10 +576,12 @@
                "pid {0} received thread creation event but "
                "GetEventMessage failed so we don't know the new tid",
                thread.GetID());
-    } else
-      WaitForNewThread(event_message);
+      ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER);
+    } else {
+      if (!MonitorClone(event_message, {{PTRACE_EVENT_CLONE, thread.GetID()}}))
+        WaitForCloneNotification(event_message);
+    }
 
-    ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER);
     break;
   }
 
@@ -649,6 +647,38 @@
     break;
   }
 
+  case (SIGTRAP | (PTRACE_EVENT_FORK << 8)): {
+    unsigned long data = 0;
+    if (GetEventMessage(thread.GetID(), &data).Fail())
+      data = 0;
+
+    if (!MonitorClone(data, {{PTRACE_EVENT_FORK, thread.GetID()}}))
+      WaitForCloneNotification(data);
+    break;
+  }
+  case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): {
+    unsigned long data = 0;
+    if (GetEventMessage(thread.GetID(), &data).Fail())
+      data = 0;
+
+    if (!MonitorClone(data, {{PTRACE_EVENT_VFORK, thread.GetID()}}))
+      WaitForCloneNotification(data);
+    break;
+  }
+  case (SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8)): {
+    // restore breakpoints
+    llvm::Error error = ReaddSoftwareBreakpoints(m_saved_breakpoints);
+    if (error) {
+      LLDB_LOG_ERROR(log, std::move(error),
+                     "failed to restore breakpoints after vfork: {0}");
+      SetState(StateType::eStateInvalid);
+      return;
+    }
+    m_saved_breakpoints.clear();
+    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
@@ -858,6 +888,92 @@
   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;
+  }
+
+  // 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: {
+    NativeThreadLinux &child_thread = AddThread(child_pid);
+    // 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;
+  }
+  case PTRACE_EVENT_FORK: {
+    MainLoop unused_loop;
+    NativeProcessLinux child_process{child_pid, m_terminal_fd, *m_delegates[0],
+                                     m_arch,    unused_loop,   {child_pid}};
+    child_process.m_software_breakpoints = m_software_breakpoints;
+    auto bp = child_process.RemoveAllSoftwareBreakpoints();
+    if (!bp) {
+      LLDB_LOG_ERROR(log, std::move(bp.takeError()),
+                     "failed to remove breakpoints from forked process {1}: {0}",
+                     child_pid);
+      SetState(StateType::eStateInvalid);
+      return true;
+    }
+    child_process.Detach();
+    ResumeThread(*parent_thread, parent_thread->GetState(),
+                 LLDB_INVALID_SIGNAL_NUMBER);
+    break;
+  }
+  case PTRACE_EVENT_VFORK: {
+    MainLoop unused_loop;
+    NativeProcessLinux child_process{child_pid, m_terminal_fd, *m_delegates[0],
+                                     m_arch,    unused_loop,   {child_pid}};
+    // vfork shares memory with the parent, so we need to temporarily remove
+    // breakpoints
+    auto bp = RemoveAllSoftwareBreakpoints();
+    if (!bp) {
+      LLDB_LOG_ERROR(log, std::move(bp.takeError()),
+                     "failed to remove breakpoints from vforked process {1}: {0}",
+                     child_pid);
+      SetState(StateType::eStateInvalid);
+      return true;
+    }
+    m_saved_breakpoints.append(bp.get());
+    child_process.Detach();
+    ResumeThread(*parent_thread, parent_thread->GetState(),
+                 LLDB_INVALID_SIGNAL_NUMBER);
+    break;
+  }
+  default:
+    assert(false && "unknown clone_info.event");
+  }
+
+  m_pending_pid_map.erase(child_pid);
+  return true;
+}
+
 bool NativeProcessLinux::SupportHardwareSingleStepping() const {
   if (m_arch.GetMachine() == llvm::Triple::arm || m_arch.IsMIPS())
     return false;
Index: lldb/source/Host/common/NativeProcessProtocol.cpp
===================================================================
--- lldb/source/Host/common/NativeProcessProtocol.cpp
+++ lldb/source/Host/common/NativeProcessProtocol.cpp
@@ -642,6 +642,30 @@
     return RemoveSoftwareBreakpoint(addr);
 }
 
+llvm::Expected<llvm::SmallVector<std::pair<lldb::addr_t, size_t>, 8>>
+NativeProcessProtocol::RemoveAllSoftwareBreakpoints() {
+  llvm::SmallVector<std::pair<lldb::addr_t, size_t>, 8> breakpoints;
+
+  while (!m_software_breakpoints.empty()) {
+    auto& bp = *m_software_breakpoints.begin();
+    breakpoints.emplace_back(bp.first, bp.second.saved_opcodes.size());
+    Status ret = RemoveSoftwareBreakpoint(bp.first);
+    if (ret.Fail())
+      return ret.ToError();
+  }
+
+  return breakpoints;
+}
+
+llvm::Error NativeProcessProtocol::ReaddSoftwareBreakpoints(llvm::ArrayRef<std::pair<lldb::addr_t, size_t>> breakpoints) {
+  for (auto& x : breakpoints) {
+    Status ret = SetSoftwareBreakpoint(x.first, x.second);
+    if (ret.Fail())
+      return ret.ToError();
+  }
+  return llvm::Error::success();
+}
+
 Status NativeProcessProtocol::ReadMemoryWithoutTrap(lldb::addr_t addr,
                                                     void *buf, size_t size,
                                                     size_t &bytes_read) {
Index: lldb/include/lldb/Host/common/NativeProcessProtocol.h
===================================================================
--- lldb/include/lldb/Host/common/NativeProcessProtocol.h
+++ lldb/include/lldb/Host/common/NativeProcessProtocol.h
@@ -142,6 +142,14 @@
 
   virtual Status RemoveBreakpoint(lldb::addr_t addr, bool hardware = false);
 
+  // Remove all software breakpoints and return a vector of breakpoint data
+  // that can be used to readd them.
+  virtual llvm::Expected<llvm::SmallVector<std::pair<lldb::addr_t, size_t>, 8>>
+  RemoveAllSoftwareBreakpoints();
+
+  virtual llvm::Error ReaddSoftwareBreakpoints(
+      llvm::ArrayRef<std::pair<lldb::addr_t, size_t>> breakpoints);
+
   // Hardware Breakpoint functions
   virtual const HardwareBreakpointMap &GetHardwareBreakpointMap() const;
 
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to