From 4a2a947317bf702178bf1af34dffd0d280d49970 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 13 Apr 2021 00:15:51 +0200 Subject: [PATCH] [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 --- lldb/include/lldb/Target/Process.h | 1 + lldb/include/lldb/lldb-private-enumerations.h | 6 + .../Process/gdb-remote/ProcessGDBRemote.cpp | 125 ++++++++++++++++++--- .../Plugins/Process/gdb-remote/ProcessGDBRemote.h | 2 + lldb/source/Target/Process.cpp | 19 ++++ lldb/source/Target/TargetProperties.td | 4 + .../Subprocess/clone-follow-child-softbp.test | 13 +++ .../Shell/Subprocess/clone-follow-child-wp.test | 15 +++ lldb/test/Shell/Subprocess/clone-follow-child.test | 10 ++ .../Shell/Subprocess/fork-follow-child-softbp.test | 12 ++ .../Shell/Subprocess/fork-follow-child-wp.test | 14 +++ lldb/test/Shell/Subprocess/fork-follow-child.test | 9 ++ .../Subprocess/fork-follow-parent-softbp.test | 1 + .../Subprocess/vfork-follow-child-softbp.test | 10 ++ .../Shell/Subprocess/vfork-follow-child-wp.test | 11 ++ lldb/test/Shell/Subprocess/vfork-follow-child.test | 9 ++ 16 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 lldb/test/Shell/Subprocess/clone-follow-child-softbp.test create mode 100644 lldb/test/Shell/Subprocess/clone-follow-child-wp.test create mode 100644 lldb/test/Shell/Subprocess/clone-follow-child.test create mode 100644 lldb/test/Shell/Subprocess/fork-follow-child-softbp.test create mode 100644 lldb/test/Shell/Subprocess/fork-follow-child-wp.test create mode 100644 lldb/test/Shell/Subprocess/fork-follow-child.test create mode 100644 lldb/test/Shell/Subprocess/vfork-follow-child-softbp.test create mode 100644 lldb/test/Shell/Subprocess/vfork-follow-child-wp.test create mode 100644 lldb/test/Shell/Subprocess/vfork-follow-child.test diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index b1f1d90..a11c8cb 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -99,6 +99,7 @@ public: 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 7009d1b..9bbb889 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 0ec972e..71177a0 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() : ""); 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 ae5fce1..69c2233 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -233,6 +233,7 @@ public: 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 @@ private: // 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 267c3ee..5ae37d06 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -110,6 +110,19 @@ public: } }; +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 8f627ad..23f18fcf 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 0000000..f4fe858 --- /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 0000000..b56cc35 --- /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 0000000..0e00e41 --- /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 0000000..6a9254d --- /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 0000000..6f3b67e --- /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 0000000..a1df300 --- /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 658d63c..4dfcb52 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 0000000..3de6941 --- /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 0000000..15fa0c5 --- /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 0000000..6b64032 --- /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 -- 2.7.4