Index: lldb/include/lldb/Host/common/NativeProcessProtocol.h =================================================================== --- lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -52,7 +52,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. /// @@ -252,8 +252,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 @@ -174,6 +174,7 @@ eServerPacketType_QMemTags, // write memory tags eServerPacketType_qLLDBSaveCore, + 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 @@ -502,7 +502,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; @@ -216,7 +216,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 @@ -286,7 +286,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. @@ -962,7 +962,7 @@ return error; } -Status NativeProcessLinux::Detach() { +Status NativeProcessLinux::Detach(bool keep_stopped) { Status error; // Stop monitoring the inferior. @@ -973,7 +973,9 @@ return error; for (const auto &thread : m_threads) { - Status e = Detach(thread->GetID()); + Status e = Detach(thread->GetID(), keep_stopped); + Log *log = GetLog(POSIXLog::Process); + LLDB_LOG(log, "Detaching from pid {0} returned {1}", thread->GetID(), e); if (e.Fail()) error = e; // Save the error, but still attempt to detach from other threads. @@ -1625,11 +1627,16 @@ 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) { 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) 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 @@ -217,6 +217,8 @@ PacketResult Handle_qSaveCore(StringExtractorGDBRemote &packet); + PacketResult Handle_qSupportsDetachAndStayStopped(StringExtractorGDBRemote &packet); + PacketResult Handle_g(StringExtractorGDBRemote &packet); PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet); 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 @@ -235,6 +235,10 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_qLLDBSaveCore, &GDBRemoteCommunicationServerLLGS::Handle_qSaveCore); + + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_qSupportsDetachAndStayStopped, + &GDBRemoteCommunicationServerLLGS::Handle_qSupportsDetachAndStayStopped); } void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) { @@ -3395,12 +3399,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); @@ -3415,7 +3432,7 @@ for (auto it = m_debugged_processes.begin(); it != m_debugged_processes.end();) { if (pid == LLDB_INVALID_PROCESS_ID || pid == it->first) { - if (llvm::Error e = it->second->Detach().ToError()) + if (llvm::Error e = it->second->Detach(keep_stopped).ToError()) detach_error = llvm::joinErrors(std::move(detach_error), std::move(e)); else { if (it->second.get() == m_current_process) @@ -3774,6 +3791,18 @@ return SendPacketNoLock(response.GetString()); } +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(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_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 @@ -5392,8 +5392,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 @@ -304,6 +304,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( @@ -3975,7 +3986,7 @@ still_should_stop = this_thread_wants_to_stop; } } - + return still_should_stop; } Index: lldb/source/Target/StopInfo.cpp =================================================================== --- lldb/source/Target/StopInfo.cpp +++ lldb/source/Target/StopInfo.cpp @@ -1164,7 +1164,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 @@ -243,6 +243,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 @@ -270,6 +270,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/test/API/linux/clone-events-notification/Makefile =================================================================== --- /dev/null +++ lldb/test/API/linux/clone-events-notification/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules \ No newline at end of file Index: lldb/test/API/linux/clone-events-notification/TestCloneEventSentAndChildStopped.py =================================================================== --- /dev/null +++ lldb/test/API/linux/clone-events-notification/TestCloneEventSentAndChildStopped.py @@ -0,0 +1,50 @@ +""" +Test the keep_stopped setting and possibility to attach to the client event. +""" +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestProcessKeepStoppedOnDetach(TestBase): + mydir = TestBase.compute_mydir(__file__) + + @no_debug_info_test # Prevent the generation of the dwarf version of this test + @add_test_categories(["pyapi"]) + @skipUnlessPlatform(["linux"]) + def test_keep_stopped(self): + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + self.runCmd("settings set target.process.stop-on-clone-events true") + self.runCmd("settings set target.process.detach-keeps-stopped true") + + self.runCmd("run", RUN_SUCCEEDED) + process = self.dbg.GetSelectedTarget().process + self.assertEqual( + process.GetState(), + lldb.eStateStopped, + PROCESS_STOPPED) + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonFork) + self.assertIsNotNone( + thread, "Expected one thread to be stopped at the fork") + + pid = thread.GetStopReasonDataAtIndex(0) + status = self.procStatus(pid) + self.assertEqual(status, 'T', + "Expected child to be stopped, but had '%s' Status" % status) + + process.Continue() + + self.assertEqual(process.GetState(), lldb.eStateExited, PROCESS_EXITED) + # Expected exitStatus to equal 83 (0b001`010`011) which means + # WIFSTOPPED, followed by WIFCONTINUED, and then by WIFEXITED. + self.assertEqual(process.GetExitStatus(), 0b001010011) + + def procStatus(self, pid): + for line in open("/proc/%d/status" % pid).readlines(): + if line.startswith("State:"): + return line.split(":",1)[1].strip().split(' ')[0] + return None \ No newline at end of file Index: lldb/test/API/linux/clone-events-notification/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/linux/clone-events-notification/main.cpp @@ -0,0 +1,90 @@ +/* This sample is a modified version of the example in +https://linux.die.net/man/2/waitpid. The expected output +when running in the shell is: +(lldb) settings set target.process.stop-on-clone-events true +(lldb) settings set target.process.detach-keeps-stopped true +(lldb) file a.out +Current executable set to 'a.out' (x86_64). +(lldb) run +Process launched: 'a.out' (x86_64) +Process stopped +* thread #1, name = 'a.out', stop reason = fork + frame #0: 0x00007ffff7e9bb85 libc.so.6`__libc_fork at arch-fork.h:49:9 +(lldb) c +Process resuming +Process stopped and restarted: thread 1 received signal: SIGCHLD +stopped by signal 19 +continued +Child PID is +Process stopped and restarted: thread 1 received signal: SIGCHLD +Process stopped and restarted: thread 1 received signal: SIGCHLD +exited, status=0 +Process exited with status = 83 (0x00000053) +(lldb) quit */ + +#include +#include +#include +#include + +/* Function to record both flags (up to 3 bits, possible + values at the moment are in the range [1, 4]) and the + order in which they occurred. Left most 3 bits are the + first seen flag, second triad is the second flag, etc. + + `update_seen(0, 4)` changes current_value to 0b100 + `update_seen(3, 1)` changes current_value to 0b011`001 + `update_seen(83, 2)` changes current_value to 0b001`010`011`010 +*/ +void +update_seen(long long& current_value, long long flag) { + if (current_value == 0) + current_value = flag; + else + current_value = (current_value << 3) | flag; +} + +int +main(int argc, char *argv[]) +{ + pid_t cpid, w; + int status; + long long seen_statuses = 0; + cpid = fork(); + + if (cpid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + if (cpid == 0) { /* Code executed by child */ + printf("Child PID is %ld\n", (long) getpid()); + exit(EXIT_SUCCESS); + } else { /* Code executed by parent */ + do { + w = waitpid(cpid, &status, WUNTRACED | WCONTINUED); + if (w == -1) { + perror("waitpid"); + exit(EXIT_FAILURE); + } + // Record signals seen by parent. + if (WIFSTOPPED(status)) { + update_seen(seen_statuses, 0b001); + printf("stopped by signal %d\n", WSTOPSIG(status)); + // Send SIGCONT to the child. + kill(cpid, SIGCONT); + } else if (WIFCONTINUED(status)) { + update_seen(seen_statuses, 0b010); + printf("continued\n"); + } else if (WIFEXITED(status)) { + update_seen(seen_statuses, 0b011); + printf("exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + update_seen(seen_statuses, 0b100); + printf("killed by signal %d\n", WTERMSIG(status)); + } + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + // Expected seen_statuses to be 83 (0b001`010`011) which is + // WIFSTOPPED, followed by WIFCONTINUED, and by WIFEXITED. + exit(seen_statuses); + } +} \ No newline at end of file Index: lldb/test/Shell/Subprocess/clone-stops-on-fork =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-stops-on-fork @@ -0,0 +1,10 @@ +# REQUIRES: native && system-linux +# RUN: %clangxx_host -g %p/Inputs/fork.cpp -DTEST_CLONE -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +settings set target.process.stop-on-clone-events true +process launch -s +continue +# CHECK: stop reason = fork +continue +# CHECK: function run in parent +# CHECK: function run in exec'd child \ No newline at end of file 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,22 @@ 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());