Index: lldb/packages/Python/lldbsuite/test/gdbclientutils.py =================================================================== --- lldb/packages/Python/lldbsuite/test/gdbclientutils.py +++ lldb/packages/Python/lldbsuite/test/gdbclientutils.py @@ -26,7 +26,11 @@ Framing includes surrounding the message between $ and #, and appending a two character hex checksum. """ - return "$%s#%02x" % (message, checksum(message)) + prefix = "$" + if message.startswith("%"): + prefix = "%" + message = message[1:] + return "%s%s#%02x" % (prefix, message, checksum(message)) def escape_binary(message): @@ -200,6 +204,12 @@ 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 == "vStopped": + return self.vStopped() + if packet == "vCtrlC": + return self.vCtrlC() if packet == "k": return self.k() @@ -333,6 +343,15 @@ def qRegisterInfo(self, num): return "" + def QNonStop(self, enabled): + 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 GetNonStopProtocol() { return m_non_stop_protocol; } + + void SetNonStopProtocol(bool enabled) { m_non_stop_protocol = enabled; } + + bool DrainNotificationQueue(llvm::StringRef command); + class Lock { public: // If interrupt_timeout == 0 seconds, only take the lock if the target is @@ -133,6 +139,9 @@ /// Whether we should resume after a stop. bool m_should_stop; + + /// Whether non-stop protocol should be used. + bool m_non_stop_protocol = 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 @@ -52,13 +52,13 @@ if (!cont_lock) return eStateInvalid; OnRunPacketSent(true); - // The main ReadPacket loop wakes up at computed_timeout intervals, just to + // The main ReadPacket loop wakes up at computed_timeout intervals, just to // check that the connection hasn't dropped. When we wake up we also check // whether there is an interrupt request that has reached its endpoint. - // If we want a shorter interrupt timeout that kWakeupInterval, we need to + // If we want a shorter interrupt timeout that kWakeupInterval, we need to // choose the shorter interval for the wake up as well. - std::chrono::seconds computed_timeout = std::min(interrupt_timeout, - kWakeupInterval); + std::chrono::seconds computed_timeout = + std::min(interrupt_timeout, kWakeupInterval); for (;;) { PacketResult read_result = ReadPacket(response, computed_timeout, false); // Reset the computed_timeout to the default value in case we are going @@ -79,7 +79,8 @@ // time left till the interrupt timeout. But don't wait longer // than our wakeup timeout. auto new_wait = m_interrupt_endpoint - cur_time; - computed_timeout = std::min(kWakeupInterval, + computed_timeout = std::min( + kWakeupInterval, std::chrono::duration_cast(new_wait)); continue; } @@ -95,10 +96,72 @@ if (response.Empty()) return eStateInvalid; - const char stop_type = response.GetChar(); LLDB_LOGF(log, "GDBRemoteClientBase::%s () got packet: %s", __FUNCTION__, response.GetStringRef().data()); + // We do not currently support full asynchronous communication. Instead, + // when in non-stop mode, we: + // 1) get the "OK" response to the continue packet + // 2) wait for the asynchronous %Stop notification + // 3) drain the notification queue + // 4) issue a "vCtrlC" command to ensure that all threads stop + // 5) repeat 1-3 for the response to this packet + + if (response.IsOKResponse()) + continue; + // TODO: get a proper mechanism for async notifications + if (response.GetStringRef().startswith("Stop:")) { + response.Reset(response.GetStringRef().substr(5)); + + if (!DrainNotificationQueue("vStopped")) + return eStateInvalid; + + switch (response.GetStringRef()[0]) { + case 'W': + case 'X': + // Do not attempt to stop the process if it exited already. + break; + default: + StringExtractorGDBRemote stop_response; + if (SendPacketAndWaitForResponseNoLock("vCtrlC", stop_response) != + PacketResult::Success) { + LLDB_LOGF(log, "GDBRemoteClientBase::%s () vCtrlC failed", + __FUNCTION__); + return eStateInvalid; + } + + LLDB_LOGF(log, "GDBRemoteClientBase::%s () vCtrlC response: %s", + __FUNCTION__, stop_response.GetStringRef().data()); + if (stop_response.IsUnsupportedResponse()) { + LLDB_LOGF(log, "GDBRemoteClientBase::%s () vCtrlC unsupported", + __FUNCTION__); + return eStateInvalid; + } + if (!stop_response.IsOKResponse()) { + LLDB_LOGF(log, "GDBRemoteClientBase::%s () vCtrlC failed", + __FUNCTION__); + return eStateInvalid; + } + + // vCtrlC may not do anything, so timeout if we don't get notification + if (ReadPacket(stop_response, milliseconds(500), false) == + PacketResult::Success) { + if (!stop_response.GetStringRef().startswith("Stop:")) { + LLDB_LOGF(log, + "GDBRemoteClientBase::%s () unexpected response " + "after vCtrlC", + __FUNCTION__); + return eStateInvalid; + } + + if (!DrainNotificationQueue("vStopped")) + return eStateInvalid; + } + } + } + + const char stop_type = response.GetChar(); + switch (stop_type) { case 'W': case 'X': @@ -283,6 +346,30 @@ BroadcastEvent(eBroadcastBitRunPacketSent, nullptr); } +bool GDBRemoteClientBase::DrainNotificationQueue(llvm::StringRef command) { + Log *log = GetLog(GDBRLog::Process); + for (;;) { + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponseNoLock(command, response) == + PacketResult::Success) { + if (response.IsUnsupportedResponse()) { + LLDB_LOGF(log, "GDBRemoteClientBase::%s () %s unsupported", + __FUNCTION__, command.data()); + return false; + } + + LLDB_LOGF(log, "GDBRemoteClientBase::%s () %s response: %s", __FUNCTION__, + command.data(), response.GetStringRef().data()); + if (response.IsOKResponse()) + return true; + } else { + LLDB_LOGF(log, "GDBRemoteClientBase::%s () sending %s failed", + __FUNCTION__, command.data()); + return false; + } + } +} + /////////////////////////////////////// // GDBRemoteClientBase::ContinueLock // /////////////////////////////////////// @@ -358,18 +445,23 @@ if (m_comm.m_async_count == 1) { // The sender has sent the continue packet and we are the first async // packet. Let's interrupt it. - const char ctrl_c = '\x03'; - ConnectionStatus status = eConnectionStatusSuccess; - size_t bytes_written = m_comm.Write(&ctrl_c, 1, status, nullptr); - if (bytes_written == 0) { + bool success; + if (m_comm.GetNonStopProtocol()) + success = m_comm.SendPacketNoLock("vCtrlC") == PacketResult::Success; + else { + const char ctrl_c = '\x03'; + ConnectionStatus status = eConnectionStatusSuccess; + success = m_comm.Write(&ctrl_c, 1, status, nullptr) != 0; + } + if (!success) { --m_comm.m_async_count; LLDB_LOGF(log, "GDBRemoteClientBase::Lock::Lock failed to send " "interrupt packet"); return; } m_comm.m_interrupt_endpoint = steady_clock::now() + m_interrupt_timeout; - if (log) - log->PutCString("GDBRemoteClientBase::Lock::Lock sent packet: \\x03"); + if (log && !m_comm.GetNonStopProtocol()) + log->PutCString("GDBRemoteClientBase::Lock::Lock sent packet: \\x03"); } m_comm.m_cv.wait(lock, [this] { return !m_comm.m_is_running; }); m_did_interrupt = 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(); @@ -523,6 +525,8 @@ bool GetSaveCoreSupported() const; + bool GetNonStopSupported() const; + protected: LazyBool m_supports_not_sending_acks = eLazyBoolCalculate; LazyBool m_supports_thread_suffix = eLazyBoolCalculate; @@ -563,6 +567,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()) + SetNonStopProtocol(true); + } +} + bool GDBRemoteCommunicationClient::GetLoadedDynamicLibrariesInfosSupported() { if (m_supports_jLoadedDynamicLibrariesInfos == eLazyBoolCalculate) { StringExtractorGDBRemote response; @@ -2747,8 +2767,13 @@ bool GDBRemoteCommunicationClient::GetStopReply( StringExtractorGDBRemote &response) { - if (SendPacketAndWaitForResponse("?", response) == PacketResult::Success) + if (SendPacketAndWaitForResponse("?", response) == PacketResult::Success) { + if (GetNonStopProtocol()) { + 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 @@ -953,6 +960,8 @@ m_gdb_comm.GetVContSupported('c'); m_gdb_comm.GetVAttachOrWaitSupported(); m_gdb_comm.EnableErrorStringInPacket(); + if (m_use_non_stop_protocol) + m_gdb_comm.EnableNonStop(); // First dispatch any commands from the platform: auto handle_cmds = [&] (const Args &args) -> void { 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/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -3082,7 +3082,7 @@ } Status Process::Halt(bool clear_thread_plans, bool use_run_lock) { - if (!StateIsRunningState(m_public_state.GetValue())) + if (!StateIsRunningState(m_private_state.GetValue())) return Status("Process is not running."); // Don't clear the m_clear_thread_plans_on_stop, only set it to true if in Index: lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py =================================================================== --- lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py +++ lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py @@ -585,3 +585,125 @@ } self.do_siginfo_test("remote-freebsd", "basic_eh_frame.yaml", data, expected) + + def test_QNonStop_query(self): + class MyResponder(MockGDBServerResponder): + vStopped_counter = 0 + + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + assert val == 1 + return "OK" + + def qfThreadInfo(self): + return "m10,12" + + def qsThreadInfo(self): + return "l" + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter % 2 == 0 + else "T00;thread:10") + + self.dbg.HandleCommand( + "settings set plugin.process.gdb-remote.use-non-stop-protocol true") + self.addTearDownHook(lambda: + self.runCmd( + "settings set plugin.process.gdb-remote.use-non-stop-protocol " + "false")) + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + self.assertPacketLogContains(["QNonStop:1", "vStopped"]) + + def test_QNonStop_run(self): + class MyResponder(MockGDBServerResponder): + vStopped_counter = 0 + + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + assert val == 1 + return "OK" + + def qfThreadInfo(self): + return "m10,12" + + def qsThreadInfo(self): + return "l" + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter % 2 == 0 + else "T00;thread:10") + + def cont(self): + return ["OK", "%Stop:T02;thread:12"] + + def vCtrlC(self): + return ["OK", "%Stop:T00;thread:10"] + + self.dbg.HandleCommand( + "settings set plugin.process.gdb-remote.use-non-stop-protocol true") + self.addTearDownHook(lambda: + self.runCmd( + "settings set plugin.process.gdb-remote.use-non-stop-protocol " + "false")) + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + self.assertPacketLogContains(["QNonStop:1"]) + + process.Continue() + self.assertPacketLogContains(["vStopped", "vCtrlC"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonSignal) + self.assertEqual(process.GetSelectedThread().GetStopDescription(100), + "signal SIGINT") + + def test_QNonStop_vCtrlC(self): + class MyResponder(MockGDBServerResponder): + vStopped_counter = 0 + + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + assert val == 1 + return "OK" + + def qfThreadInfo(self): + return "m10,12" + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter % 2 == 0 + else "T00;thread:10") + + def cont(self): + return ["OK"] + + def vCtrlC(self): + return ["OK", "%Stop:T00;thread:10"] + + self.dbg.HandleCommand( + "settings set plugin.process.gdb-remote.use-non-stop-protocol true") + self.addTearDownHook(lambda: + self.runCmd( + "settings set plugin.process.gdb-remote.use-non-stop-protocol " + "false")) + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + self.assertPacketLogContains(["QNonStop:1"]) + + self.dbg.SetAsync(True) + process.Continue() + process.Stop() + self.assertPacketLogContains(["vStopped", "vCtrlC"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonNone)