[Lldb-commits] [lldb] 4a2a947 - [lldb] [client] Implement follow-fork-mode

Michał Górny via lldb-commits lldb-commits at lists.llvm.org
Thu Sep 2 03:17:30 PDT 2021


Author: Michał Górny
Date: 2021-09-02T12:16:58+02:00
New Revision: 4a2a947317bf702178bf1af34dffd0d280d49970

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

LOG: [lldb] [client] Implement follow-fork-mode

Implement a new target.process.follow-fork-mode setting to control
LLDB's behavior on fork.  If set to 'parent', the forked child is
detached and parent continues being traced.  If set to 'child',
the parent is detached and child becomes traced instead.

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

Added: 
    lldb/test/Shell/Subprocess/clone-follow-child-softbp.test
    lldb/test/Shell/Subprocess/clone-follow-child-wp.test
    lldb/test/Shell/Subprocess/clone-follow-child.test
    lldb/test/Shell/Subprocess/fork-follow-child-softbp.test
    lldb/test/Shell/Subprocess/fork-follow-child-wp.test
    lldb/test/Shell/Subprocess/fork-follow-child.test
    lldb/test/Shell/Subprocess/vfork-follow-child-softbp.test
    lldb/test/Shell/Subprocess/vfork-follow-child-wp.test
    lldb/test/Shell/Subprocess/vfork-follow-child.test

Modified: 
    lldb/include/lldb/Target/Process.h
    lldb/include/lldb/lldb-private-enumerations.h
    lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
    lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
    lldb/source/Target/Process.cpp
    lldb/source/Target/TargetProperties.td
    lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index b1f1d908c3581..a11c8cbf87ec9 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -99,6 +99,7 @@ class ProcessProperties : public Properties {
   bool GetOSPluginReportsAllThreads() const;
   void SetOSPluginReportsAllThreads(bool does_report);
   bool GetSteppingRunsAllThreads() const;
+  FollowForkMode GetFollowForkMode() const;
 
 protected:
   Process *m_process; // Can be nullptr for global ProcessProperties

diff  --git a/lldb/include/lldb/lldb-private-enumerations.h b/lldb/include/lldb/lldb-private-enumerations.h
index 7009d1b4fba78..9bbb889359b12 100644
--- a/lldb/include/lldb/lldb-private-enumerations.h
+++ b/lldb/include/lldb/lldb-private-enumerations.h
@@ -172,6 +172,12 @@ enum MemoryModuleLoadLevel {
   eMemoryModuleLoadLevelComplete, // Load sections and all symbols
 };
 
+// Behavior on fork/vfork
+enum FollowForkMode {
+  eFollowParent, // Follow parent process
+  eFollowChild,  // Follow child process
+};
+
 // Result enums for when reading multiple lines from IOHandlers
 enum class LineStatus {
   Success, // The line that was just edited if good and should be added to the

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 0ec972ec37d03..71177a0811b07 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -5498,6 +5498,31 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
   });
 }
 
+void ProcessGDBRemote::DidForkSwitchHardwareTraps(bool enable) {
+  if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
+    GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
+      if (bp_site->IsEnabled() &&
+          bp_site->GetType() == BreakpointSite::eHardware) {
+        m_gdb_comm.SendGDBStoppointTypePacket(
+            eBreakpointHardware, enable, bp_site->GetLoadAddress(),
+            GetSoftwareBreakpointTrapOpcode(bp_site), GetInterruptTimeout());
+      }
+    });
+  }
+
+  WatchpointList &wps = GetTarget().GetWatchpointList();
+  size_t wp_count = wps.GetSize();
+  for (size_t i = 0; i < wp_count; ++i) {
+    WatchpointSP wp = wps.GetByIndex(i);
+    if (wp->IsEnabled()) {
+      GDBStoppointType type = GetGDBStoppointType(wp.get());
+      m_gdb_comm.SendGDBStoppointTypePacket(type, enable, wp->GetLoadAddress(),
+                                            wp->GetByteSize(),
+                                            GetInterruptTimeout());
+    }
+  }
+}
+
 void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
   Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
 
