Index: lldb/packages/Python/lldbsuite/test/gdbclientutils.py =================================================================== --- lldb/packages/Python/lldbsuite/test/gdbclientutils.py +++ lldb/packages/Python/lldbsuite/test/gdbclientutils.py @@ -23,10 +23,12 @@ Create a framed packet that's ready to send over the GDB connection channel. - Framing includes surrounding the message between $ and #, and appending - a two character hex checksum. + Framing includes adding a $ prefix (unless the message already starts + with a % prefix, in which case no additional prefix is added), + and appending # followed by a two character hex checksum. """ - return "$%s#%02x" % (message, checksum(message)) + prefix = "" if message.startswith("%") else "$" + return "%s%s#%02x" % (prefix, message, checksum(message)) def escape_binary(message): @@ -200,6 +202,14 @@ if packet.startswith("qRegisterInfo"): regnum = int(packet[len("qRegisterInfo"):], 16) return self.qRegisterInfo(regnum) + if packet.startswith("QNonStop:"): + return self.QNonStop(int(packet.split(":", 1)[1])) + if packet == "vStdio": + return self.vStdio() + if packet == "vStopped": + return self.vStopped() + if packet == "vCtrlC": + return self.vCtrlC() if packet == "k": return self.k() @@ -333,6 +343,18 @@ def qRegisterInfo(self, num): return "" + def QNonStop(self, enabled): + return "" + + def vStdio(self): + return "" + + def vStopped(self): + return "" + + def vCtrlC(self): + return "" + def k(self): return ["W01", self.RESPONSE_DISCONNECT] Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h @@ -59,6 +59,12 @@ std::chrono::seconds interrupt_timeout, llvm::function_ref output_callback); + bool GetNonStopEnabled() { return m_non_stop; } + + void SetNonStopEnabled(bool enabled) { m_non_stop = enabled; } + + bool DrainNotificationQueue(llvm::StringRef command); + class Lock { public: // If interrupt_timeout == 0 seconds, only take the lock if the target is @@ -129,6 +135,9 @@ /// Whether we should resume after a stop. bool m_should_stop; + + /// Whether non-stop protocol should be used. + bool m_non_stop = false; /// @} /// This handles the synchronization between individual async threads. For Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp @@ -63,7 +63,8 @@ std::chrono::time_point interrupt_endpoint; bool interrupt_sent = false; for (;;) { - PacketResult read_result = ReadPacket(response, computed_timeout, false); + PacketResult read_result = + ReadPacket(response, computed_timeout, false, m_non_stop); // Reset the computed_timeout to the default value in case we are going // round again. computed_timeout = std::min(interrupt_timeout, kWakeupInterval); @@ -74,15 +75,25 @@ continue; } if (!interrupt_sent) { - const char ctrl_c = '\x03'; - ConnectionStatus status = eConnectionStatusSuccess; - size_t bytes_written = Write(&ctrl_c, 1, status, nullptr); - if (bytes_written == 0) { + bool success; + if (GetNonStopEnabled()) { + // In non-stop mode, we expect an immediate "OK" response + // to the "vCtrlC" packet, then a regular stop response. + StringExtractorGDBRemote halt_response; + success = SendPacketAndWaitForResponseNoLock( + "vCtrlC", halt_response) == PacketResult::Success && + halt_response.IsOKResponse(); + } else { + const char ctrl_c = '\x03'; + ConnectionStatus status = eConnectionStatusSuccess; + success = Write(&ctrl_c, 1, status, nullptr) != 0; + } + if (!success) { LLDB_LOG(log, "failed to send interrupt packet"); return eStateInvalid; } interrupt_endpoint = steady_clock::now() + interrupt_timeout; - if (log) + if (log && !GetNonStopEnabled()) log->PutCString( "GDBRemoteClientBase::SendContinuePacketAndWaitForResponse sent " "packet: \\x03"); @@ -107,6 +118,7 @@ } break; } + case PacketResult::Notify: case PacketResult::Success: break; default: @@ -117,9 +129,43 @@ if (response.Empty()) return eStateInvalid; + LLDB_LOG(log, "got result {0}, packet: {1}", read_result, + response.GetStringRef()); + + // We do not currently support full asynchronous communication. Instead, + // when in non-stop mode, we wait for the asynchronous %Stop notification + // and then drain the notification queue + // TODO: issue vCont;t to ensure that all threads have actually stopped + // (this is not needed for LLGS but for gdbserver) + + if (read_result == PacketResult::Notify && + response.GetStringRef().startswith("Stdio:")) { + std::string inferior_stdout; + response.SetFilePos(6); + while (!response.IsOKResponse()) { + if (response.GetChar() == 'O') { + response.GetHexByteString(inferior_stdout); + delegate.HandleAsyncStdout(inferior_stdout); + } + if (SendPacketAndWaitForResponseNoLock("vStdio", response) != + PacketResult::Success || + response.IsUnsupportedResponse() || response.IsErrorResponse()) { + LLDB_LOG(log, "vStdio request failed"); + return eStateInvalid; + } + } + continue; + } + + if (read_result == PacketResult::Notify && + response.GetStringRef().startswith("Stop:")) { + response.Reset(response.GetStringRef().substr(5)); + + if (!DrainNotificationQueue("vStopped")) + return eStateInvalid; + } + const char stop_type = response.GetChar(); - LLDB_LOGF(log, "GDBRemoteClientBase::%s () got packet: %s", __FUNCTION__, - response.GetStringRef().data()); switch (stop_type) { case 'W': @@ -305,6 +351,22 @@ BroadcastEvent(eBroadcastBitRunPacketSent, nullptr); } +bool GDBRemoteClientBase::DrainNotificationQueue(llvm::StringRef command) { + Log *log = GetLog(GDBRLog::Process); + for (;;) { + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponseNoLock(command, response) != + PacketResult::Success || response.IsUnsupportedResponse()) { + LLDB_LOG(log, "{0} failed", command); + return false; + } + + if (response.IsOKResponse()) + return true; + // otherwise, loop + } +} + /////////////////////////////////////// // GDBRemoteClientBase::ContinueLock // /////////////////////////////////////// @@ -344,9 +406,20 @@ __FUNCTION__); return LockResult::Cancelled; } - if (m_comm.SendPacketNoLock(m_comm.m_continue_packet) != - PacketResult::Success) - return LockResult::Failed; + + // When in non-stop mode, all continue packets should yield an immediate "OK" + // response before we consider the action successful. + if (m_comm.m_non_stop) { + StringExtractorGDBRemote response; + if (m_comm.SendPacketAndWaitForResponseNoLock( + m_comm.m_continue_packet, response) != PacketResult::Success || + !response.IsOKResponse()) + return LockResult::Failed; + } else { + if (m_comm.SendPacketNoLock(m_comm.m_continue_packet) != + PacketResult::Success) + return LockResult::Failed; + } lldbassert(!m_comm.m_is_running); m_comm.m_is_running = true; Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -321,6 +321,8 @@ void EnableErrorStringInPacket(); + void EnableNonStop(); + bool GetQXferLibrariesReadSupported(); bool GetQXferLibrariesSVR4ReadSupported(); @@ -525,6 +527,8 @@ llvm::Expected KillProcess(lldb::pid_t pid); + bool GetNonStopSupported() const; + protected: LazyBool m_supports_not_sending_acks = eLazyBoolCalculate; LazyBool m_supports_thread_suffix = eLazyBoolCalculate; @@ -565,6 +569,7 @@ LazyBool m_supports_multiprocess = eLazyBoolCalculate; LazyBool m_supports_memory_tagging = eLazyBoolCalculate; LazyBool m_supports_qSaveCore = eLazyBoolCalculate; + LazyBool m_supports_QNonStop = eLazyBoolCalculate; LazyBool m_uses_native_signals = eLazyBoolCalculate; bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1, Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -276,6 +276,7 @@ m_avoid_g_packets = eLazyBoolCalculate; m_supports_multiprocess = eLazyBoolCalculate; m_supports_qSaveCore = eLazyBoolCalculate; + m_supports_QNonStop = eLazyBoolCalculate; m_supports_qXfer_auxv_read = eLazyBoolCalculate; m_supports_qXfer_libraries_read = eLazyBoolCalculate; m_supports_qXfer_libraries_svr4_read = eLazyBoolCalculate; @@ -335,6 +336,7 @@ m_supports_QPassSignals = eLazyBoolNo; m_supports_memory_tagging = eLazyBoolNo; m_supports_qSaveCore = eLazyBoolNo; + m_supports_QNonStop = eLazyBoolNo; m_uses_native_signals = eLazyBoolNo; m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if @@ -384,6 +386,8 @@ m_supports_memory_tagging = eLazyBoolYes; else if (x == "qSaveCore+") m_supports_qSaveCore = eLazyBoolYes; + else if (x == "QNonStop+") + m_supports_QNonStop = eLazyBoolYes; else if (x == "native-signals+") m_uses_native_signals = eLazyBoolYes; // Look for a list of compressions in the features list e.g. @@ -530,6 +534,10 @@ return m_supports_qSaveCore == eLazyBoolYes; } +bool GDBRemoteCommunicationClient::GetNonStopSupported() const { + return m_supports_QNonStop == eLazyBoolYes; +} + StructuredData::ObjectSP GDBRemoteCommunicationClient::GetThreadsInfo() { // Get information on all threads at one using the "jThreadsInfo" packet StructuredData::ObjectSP object_sp; @@ -579,6 +587,18 @@ } } +void GDBRemoteCommunicationClient::EnableNonStop() { + if (!GetNonStopSupported()) + return; + + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse("QNonStop:1", response) == + PacketResult::Success) { + if (response.IsOKResponse()) + SetNonStopEnabled(true); + } +} + bool GDBRemoteCommunicationClient::GetLoadedDynamicLibrariesInfosSupported() { if (m_supports_jLoadedDynamicLibrariesInfos == eLazyBoolCalculate) { StringExtractorGDBRemote response; @@ -2762,8 +2782,13 @@ bool GDBRemoteCommunicationClient::GetStopReply( StringExtractorGDBRemote &response) { - if (SendPacketAndWaitForResponse("?", response) == PacketResult::Success) + if (SendPacketAndWaitForResponse("?", response) == PacketResult::Success) { + if (GetNonStopEnabled()) { + if (!DrainNotificationQueue("vStopped")) + return false; // TODO: better error handling + } return response.IsNormalResponse(); + } return false; } 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 @@ -287,6 +287,7 @@ int64_t m_breakpoint_pc_offset; lldb::tid_t m_initial_tid; // The initial thread ID, given by stub on attach bool m_use_g_packet_for_reading; + bool m_use_non_stop_protocol; bool m_allow_flash_writes; using FlashRangeVector = lldb_private::RangeVector; 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 @@ -164,6 +164,11 @@ const uint32_t idx = ePropertyUseGPacketForReading; return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); } + + bool GetUseNonStopProtocol() const { + const uint32_t idx = ePropertyUseNonStopProtocol; + return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); + } }; static PluginProperties &GetGlobalPluginProperties() { @@ -301,6 +306,8 @@ m_use_g_packet_for_reading = GetGlobalPluginProperties().GetUseGPacketForReading(); + m_use_non_stop_protocol = + GetGlobalPluginProperties().GetUseNonStopProtocol(); } // Destructor @@ -1028,6 +1035,11 @@ else SetUnixSignals(UnixSignals::Create(GetTarget().GetArchitecture())); } + + // Delay enabling non-stop protocol until we've taken care of full setup + // to workaround a bug in gdbserver. + if (m_use_non_stop_protocol) + m_gdb_comm.EnableNonStop(); } void ProcessGDBRemote::MaybeLoadExecutableModule() { Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteProperties.td =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteProperties.td +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteProperties.td @@ -9,6 +9,10 @@ Global, DefaultStringValue<"">, Desc<"The file that provides the description for remote target registers.">; + def UseNonStopProtocol: Property<"use-non-stop-protocol", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, LLDB will enable non-stop mode on the server and expect non-stop-style notifications from it. Note that LLDB currently requires that all threads are stopped anyway in non-stop mode.">; def UseSVR4: Property<"use-libraries-svr4", "Boolean">, Global, DefaultTrue, Index: lldb/test/API/functionalities/gdb_remote_client/TestNonStop.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/gdb_remote_client/TestNonStop.py @@ -0,0 +1,120 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test.gdbclientutils import * +from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase + + +class NonStopResponder(MockGDBServerResponder): + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + return "OK" + + def qfThreadInfo(self): + return "mp10.10,p10.12" + + def qsThreadInfo(self): + return "l" + + +class TestNonStop(GDBRemoteTestBase): + def test_run(self): + class MyResponder(NonStopResponder): + vStopped_counter = 0 + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter > 1 + else "T00thread:p10.10;") + + def cont(self): + self.vStopped_counter = 0 + return ["OK", "%Stop:T02thread:p10.12;"] + + self.dbg.HandleCommand( + "settings set plugin.process.gdb-remote.use-non-stop-protocol true") + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + self.assertPacketLogContains(["QNonStop:1"]) + + process.Continue() + self.assertPacketLogContains(["vStopped"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonSignal) + self.assertEqual(process.GetSelectedThread().GetStopDescription(100), + "signal SIGINT") + + def test_stdio(self): + class MyResponder(NonStopResponder): + vStdio_counter = 0 + vStopped_counter = 0 + + def vStdio(self): + self.vStdio_counter += 1 + # intersperse notifications with replies for better testing + return ("OK" if self.vStdio_counter > 1 + else ["%Stop:T02thread:p10.12;", + "O7365636f6e64206c696e650d0a"]) + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter > 1 + else "T00thread:p10.10;") + + def cont(self): + self.vStopped_counter = 0 + self.vStdio_counter = 0 + return ["OK", + "%Stdio:O6669727374206c696e650d0a",] + + self.dbg.HandleCommand( + "settings set plugin.process.gdb-remote.use-non-stop-protocol true") + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + self.assertPacketLogContains(["QNonStop:1"]) + + process.Continue() + self.assertPacketLogContains(["vStdio", "vStopped"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonSignal) + self.assertEqual(process.GetSelectedThread().GetStopDescription(100), + "signal SIGINT") + + def test_vCtrlC(self): + class MyResponder(NonStopResponder): + vStopped_counter = 0 + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter % 2 == 0 + else "T00thread:p10.10;") + + def cont(self): + return ["OK"] + + def vCtrlC(self): + return ["OK", "%Stop:T00thread:p10.10;"] + + self.dbg.HandleCommand( + "settings set plugin.process.gdb-remote.use-non-stop-protocol true") + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + self.assertPacketLogContains(["QNonStop:1"]) + + self.dbg.SetAsync(True) + lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, + [lldb.eStateStopped]) + process.Continue() + lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, + [lldb.eStateRunning]) + process.Stop() + lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, + [lldb.eStateStopped]) + self.assertPacketLogContains(["vStopped"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonNone)