mgorny created this revision. mgorny added reviewers: labath, krytarowski, emaste. Herald added a subscriber: arichardson. Herald added a project: All. mgorny requested review of this revision.
Add a client support for using the non-stop protocol to run processes in all-stop mode. The operations are still done synchronously, so this does not enable client to asynchronously resume threads or send additional requests while the process is running. If plugin.process.gdb-remote.use-non-stop-protocol is set to true prior to connecting and the server reports support for QNonStop extension, the client attempts to enable non-stop mode. Using non-stop mode has the following implications: 1. When a continue packet is sent, the client receives the synchronous "OK" notification and blocks until %Stop notification arrives. Then it issues an additional "vCtrlC" command to ensure that all threads are stopped, as is currently required by the client's architecture. In both events, the client drains the notification queue. 2. When a "?" packet is sent, the client issues "vStopped" command series in order to drain the notification queue. 3. When the process is about to be stopped, the client issues a "vCtrlC" command instead of the break sequence. API tests required changing the Process::Halt() API to inspect process' private state rather than the public state, that remains eStateUnloaded in gdb_remote_client tests. Sponsored by: The FreeBSD Foundation https://reviews.llvm.org/D126614 Files: lldb/packages/Python/lldbsuite/test/gdbclientutils.py lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteProperties.td lldb/source/Target/Process.cpp lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py
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) 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/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/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<lldb::addr_t, size_t>; 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/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/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<void(llvm::StringRef)> 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<std::chrono::seconds>(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/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]
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits