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 @@ -121,10 +127,6 @@ /// an async thread e.g. to inject a signal. std::string m_continue_packet; - /// When was the interrupt packet sent. Used to make sure we time out if the - /// stub does not respond to interrupt requests. - std::chrono::time_point m_interrupt_endpoint; - /// Number of threads interested in sending. uint32_t m_async_count; @@ -133,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 @@ -11,6 +11,7 @@ #include "llvm/ADT/StringExtras.h" #include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/Connection.h" #include "lldb/Utility/LLDBAssert.h" #include "ProcessGDBRemoteLog.h" @@ -52,15 +53,18 @@ 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); + 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); @@ -70,21 +74,51 @@ if (m_async_count == 0) { continue; } + if (!interrupt_sent) { + 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 && !GetNonStopEnabled()) + log->PutCString( + "GDBRemoteClientBase::SendContinuePacketAndWaitForResponse sent " + "packet: \\x03"); + + interrupt_sent = true; + continue; + } + auto cur_time = steady_clock::now(); - if (cur_time >= m_interrupt_endpoint) + if (cur_time >= interrupt_endpoint) return eStateInvalid; else { // We woke up and found an interrupt is in flight, but we haven't // exceeded the interrupt wait time. So reset the wait time to the // 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, + auto new_wait = interrupt_endpoint - cur_time; + computed_timeout = std::min( + kWakeupInterval, std::chrono::duration_cast(new_wait)); continue; } break; } + case PacketResult::Notify: case PacketResult::Success: break; default: @@ -95,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': @@ -283,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 // /////////////////////////////////////// @@ -322,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; @@ -347,7 +442,6 @@ } void GDBRemoteClientBase::Lock::SyncWithContinueThread() { - 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 @@ -355,22 +449,7 @@ ++m_comm.m_async_count; if (m_comm.m_is_running) { - 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) { - --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"); - } + m_comm.GetConnection()->InterruptRead(); m_comm.m_cv.wait(lock, [this] { return !m_comm.m_is_running; }); m_did_interrupt = true; } 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 @@ -319,6 +319,8 @@ void EnableErrorStringInPacket(); + void EnableNonStop(); + bool GetQXferLibrariesReadSupported(); bool GetQXferLibrariesSVR4ReadSupported(); @@ -523,6 +525,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; @@ -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,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)