@@ -5506,30 +5531,58 @@ void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
   // anyway.
   lldb::tid_t parent_tid = m_thread_ids.front();
 
-  if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) {
-    // Switch to the new process to clear breakpoints there.
-    if (!m_gdb_comm.SetCurrentThread(child_tid, child_pid)) {
-      LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
-      return;
-    }
+  lldb::pid_t follow_pid, detach_pid;
+  lldb::tid_t follow_tid, detach_tid;
+
+  switch (GetFollowForkMode()) {
+  case eFollowParent:
+    follow_pid = parent_pid;
+    follow_tid = parent_tid;
+    detach_pid = child_pid;
+    detach_tid = child_tid;
+    break;
+  case eFollowChild:
+    follow_pid = child_pid;
+    follow_tid = child_tid;
+    detach_pid = parent_pid;
+    detach_tid = parent_tid;
+    break;
+  }
 
-    // Disable all software breakpoints in the forked process.
+  // Switch to the process that is going to be detached.
+  if (!m_gdb_comm.SetCurrentThread(detach_tid, detach_pid)) {
+    LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
+    return;
+  }
+
+  // Disable all software breakpoints in the forked process.
+  if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
     DidForkSwitchSoftwareBreakpoints(false);
 
-    // Reset gdb-remote to the original process.
-    if (!m_gdb_comm.SetCurrentThread(parent_tid, parent_pid)) {
-      LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
-      return;
-    }
+  // Remove hardware breakpoints / watchpoints from parent process if we're
+  // following child.
+  if (GetFollowForkMode() == eFollowChild)
+    DidForkSwitchHardwareTraps(false);
+
+  // Switch to the process that is going to be followed
+  if (!m_gdb_comm.SetCurrentThread(follow_tid, follow_pid) ||
+      !m_gdb_comm.SetCurrentThreadForRun(follow_tid, follow_pid)) {
+    LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
+    return;
   }
 
-  LLDB_LOG(log, "Detaching forked child {0}", child_pid);
-  Status error = m_gdb_comm.Detach(false, child_pid);
+  LLDB_LOG(log, "Detaching process {0}", detach_pid);
+  Status error = m_gdb_comm.Detach(false, detach_pid);
   if (error.Fail()) {
     LLDB_LOG(log, "ProcessGDBRemote::DidFork() detach packet send failed: {0}",
              error.AsCString() ? error.AsCString() : "<unknown error>");
     return;
   }
+
+  // Hardware breakpoints/watchpoints are not inherited implicitly,
+  // so we need to readd them if we're following child.
+  if (GetFollowForkMode() == eFollowChild)
+    DidForkSwitchHardwareTraps(true);
 }
 
 void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
@@ -5542,8 +5595,40 @@ void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
   if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
     DidForkSwitchSoftwareBreakpoints(false);
 
-  LLDB_LOG(log, "Detaching forked child {0}", child_pid);
-  Status error = m_gdb_comm.Detach(false, child_pid);
+  lldb::pid_t detach_pid;
+  lldb::tid_t detach_tid;
+
+  switch (GetFollowForkMode()) {
+  case eFollowParent:
+    detach_pid = child_pid;
+    detach_tid = child_tid;
+    break;
+  case eFollowChild:
+    detach_pid = m_gdb_comm.GetCurrentProcessID();
+    // Any valid TID will suffice, thread-relevant actions will set a proper TID
+    // anyway.
+    detach_tid = m_thread_ids.front();
+
+    // Switch to the parent process before detaching it.
+    if (!m_gdb_comm.SetCurrentThread(detach_tid, detach_pid)) {
+      LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
+      return;
+    }
+
+    // Remove hardware breakpoints / watchpoints from the parent process.
+    DidForkSwitchHardwareTraps(false);
+
+    // Switch to the child process.
+    if (!m_gdb_comm.SetCurrentThread(child_tid, child_pid) ||
+        !m_gdb_comm.SetCurrentThreadForRun(child_tid, child_pid)) {
+      LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
+      return;
+    }
+    break;
+  }
+
+  LLDB_LOG(log, "Detaching process {0}", detach_pid);
+  Status error = m_gdb_comm.Detach(false, detach_pid);
   if (error.Fail()) {
       LLDB_LOG(log,
                "ProcessGDBRemote::DidFork() detach packet send failed: {0}",
@@ -5560,3 +5645,11 @@ void ProcessGDBRemote::DidVForkDone() {
   if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
     DidForkSwitchSoftwareBreakpoints(true);
 }
+
+void ProcessGDBRemote::DidExec() {
+  // If we are following children, vfork is finished by exec (rather than
+  // vforkdone that is submitted for parent).
+  if (GetFollowForkMode() == eFollowChild)
+    m_vfork_in_progress = false;
+  Process::DidExec();
+}

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index ae5fce1c8370b..69c2233f40e74 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -233,6 +233,7 @@ class ProcessGDBRemote : public Process,
   void DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override;
   void DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override;
   void DidVForkDone() override;
+  void DidExec() override;
 
 protected:
   friend class ThreadGDBRemote;
@@ -468,6 +469,7 @@ class ProcessGDBRemote : public Process,
 
   // fork helpers
   void DidForkSwitchSoftwareBreakpoints(bool enable);
+  void DidForkSwitchHardwareTraps(bool enable);
 };
 
 } // namespace process_gdb_remote

