mgorny created this revision. mgorny added reviewers: labath, emaste, krytarowski. mgorny requested review of this revision.
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. https://reviews.llvm.org/D100503 Files: 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/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/fork-follow-parent-softbp.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
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 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-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/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/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-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-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/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/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-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/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/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/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() : "<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) { @@ -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/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/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
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits