diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index b1f1d908c35812..a11c8cbf87ec99 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 7009d1b4fba787..9bbb889359b125 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 0ec972ec37d037..71177a0811b076 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 ae5fce1c8370b3..69c2233f40e742 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 267c3ee5d0e907..5ae37d062581d2 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 8f627ad0f1a868..23f18fcf0c5e42 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 00000000000000..f4fe8588ca3208 --- /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 00000000000000..b56cc35f2e94c1 --- /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 00000000000000..0e00e41d3a138a --- /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 00000000000000..6a9254d1ba8c80 --- /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 00000000000000..6f3b67ea25dd96 --- /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 00000000000000..a1df30082b33b6 --- /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 658d63cd15d4ef..4dfcb52a61fc7f 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 00000000000000..3de6941d671c43 --- /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 00000000000000..15fa0c5fdd33d6 --- /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 00000000000000..6b6403274a11cf --- /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