diff  --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 267c3ee5d0e90..5ae37d062581d 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -110,6 +110,19 @@ class ProcessOptionValueProperties
   }
 };
 
+static constexpr OptionEnumValueElement g_follow_fork_mode_values[] = {
+    {
+        eFollowParent,
+        "parent",
+        "Continue tracing the parent process and detach the child.",
+    },
+    {
+        eFollowChild,
+        "child",
+        "Trace the child process and detach the parent.",
+    },
+};
+
 #define LLDB_PROPERTIES_process
 #include "TargetProperties.inc"
 
@@ -334,6 +347,12 @@ void ProcessProperties::SetOSPluginReportsAllThreads(bool does_report) {
         nullptr, ePropertyOSPluginReportsAllThreads, does_report);
 }
 
+FollowForkMode ProcessProperties::GetFollowForkMode() const {
+  const uint32_t idx = ePropertyFollowForkMode;
+  return (FollowForkMode)m_collection_sp->GetPropertyAtIndexAsEnumeration(
+      nullptr, idx, g_process_properties[idx].default_uint_value);
+}
+
 ProcessSP Process::FindPlugin(lldb::TargetSP target_sp,
                               llvm::StringRef plugin_name,
                               ListenerSP listener_sp,

diff  --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index 8f627ad0f1a86..23f18fcf0c5e4 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -239,6 +239,10 @@ let Definition = "process" in {
   def VirtualAddressableBits: Property<"virtual-addressable-bits", "UInt64">,
     DefaultUnsignedValue<0>,
     Desc<"The number of bits used for addressing. If the value is 39, then bits 0..38 are used for addressing. The default value of 0 means unspecified.">;
+  def FollowForkMode: Property<"follow-fork-mode", "Enum">,
+    DefaultEnumValue<"eFollowParent">,
+    EnumValues<"OptionEnumValues(g_follow_fork_mode_values)">,
+    Desc<"Debugger's behavior upon fork or vfork.">;
 }
 
 let Definition = "platform" in {

diff  --git a/lldb/test/Shell/Subprocess/clone-follow-child-softbp.test b/lldb/test/Shell/Subprocess/clone-follow-child-softbp.test
new file mode 100644
index 0000000000000..f4fe8588ca320
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/clone-follow-child-softbp.test
@@ -0,0 +1,13 @@
+# REQUIRES: native && system-linux
+# clone() tests fails on arm64 Linux, PR #49899
+# UNSUPPORTED: system-linux && target-aarch64
+# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_CLONE -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+settings set target.process.follow-fork-mode child
+b child_func
+b parent_func
+process launch
+# CHECK: stop reason = breakpoint
+# CHECK-NEXT: child_func
+continue
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/clone-follow-child-wp.test b/lldb/test/Shell/Subprocess/clone-follow-child-wp.test
new file mode 100644
index 0000000000000..b56cc35f2e94c
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/clone-follow-child-wp.test
@@ -0,0 +1,15 @@
+# REQUIRES: native && system-linux && dbregs-set
+# clone() tests fails on arm64 Linux, PR #49899
+# UNSUPPORTED: system-linux && target-aarch64
+# RUN: %clangxx_host -g %p/Inputs/fork.cpp -DTEST_CLONE -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+settings set target.process.follow-fork-mode child
+process launch -s
+watchpoint set variable -w write g_val
+# CHECK: Watchpoint created:
+continue
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/clone-follow-child.test b/lldb/test/Shell/Subprocess/clone-follow-child.test
new file mode 100644
index 0000000000000..0e00e41d3a138
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/clone-follow-child.test
@@ -0,0 +1,10 @@
+# REQUIRES: native && system-linux
+# clone() tests fails on arm64 Linux, PR #49899
+# UNSUPPORTED: system-linux && target-aarch64
+# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_CLONE -o %t
+# RUN: %lldb -b -s %s %t | FileCheck %s
+settings set target.process.follow-fork-mode child
+b parent_func
+process launch
+# CHECK: function run in parent
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/fork-follow-child-softbp.test b/lldb/test/Shell/Subprocess/fork-follow-child-softbp.test
new file mode 100644
index 0000000000000..6a9254d1ba8c8
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/fork-follow-child-softbp.test
@@ -0,0 +1,12 @@
+# 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.follow-fork-mode child
+b child_func
+b parent_func
+process launch
+# CHECK: stop reason = breakpoint
+# CHECK-NEXT: child_func
+continue
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/fork-follow-child-wp.test b/lldb/test/Shell/Subprocess/fork-follow-child-wp.test
new file mode 100644
index 0000000000000..6f3b67ea25dd9
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/fork-follow-child-wp.test
@@ -0,0 +1,14 @@
+# 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
+settings set target.process.follow-fork-mode child
+process launch -s
+watchpoint set variable -w write g_val
+# CHECK: Watchpoint created:
+continue
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: stop reason = watchpoint
+continue
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/fork-follow-child.test b/lldb/test/Shell/Subprocess/fork-follow-child.test
new file mode 100644
index 0000000000000..a1df30082b33b
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/fork-follow-child.test
@@ -0,0 +1,9 @@
+# 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.follow-fork-mode child
+b parent_func
+process launch
+# CHECK: function run in parent
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test b/lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test
index 658d63cd15d4e..4dfcb52a61fc7 100644
--- a/lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test
+++ b/lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test
@@ -8,6 +8,7 @@ b child_func
 process launch
 # CHECK-NOT: function run in parent
 # CHECK: stop reason = breakpoint
+# CHECK-NEXT: parent_func
 continue
 # CHECK: function run in parent
 # CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/vfork-follow-child-softbp.test b/lldb/test/Shell/Subprocess/vfork-follow-child-softbp.test
new file mode 100644
index 0000000000000..3de6941d671c4
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/vfork-follow-child-softbp.test
@@ -0,0 +1,10 @@
+# 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.follow-fork-mode child
+b child_func
+b parent_func
+process launch
+# CHECK: function run in parent
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/vfork-follow-child-wp.test b/lldb/test/Shell/Subprocess/vfork-follow-child-wp.test
new file mode 100644
index 0000000000000..15fa0c5fdd33d
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/vfork-follow-child-wp.test
@@ -0,0 +1,11 @@
+# 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
+settings set target.process.follow-fork-mode child
+process launch -s
+watchpoint set variable -w write g_val
+# CHECK: Watchpoint created:
+continue
+# CHECK: child exited: 0

diff  --git a/lldb/test/Shell/Subprocess/vfork-follow-child.test b/lldb/test/Shell/Subprocess/vfork-follow-child.test
new file mode 100644
index 0000000000000..6b6403274a11c
--- /dev/null
+++ b/lldb/test/Shell/Subprocess/vfork-follow-child.test
@@ -0,0 +1,9 @@
+# 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.follow-fork-mode child
+b parent_func
+process launch
+# CHECK: function run in parent
+# CHECK: child exited: 0


        


More information about the lldb-commits mailing list