Index: lldb/include/lldb/Target/Process.h =================================================================== --- lldb/include/lldb/Target/Process.h +++ lldb/include/lldb/Target/Process.h @@ -95,6 +95,7 @@ bool GetOSPluginReportsAllThreads() const; void SetOSPluginReportsAllThreads(bool does_report); bool GetSteppingRunsAllThreads() const; + FollowForkMode GetFollowForkMode() const; protected: Process *m_process; // Can be nullptr for global ProcessProperties Index: lldb/include/lldb/lldb-private-enumerations.h =================================================================== --- lldb/include/lldb/lldb-private-enumerations.h +++ lldb/include/lldb/lldb-private-enumerations.h @@ -172,6 +172,12 @@ 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 Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -233,6 +233,7 @@ 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; @@ -460,6 +461,7 @@ // fork helpers void DidForkSwitchSoftwareBreakpoints(bool enable); + void DidForkSwitchHardwareTraps(bool enable); }; } // namespace process_gdb_remote Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -5450,6 +5450,30 @@ }); } +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(), + bp_site->GetTrapOpcodeMaxByteSize()); + } + }); + } + + 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()); + } + } +} + void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) { Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); @@ -5458,30 +5482,58 @@ // 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) { @@ -5494,8 +5546,40 @@ 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}", @@ -5512,3 +5596,11 @@ 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(); +} Index: lldb/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -108,6 +108,19 @@ } }; +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" @@ -315,6 +328,12 @@ 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, Index: lldb/source/Target/TargetProperties.td =================================================================== --- lldb/source/Target/TargetProperties.td +++ lldb/source/Target/TargetProperties.td @@ -233,6 +233,10 @@ def SteppingRunsAllThreads: Property<"run-all-threads", "Boolean">, DefaultFalse, Desc<"If true, stepping operations will run all threads. This is equivalent to setting the run-mode option to 'all-threads'.">; + 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 { Index: lldb/test/Shell/Subprocess/clone-follow-child-softbp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-follow-child-softbp.test @@ -0,0 +1,13 @@ +# REQUIRES: native && (system-linux || system-netbsd) +# 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 Index: lldb/test/Shell/Subprocess/clone-follow-child-wp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-follow-child-wp.test @@ -0,0 +1,15 @@ +# REQUIRES: native && (system-linux || system-netbsd) && 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 Index: lldb/test/Shell/Subprocess/clone-follow-child.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-follow-child.test @@ -0,0 +1,10 @@ +# REQUIRES: native && (system-linux || system-netbsd) +# 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 Index: lldb/test/Shell/Subprocess/fork-follow-child-softbp.test =================================================================== --- /dev/null +++ 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 Index: lldb/test/Shell/Subprocess/fork-follow-child-wp.test =================================================================== --- /dev/null +++ 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 Index: lldb/test/Shell/Subprocess/fork-follow-child.test =================================================================== --- /dev/null +++ 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 Index: lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test =================================================================== --- lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test +++ lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test @@ -7,6 +7,7 @@ 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 Index: lldb/test/Shell/Subprocess/vfork-follow-child-softbp.test =================================================================== --- /dev/null +++ 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 Index: lldb/test/Shell/Subprocess/vfork-follow-child-wp.test =================================================================== --- /dev/null +++ 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 Index: lldb/test/Shell/Subprocess/vfork-follow-child.test =================================================================== --- /dev/null +++ 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