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 @@ -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 = 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,15 +52,16 @@ 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); + 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); @@ -79,12 +80,14 @@ // 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; } break; } + case PacketResult::Notify: case PacketResult::Success: break; default: @@ -95,9 +98,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': @@ -283,6 +320,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 // /////////////////////////////////////// @@ -322,9 +375,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; @@ -347,7 +411,7 @@ } void GDBRemoteClientBase::Lock::SyncWithContinueThread() { - Log *log = GetLog(GDBRLog::Process|GDBRLog::Packets); + Log *log = GetLog(GDBRLog::Process | GDBRLog::Packets); std::unique_lock lock(m_comm.m_mutex); if (m_comm.m_is_running && m_interrupt_timeout == std::chrono::seconds(0)) return; // We were asked to avoid interrupting the sender. Lock is not @@ -358,17 +422,25 @@ 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.GetNonStopEnabled()) { + StringExtractorGDBRemote halt_response; + // Note: we expect an "OK" response here but due to the async logic, + // we need to check for it in SendContinuePacketAndWaitForResponse() + 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) + if (log && !m_comm.GetNonStopEnabled()) log->PutCString("GDBRemoteClientBase::Lock::Lock sent packet: \\x03"); } m_comm.m_cv.wait(lock, [this] { return !m_comm.m_is_running; }); Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -12,6 +12,7 @@ #include "GDBRemoteCommunicationHistory.h" #include +#include #include #include #include @@ -89,17 +90,18 @@ enum class PacketType { Invalid = 0, Standard, Notify }; enum class PacketResult { - Success = 0, // Success - ErrorSendFailed, // Status sending the packet - ErrorSendAck, // Didn't get an ack back after sending a packet - ErrorReplyFailed, // Status getting the reply - ErrorReplyTimeout, // Timed out waiting for reply - ErrorReplyInvalid, // Got a reply but it wasn't valid for the packet that - // was sent - ErrorReplyAck, // Sending reply ack failed - ErrorDisconnected, // We were disconnected - ErrorNoSequenceLock // We couldn't get the sequence lock for a multi-packet - // request + Success = 0, // Success + ErrorSendFailed, // Status sending the packet + ErrorSendAck, // Didn't get an ack back after sending a packet + ErrorReplyFailed, // Status getting the reply + ErrorReplyTimeout, // Timed out waiting for reply + ErrorReplyInvalid, // Got a reply but it wasn't valid for the packet that + // was sent + ErrorReplyAck, // Sending reply ack failed + ErrorDisconnected, // We were disconnected + ErrorNoSequenceLock, // We couldn't get the sequence lock for a multi-packet + // request + Notify, // Successfully gotten a notification packet }; // Class to change the timeout for a given scope and restore it to the @@ -179,6 +181,7 @@ bool m_is_platform; // Set to true if this class represents a platform, // false if this class represents a debug session for // a single process + std::deque m_notification_packet_queue; CompressionType m_compression_type; @@ -190,7 +193,8 @@ bool skip_ack = false); PacketResult ReadPacket(StringExtractorGDBRemote &response, - Timeout timeout, bool sync_on_timeout); + Timeout timeout, bool sync_on_timeout, + bool allow_notification = false); PacketResult ReadPacketWithOutputSupport( StringExtractorGDBRemote &response, Timeout timeout, @@ -199,7 +203,8 @@ PacketResult WaitForPacketNoLock(StringExtractorGDBRemote &response, Timeout timeout, - bool sync_on_timeout); + bool sync_on_timeout, + bool allow_notification = false); bool CompressionIsEnabled() { return m_compression_type != CompressionType::None; Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -239,16 +239,15 @@ return result; } -GDBRemoteCommunication::PacketResult -GDBRemoteCommunication::ReadPacket(StringExtractorGDBRemote &response, - Timeout timeout, - bool sync_on_timeout) { +GDBRemoteCommunication::PacketResult GDBRemoteCommunication::ReadPacket( + StringExtractorGDBRemote &response, Timeout timeout, + bool sync_on_timeout, bool allow_notification) { using ResponseType = StringExtractorGDBRemote::ResponseType; Log *log = GetLog(GDBRLog::Packets); for (;;) { - PacketResult result = - WaitForPacketNoLock(response, timeout, sync_on_timeout); + PacketResult result = WaitForPacketNoLock( + response, timeout, sync_on_timeout, allow_notification); if (result != PacketResult::Success || (response.GetResponseType() != ResponseType::eAck && response.GetResponseType() != ResponseType::eNack)) @@ -260,15 +259,30 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunication::WaitForPacketNoLock(StringExtractorGDBRemote &packet, Timeout timeout, - bool sync_on_timeout) { + bool sync_on_timeout, + bool allow_notification) { uint8_t buffer[8192]; Status error; Log *log = GetLog(GDBRLog::Packets); + // If the caller expects notifications, check the notification queue first. + if (allow_notification && !m_notification_packet_queue.empty()) { + packet.Reset(m_notification_packet_queue.front()); + m_notification_packet_queue.pop_front(); + return PacketResult::Notify; + } + // Check for a packet from our cache first without trying any reading... - if (CheckForPacket(nullptr, 0, packet) != PacketType::Invalid) - return PacketResult::Success; + PacketType packet_type = CheckForPacket(nullptr, 0, packet); + if (packet_type == PacketType::Notify && !allow_notification) { + // If the caller does not support notifications, queue it for later. + m_notification_packet_queue.push_back(packet.GetStringRef().str()); + packet_type = CheckForPacket(nullptr, 0, packet); + } + if (packet_type != PacketType::Invalid) + return packet_type == PacketType::Standard ? PacketResult::Success + : PacketResult::Notify; bool timed_out = false; bool disconnected = false; @@ -283,8 +297,12 @@ bytes_read); if (bytes_read > 0) { - if (CheckForPacket(buffer, bytes_read, packet) != PacketType::Invalid) - return PacketResult::Success; + packet_type = CheckForPacket(buffer, bytes_read, packet); + if (packet_type == PacketType::Notify && !allow_notification) + m_notification_packet_queue.push_back(packet.GetStringRef().str()); + else if (packet_type != PacketType::Invalid) + return packet_type == PacketType::Standard ? PacketResult::Success + : PacketResult::Notify; } else { switch (status) { case eConnectionStatusTimedOut: @@ -1315,6 +1333,9 @@ case PacketResult::ErrorNoSequenceLock: Stream << "ErrorNoSequenceLock"; break; + case PacketResult::Notify: + Stream << "Notify"; + break; } } 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()) + SetNonStopEnabled(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 (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 @@ -1077,6 +1084,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,151 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test.gdbclientutils import * +from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase + + +class TestNonStop(GDBRemoteTestBase): + def test_run(self): + class MyResponder(MockGDBServerResponder): + vStopped_counter = 0 + + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + 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 > 1 + else "T00;thread:10") + + def cont(self): + self.vStopped_counter = 0 + return ["OK", "%Stop:T02;thread:12"] + + 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"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonSignal) + self.assertEqual(process.GetSelectedThread().GetStopDescription(100), + "signal SIGINT") + + def test_stdio(self): + class MyResponder(MockGDBServerResponder): + vStdio_counter = 0 + vStopped_counter = 0 + + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + return "OK" + + def qfThreadInfo(self): + return "m10,12" + + def qsThreadInfo(self): + return "l" + + def vStdio(self): + self.vStdio_counter += 1 + # intersperse notifications with replies for better testing + return ("OK" if self.vStdio_counter > 1 + else ["%Stop:T02;thread:12", + "O7365636f6e64206c696e650d0a"]) + + def vStopped(self): + self.vStopped_counter += 1 + return ("OK" if self.vStopped_counter > 1 + else "T00;thread: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.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(["vStdio", "vStopped"]) + self.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonSignal) + self.assertEqual(process.GetSelectedThread().GetStopDescription(100), + "signal SIGINT") + + def test_vCtrlC(self): + class MyResponder(MockGDBServerResponder): + vStopped_counter = 0 + + def qSupported(self, client_supported): + return "QNonStop+;" + super().qSupported(client_supported) + + def QNonStop(self, val): + 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) + 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)