Index: lldb/include/lldb/Host/common/NativeProcessProtocol.h =================================================================== --- lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -63,7 +63,7 @@ virtual Status Halt() = 0; - virtual Status Detach() = 0; + virtual Status Detach(bool keep_stopped = false) = 0; /// Sends a process a UNIX signal \a signal. /// @@ -267,8 +267,9 @@ memory_tagging = (1u << 6), savecore = (1u << 7), siginfo_read = (1u << 8), + keep_stopped = (1u << 9), - LLVM_MARK_AS_BITMASK_ENUM(siginfo_read) + LLVM_MARK_AS_BITMASK_ENUM(keep_stopped) }; class Factory { Index: lldb/include/lldb/Target/Process.h =================================================================== --- lldb/include/lldb/Target/Process.h +++ lldb/include/lldb/Target/Process.h @@ -94,6 +94,8 @@ bool GetWarningsOptimization() const; bool GetWarningsUnsupportedLanguage() const; bool GetStopOnExec() const; + bool GetStopOnCloneEvents() const; + void SetStopOnCloneEvents(bool stop); std::chrono::seconds GetUtilityExpressionTimeout() const; std::chrono::seconds GetInterruptTimeout() const; bool GetOSPluginReportsAllThreads() const; Index: lldb/include/lldb/Utility/StringExtractorGDBRemote.h =================================================================== --- lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -180,6 +180,7 @@ eServerPacketType_vStopped, eServerPacketType_vCtrlC, eServerPacketType_vStdio, + eServerPacketType_qSupportsDetachAndStayStopped, }; ServerPacketType GetServerPacketType() const; Index: lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h +++ lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h @@ -48,7 +48,7 @@ Status Halt() override; - Status Detach() override; + Status Detach(bool keep_stopped = false) override; Status Signal(int signo) override; Index: lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp +++ lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp @@ -506,7 +506,7 @@ return error; } -Status NativeProcessFreeBSD::Detach() { +Status NativeProcessFreeBSD::Detach(bool keep_stopped) { Status error; // Stop monitoring the inferior. Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -58,7 +58,7 @@ Status Halt() override; - Status Detach() override; + Status Detach(bool keep_stopped = false) override; Status Signal(int signo) override; @@ -220,7 +220,7 @@ void NotifyThreadDeath(lldb::tid_t tid); - Status Detach(lldb::tid_t tid); + Status Detach(lldb::tid_t tid, bool keep_stopped = false); // This method is requests a stop on all threads which are still running. It // sets up a Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -287,7 +287,7 @@ NativeProcessLinux::Extension supported = Extension::multiprocess | Extension::fork | Extension::vfork | Extension::pass_signals | Extension::auxv | Extension::libraries_svr4 | - Extension::siginfo_read; + Extension::siginfo_read | Extension::keep_stopped; #ifdef __aarch64__ // At this point we do not have a process so read auxv directly. @@ -969,7 +969,7 @@ return error; } -Status NativeProcessLinux::Detach() { +Status NativeProcessLinux::Detach(bool keep_stopped) { Status error; // Stop monitoring the inferior. @@ -980,7 +980,7 @@ return error; for (const auto &thread : m_threads) { - Status e = Detach(thread->GetID()); + Status e = Detach(thread->GetID(), keep_stopped); if (e.Fail()) error = e; // Save the error, but still attempt to detach from other threads. @@ -1633,13 +1633,19 @@ return PtraceWrapper(PTRACE_GETEVENTMSG, tid, nullptr, message); } -Status NativeProcessLinux::Detach(lldb::tid_t tid) { +Status NativeProcessLinux::Detach(lldb::tid_t tid, bool keep_stopped) { if (tid == LLDB_INVALID_THREAD_ID) return Status(); - - return PtraceWrapper(PTRACE_DETACH, tid); + if (keep_stopped) { + intptr_t data = SIGSTOP; + return PtraceWrapper(PTRACE_DETACH, tid, nullptr, + reinterpret_cast(data)); + } else { + return PtraceWrapper(PTRACE_DETACH, tid); + } } + bool NativeProcessLinux::HasThreadNoLock(lldb::tid_t thread_id) { for (const auto &thread : m_threads) { assert(thread && "thread list should not contain NULL threads"); Index: lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h =================================================================== --- lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h +++ lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h @@ -45,7 +45,7 @@ Status Halt() override; - Status Detach() override; + Status Detach(bool keep_stopped = false) override; Status Signal(int signo) override; Index: lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp =================================================================== --- lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp +++ lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp @@ -535,7 +535,7 @@ Status NativeProcessNetBSD::Halt() { return PtraceWrapper(PT_STOP, GetID()); } -Status NativeProcessNetBSD::Detach() { +Status NativeProcessNetBSD::Detach(bool keep_stopped) { Status error; // Stop monitoring the inferior. Index: lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h =================================================================== --- lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -46,7 +46,7 @@ Status Halt() override; - Status Detach() override; + Status Detach(bool keep_stopped = false) override; Status Signal(int signo) override; Index: lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp =================================================================== --- lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -166,7 +166,7 @@ return Status(); } -Status NativeProcessWindows::Detach() { +Status NativeProcessWindows::Detach(bool keep_stopped) { Status error; Log *log = GetLog(WindowsLog::Process); StateType state = GetState(); Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -247,6 +247,8 @@ PacketResult Handle_qSaveCore(StringExtractorGDBRemote &packet); + PacketResult Handle_qSupportsDetachAndStayStopped(StringExtractorGDBRemote &packet); + PacketResult Handle_QNonStop(StringExtractorGDBRemote &packet); PacketResult HandleNotificationAck(std::deque &queue); Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -252,6 +252,10 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_vCtrlC, &GDBRemoteCommunicationServerLLGS::Handle_vCtrlC); + + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_qSupportsDetachAndStayStopped, + &GDBRemoteCommunicationServerLLGS::Handle_qSupportsDetachAndStayStopped); } void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) { @@ -3546,12 +3550,25 @@ StopSTDIOForwarding(); lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; - - // Consume the ';' after D. + bool keep_stopped = false; + // Consume the ';' and, possibly, '1' after D. packet.SetFilePos(1); if (packet.GetBytesLeft()) { - if (packet.GetChar() != ';') - return SendIllFormedResponse(packet, "D missing expected ';'"); + const char ch = packet.GetChar(); + switch (ch) { + case '1': + { + if (packet.GetChar() != ';') + return SendIllFormedResponse(packet, "D1 missing expected ';'"); + keep_stopped = true; + break; + } + case ';': { + break; + } + default: + return SendIllFormedResponse(packet, "D missing expected ';'"); + } // Grab the PID from which we will detach (assume hex encoding). pid = packet.GetU32(LLDB_INVALID_PROCESS_ID, 16); @@ -3569,7 +3586,7 @@ LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s detaching %" PRId64, __FUNCTION__, it->first); - if (llvm::Error e = it->second.process_up->Detach().ToError()) + if (llvm::Error e = it->second.process_up->Detach(keep_stopped).ToError()) detach_error = llvm::joinErrors(std::move(detach_error), std::move(e)); else { if (it->second.process_up.get() == m_current_process) @@ -4053,6 +4070,18 @@ return SendOKResponse(); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_qSupportsDetachAndStayStopped( + StringExtractorGDBRemote &packet) { + using Extension = NativeProcessProtocol::Extension; + Extension plugin_features = m_process_factory.GetSupportedExtensions(); + if (bool(plugin_features & Extension::keep_stopped)) { + return SendOKResponse(); + } + + return SendErrorResponse(Status("Unsupported qSupportsDetachAndStayStopped")); +} + void GDBRemoteCommunicationServerLLGS::MaybeCloseInferiorTerminalConnection() { Log *log = GetLog(LLDBLog::Process); 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 @@ -5231,8 +5231,9 @@ return; } - LLDB_LOG(log, "Detaching process {0}", detach_pid); - Status error = m_gdb_comm.Detach(false, detach_pid); + bool keep_stop_on_detach = GetDetachKeepsStopped(); + LLDB_LOG(log, "Detaching process {0} (keep_stopped={1})", detach_pid, keep_stop_on_detach); + Status error = m_gdb_comm.Detach(keep_stop_on_detach, detach_pid); if (error.Fail()) { LLDB_LOG(log, "ProcessGDBRemote::DidFork() detach packet send failed: {0}", error.AsCString() ? error.AsCString() : ""); Index: lldb/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -305,6 +305,17 @@ nullptr, idx, g_process_properties[idx].default_uint_value != 0); } +bool ProcessProperties::GetStopOnCloneEvents() const { + const uint32_t idx = ePropertyStopOnCloneEvents; + return m_collection_sp->GetPropertyAtIndexAsBoolean( + nullptr, idx, g_process_properties[idx].default_uint_value != 0); +} + +void ProcessProperties::SetStopOnCloneEvents(bool stop) { + const uint32_t idx = ePropertyStopOnCloneEvents; + m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, stop); +} + std::chrono::seconds ProcessProperties::GetUtilityExpressionTimeout() const { const uint32_t idx = ePropertyUtilityExpressionTimeout; uint64_t value = m_collection_sp->GetPropertyAtIndexAsUInt64( Index: lldb/source/Target/StopInfo.cpp =================================================================== --- lldb/source/Target/StopInfo.cpp +++ lldb/source/Target/StopInfo.cpp @@ -1275,7 +1275,12 @@ ~StopInfoFork() override = default; - bool ShouldStop(Event *event_ptr) override { return false; } + bool ShouldStop(Event *event_ptr) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + return thread_sp->GetProcess()->GetStopOnCloneEvents(); + return false; + } StopReason GetStopReason() const override { return eStopReasonFork; } Index: lldb/source/Target/TargetProperties.td =================================================================== --- lldb/source/Target/TargetProperties.td +++ lldb/source/Target/TargetProperties.td @@ -250,6 +250,10 @@ DefaultEnumValue<"eFollowParent">, EnumValues<"OptionEnumValues(g_follow_fork_mode_values)">, Desc<"Debugger's behavior upon fork or vfork.">; + def StopOnCloneEvents: Property<"stop-on-clone-events", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, stop after a new child is spawned and the process to keep debugged selected (see 'follow-fork-mode').">; } let Definition = "platform" in { Index: lldb/source/Utility/StringExtractorGDBRemote.cpp =================================================================== --- lldb/source/Utility/StringExtractorGDBRemote.cpp +++ lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -277,6 +277,8 @@ return eServerPacketType_qStepPacketSupported; if (PACKET_STARTS_WITH("qSupported")) return eServerPacketType_qSupported; + if (PACKET_STARTS_WITH("qSupportsDetachAndStayStopped")) + return eServerPacketType_qSupportsDetachAndStayStopped; if (PACKET_MATCHES("qSyncThreadStateSupported")) return eServerPacketType_qSyncThreadStateSupported; break; Index: lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp =================================================================== --- lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -591,3 +591,23 @@ std::vector{0x99}, "QMemTags:456789,0:80000000:99", "E03", false); } + + +TEST_F(GDBRemoteCommunicationClientTest, DetachAndKeepStopped) { + llvm::Triple triple("i386-pc-linux"); + const lldb::tid_t tid = 0x18d915; + std::future async_result = std::async( + std::launch::async, [&] { return client.Detach(true, tid); }); + HandlePacket(server, "qSupportsDetachAndStayStopped:", "OK"); + HandlePacket(server, "D1;000000000018d915", "OK"); + EXPECT_TRUE(async_result.get().Success()); +} + +TEST_F(GDBRemoteCommunicationClientTest, DetachAndContinue) { + llvm::Triple triple("i386-pc-linux"); + const lldb::tid_t tid = 0x18d915; + std::future async_result = std::async( + std::launch::async, [&] { return client.Detach(false, tid); }); + HandlePacket(server, "D;000000000018d915", "OK"); + EXPECT_TRUE(async_result.get().Success()); +} Index: lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h =================================================================== --- lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h +++ lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h @@ -47,7 +47,7 @@ MOCK_METHOD1(Resume, Status(const ResumeActionList &ResumeActions)); MOCK_METHOD0(Halt, Status()); - MOCK_METHOD0(Detach, Status()); + MOCK_METHOD1(Detach, Status(bool KeepStopped)); MOCK_METHOD1(Signal, Status(int Signo)); MOCK_METHOD0(Kill, Status()); MOCK_METHOD0(GetSharedLibraryInfoAddress, addr_t());