diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -174,7 +174,10 @@ eServerPacketType_QMemTags, // write memory tags eServerPacketType_qLLDBSaveCore, - eServerPacketType_QSetIgnoredExceptions + eServerPacketType_QSetIgnoredExceptions, + eServerPacketType_QNonStop, + eServerPacketType_vStopped, + eServerPacketType_vCtrlC, }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -851,6 +851,7 @@ "memory-tagging", "qSaveCore", "native-signals", + "QNonStop", ] def parse_qSupported_response(self, context): diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py @@ -866,7 +866,7 @@ class Server(object): - _GDB_REMOTE_PACKET_REGEX = re.compile(br'^\$([^\#]*)#[0-9a-fA-F]{2}') + _GDB_REMOTE_PACKET_REGEX = re.compile(br'^[\$%]([^\#]*)#[0-9a-fA-F]{2}') class ChecksumMismatch(Exception): pass diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -183,6 +183,9 @@ CompressionType m_compression_type; PacketResult SendPacketNoLock(llvm::StringRef payload); + PacketResult SendNotificationPacketNoLock(llvm::StringRef notify_type, + std::deque& queue, + llvm::StringRef payload); PacketResult SendRawPacketNoLock(llvm::StringRef payload, bool skip_ack = false); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -124,6 +124,29 @@ return SendRawPacketNoLock(packet_str); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunication::SendNotificationPacketNoLock( + llvm::StringRef notify_type, std::deque &queue, + llvm::StringRef payload) { + PacketResult ret = PacketResult::Success; + + // If there are no notification in the queue, send the notification + // packet. + if (queue.empty()) { + StreamString packet(0, 4, eByteOrderBig); + packet.PutChar('%'); + packet.Write(notify_type.data(), notify_type.size()); + packet.PutChar(':'); + packet.Write(payload.data(), payload.size()); + packet.PutChar('#'); + packet.PutHex8(CalculcateChecksum(payload)); + ret = SendRawPacketNoLock(packet.GetString(), true); + } + + queue.push_back(payload.str()); + return ret; +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunication::SendRawPacketNoLock(llvm::StringRef packet, bool skip_ack) { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -106,6 +106,8 @@ uint32_t m_next_saved_registers_id = 1; bool m_thread_suffix_supported = false; bool m_list_threads_in_stop_reply = false; + bool m_non_stop = false; + std::deque m_stop_notification_queue; NativeProcessProtocol::Extension m_extensions_supported = {}; @@ -113,11 +115,17 @@ PacketResult SendWResponse(NativeProcessProtocol *process); + StreamString PrepareStopReplyPacketForThread(NativeThreadProtocol &thread); + PacketResult SendStopReplyPacketForThread(NativeProcessProtocol &process, - lldb::tid_t tid); + lldb::tid_t tid, + bool force_synchronous); PacketResult SendStopReasonForState(NativeProcessProtocol &process, - lldb::StateType process_state); + lldb::StateType process_state, + bool force_synchronous); + + void EnqueueStopReplyPackets(lldb::tid_t thread_to_skip); PacketResult Handle_k(StringExtractorGDBRemote &packet); @@ -219,6 +227,12 @@ PacketResult Handle_qSaveCore(StringExtractorGDBRemote &packet); + PacketResult Handle_QNonStop(StringExtractorGDBRemote &packet); + + PacketResult Handle_vStopped(StringExtractorGDBRemote &packet); + + PacketResult Handle_vCtrlC(StringExtractorGDBRemote &packet); + PacketResult Handle_g(StringExtractorGDBRemote &packet); PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet); @@ -246,6 +260,10 @@ std::vector HandleFeatures( const llvm::ArrayRef client_features) override; + // Provide a response for successful continue action, i.e. send "OK" + // in non-stop mode, no response otherwise. + PacketResult SendContinueSuccessResponse(); + private: llvm::Expected> BuildTargetXml(); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -235,6 +235,16 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_qLLDBSaveCore, &GDBRemoteCommunicationServerLLGS::Handle_qSaveCore); + + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_QNonStop, + &GDBRemoteCommunicationServerLLGS::Handle_QNonStop); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_vStopped, + &GDBRemoteCommunicationServerLLGS::Handle_vStopped); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_vCtrlC, + &GDBRemoteCommunicationServerLLGS::Handle_vCtrlC); } void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) { @@ -476,6 +486,9 @@ response.Format("{0:g}", *wait_status); if (bool(m_extensions_supported & NativeProcessProtocol::Extension::multiprocess)) response.Format(";process:{0:x-}", process->GetID()); + if (m_non_stop) + return SendNotificationPacketNoLock("Stop", m_stop_notification_queue, + response.GetString()); return SendPacketNoLock(response.GetString()); } @@ -766,23 +779,22 @@ return threads_array; } -GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread( - NativeProcessProtocol &process, lldb::tid_t tid) { +StreamString +GDBRemoteCommunicationServerLLGS::PrepareStopReplyPacketForThread( + NativeThreadProtocol &thread) { Log *log = GetLog(LLDBLog::Process | LLDBLog::Thread); - LLDB_LOG(log, "preparing packet for pid {0} tid {1}", process.GetID(), tid); + NativeProcessProtocol &process = thread.GetProcess(); - // Ensure we can get info on the given thread. - NativeThreadProtocol *thread = process.GetThreadByID(tid); - if (!thread) - return SendErrorResponse(51); + LLDB_LOG(log, "preparing packet for pid {0} tid {1}", process.GetID(), + thread.GetID()); // Grab the reason this thread stopped. + StreamString response; struct ThreadStopInfo tid_stop_info; std::string description; - if (!thread->GetStopReason(tid_stop_info, description)) - return SendErrorResponse(52); + if (!thread.GetStopReason(tid_stop_info, description)) + return response; // FIXME implement register handling for exec'd inferiors. // if (tid_stop_info.reason == eStopReasonExec) { @@ -790,14 +802,13 @@ // InitializeRegisters(force); // } - StreamString response; // Output the T packet with the thread response.PutChar('T'); int signum = tid_stop_info.details.signal.signo; LLDB_LOG( log, "pid {0}, tid {1}, got signal signo = {2}, reason = {3}, exc_type = {4}", - process.GetID(), tid, signum, int(tid_stop_info.reason), + process.GetID(), thread.GetID(), signum, int(tid_stop_info.reason), tid_stop_info.details.exception.type); // Print the signal number. @@ -808,10 +819,10 @@ if (bool(m_extensions_supported & NativeProcessProtocol::Extension::multiprocess)) response.Format("p{0:x-}.", process.GetID()); - response.Format("{0:x-};", tid); + response.Format("{0:x-};", thread.GetID()); // Include the thread name if there is one. - const std::string thread_name = thread->GetName(); + const std::string thread_name = thread.GetName(); if (!thread_name.empty()) { size_t thread_name_len = thread_name.length(); @@ -875,7 +886,7 @@ char delimiter = ':'; for (NativeThreadProtocol *thread; (thread = process.GetThreadAtIndex(i)) != nullptr; ++i) { - NativeRegisterContext& reg_ctx = thread->GetRegisterContext(); + NativeRegisterContext ®_ctx = thread->GetRegisterContext(); uint32_t reg_to_read = reg_ctx.ConvertRegisterKindToRegisterNumber( eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC); @@ -906,7 +917,7 @@ // // Grab the register context. - NativeRegisterContext& reg_ctx = thread->GetRegisterContext(); + NativeRegisterContext ®_ctx = thread.GetRegisterContext(); const auto expedited_regs = reg_ctx.GetExpeditedRegisters(ExpeditedRegs::Full); @@ -923,8 +934,9 @@ ®_value, lldb::eByteOrderBig); response.PutChar(';'); } else { - LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s failed to read " - "register '%s' index %" PRIu32 ": %s", + LLDB_LOGF(log, + "GDBRemoteCommunicationServerLLGS::%s failed to read " + "register '%s' index %" PRIu32 ": %s", __FUNCTION__, reg_info_p->name ? reg_info_p->name : "", reg_num, error.AsCString()); @@ -973,9 +985,46 @@ tid_stop_info.details.fork.child_tid); } + return response; +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread( + NativeProcessProtocol &process, lldb::tid_t tid, bool force_synchronous) { + // Ensure we can get info on the given thread. + NativeThreadProtocol *thread = process.GetThreadByID(tid); + if (!thread) + return SendErrorResponse(51); + + StreamString response = PrepareStopReplyPacketForThread(*thread); + if (response.Empty()) + return SendErrorResponse(42); + + if (m_non_stop && !force_synchronous) { + PacketResult ret = SendNotificationPacketNoLock( + "Stop", m_stop_notification_queue, response.GetString()); + // Queue notification events for the remaining threads. + EnqueueStopReplyPackets(tid); + return ret; + } + return SendPacketNoLock(response.GetString()); } +void GDBRemoteCommunicationServerLLGS::EnqueueStopReplyPackets( + lldb::tid_t thread_to_skip) { + if (!m_non_stop) + return; + + uint32_t thread_index = 0; + while (NativeThreadProtocol *listed_thread = + m_current_process->GetThreadAtIndex(thread_index++)) { + if (listed_thread->GetID() != thread_to_skip) + m_stop_notification_queue.push_back( + PrepareStopReplyPacketForThread(*listed_thread).GetString().str()); + } +} + void GDBRemoteCommunicationServerLLGS::HandleInferiorState_Exited( NativeProcessProtocol *process) { assert(process && "process cannot be NULL"); @@ -983,8 +1032,8 @@ Log *log = GetLog(LLDBLog::Process); LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s called", __FUNCTION__); - PacketResult result = - SendStopReasonForState(*process, StateType::eStateExited); + PacketResult result = SendStopReasonForState( + *process, StateType::eStateExited, /*force_synchronous=*/false); if (result != PacketResult::Success) { LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s failed to send stop " @@ -996,9 +1045,13 @@ // up. MaybeCloseInferiorTerminalConnection(); - // We are ready to exit the debug monitor. - m_exit_now = true; - m_mainloop.RequestTermination(); + // When running in non-stop mode, wait for the vStopped to clear + // the notification queue. + if (!m_non_stop) { + // We are ready to exit the debug monitor. + m_exit_now = true; + m_mainloop.RequestTermination(); + } } void GDBRemoteCommunicationServerLLGS::HandleInferiorState_Stopped( @@ -1016,8 +1069,8 @@ break; default: // In all other cases, send the stop reason. - PacketResult result = - SendStopReasonForState(*process, StateType::eStateStopped); + PacketResult result = SendStopReasonForState( + *process, StateType::eStateStopped, /*force_synchronous=*/false); if (result != PacketResult::Success) { LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s failed to send stop " @@ -1373,9 +1426,11 @@ LLDB_LOG(log, "Failed to kill debugged process {0}: {1}", m_current_process->GetID(), error); - // No OK response for kill packet. - // return SendOKResponse (); - return PacketResult::Success; + // The response to kill packet is undefined per the spec. LLDB + // follows the same rules as for continue packets, i.e. no response + // in all-stop mode, and "OK" in non-stop mode; in both cases this + // is followed by the actual stop reason. + return SendContinueSuccessResponse(); } GDBRemoteCommunication::PacketResult @@ -1503,8 +1558,9 @@ return SendErrorResponse(0x38); } - // Don't send an "OK" packet; response is the stopped/exited message. - return PacketResult::Success; + // Don't send an "OK" packet, except in non-stop mode; + // otherwise, the response is the stopped/exited message. + return SendContinueSuccessResponse(); } GDBRemoteCommunication::PacketResult @@ -1543,8 +1599,8 @@ } LLDB_LOG(log, "continued process {0}", m_continue_process->GetID()); - // No response required from continue. - return PacketResult::Success; + + return SendContinueSuccessResponse(); } GDBRemoteCommunication::PacketResult @@ -1582,6 +1638,9 @@ // Move past the ';', then do a simple 's'. packet.SetFilePos(packet.GetFilePos() + 1); return Handle_s(packet); + } else if (m_non_stop && ::strcmp(packet.Peek(), ";t") == 0) { + // TODO: add full support for "t" action + return SendOKResponse(); } // Ensure we have a native process. @@ -1659,8 +1718,8 @@ } LLDB_LOG(log, "continued process {0}", m_continue_process->GetID()); - // No response required from vCont. - return PacketResult::Success; + + return SendContinueSuccessResponse(); } void GDBRemoteCommunicationServerLLGS::SetCurrentThreadID(lldb::tid_t tid) { @@ -1688,13 +1747,38 @@ if (!m_current_process) return SendErrorResponse(02); + if (m_non_stop) { + // Clear the notification queue first, except for pending exit + // notifications. + llvm::erase_if(m_stop_notification_queue, [](const std::string &x) { + return x.front() != 'W' && x.front() != 'X'; + }); + + // Queue stop reply packets for all active threads. Start with the current + // thread (for clients that don't actually support multiple stop reasons). + NativeThreadProtocol *thread = m_current_process->GetCurrentThread(); + if (thread) + m_stop_notification_queue.push_back( + PrepareStopReplyPacketForThread(*thread).GetString().str()); + EnqueueStopReplyPackets(thread ? thread->GetID() : LLDB_INVALID_THREAD_ID); + + // If the notification queue is empty (i.e. everything is running), send OK. + if (m_stop_notification_queue.empty()) + return SendOKResponse(); + + // Send the first item from the new notification queue synchronously. + return SendPacketNoLock(m_stop_notification_queue.front()); + } + return SendStopReasonForState(*m_current_process, - m_current_process->GetState()); + m_current_process->GetState(), + /*force_synchronous=*/true); } GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::SendStopReasonForState( - NativeProcessProtocol &process, lldb::StateType process_state) { + NativeProcessProtocol &process, lldb::StateType process_state, + bool force_synchronous) { Log *log = GetLog(LLDBLog::Process); switch (process_state) { @@ -1714,7 +1798,7 @@ // Make sure we set the current thread so g and p packets return the data // the gdb will expect. SetCurrentThreadID(tid); - return SendStopReplyPacketForThread(process, tid); + return SendStopReplyPacketForThread(process, tid, force_synchronous); } case eStateInvalid: @@ -2806,8 +2890,9 @@ return SendErrorResponse(0x49); } - // No response here - the stop or exit will come from the resulting action. - return PacketResult::Success; + // No response here, unless in non-stop mode. + // Otherwise, the stop or exit will come from the resulting action. + return SendContinueSuccessResponse(); } llvm::Expected> @@ -3176,7 +3261,8 @@ // Notify we attached by sending a stop packet. assert(m_current_process); return SendStopReasonForState(*m_current_process, - m_current_process->GetState()); + m_current_process->GetState(), + /*force_synchronous=*/false); } GDBRemoteCommunication::PacketResult @@ -3208,7 +3294,8 @@ // Notify we attached by sending a stop packet. assert(m_current_process); return SendStopReasonForState(*m_current_process, - m_current_process->GetState()); + m_current_process->GetState(), + /*force_synchronous=*/false); } GDBRemoteCommunication::PacketResult @@ -3246,7 +3333,8 @@ // Notify we attached by sending a stop packet. assert(m_current_process); return SendStopReasonForState(*m_current_process, - m_current_process->GetState()); + m_current_process->GetState(), + /*force_synchronous=*/false); } GDBRemoteCommunication::PacketResult @@ -3277,7 +3365,8 @@ if (m_process_launch_error.Success()) { assert(m_current_process); return SendStopReasonForState(*m_current_process, - m_current_process->GetState()); + m_current_process->GetState(), + /*force_synchronous=*/true); } LLDB_LOG(log, "failed to launch exe: {0}", m_process_launch_error); } @@ -3349,7 +3438,8 @@ __FUNCTION__, packet.GetStringRef().data()); return SendErrorResponse(0x15); } - return SendStopReplyPacketForThread(*m_current_process, tid); + return SendStopReplyPacketForThread(*m_current_process, tid, + /*force_synchronous=*/true); } GDBRemoteCommunication::PacketResult @@ -3672,6 +3762,59 @@ return SendPacketNoLock(response.GetString()); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_QNonStop( + StringExtractorGDBRemote &packet) { + StringRef packet_str{packet.GetStringRef()}; + assert(packet_str.startswith("QNonStop:")); + packet_str.consume_front("QNonStop:"); + if (packet_str == "0") { + m_non_stop = false; + // TODO: stop all threads + } else if (packet_str == "1") { + m_non_stop = true; + } else + return SendErrorResponse(Status("Invalid QNonStop packet")); + return SendOKResponse(); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_vStopped( + StringExtractorGDBRemote &packet) { + // Per the protocol, the first message put into the queue is sent + // immediately. However, it remains the queue until the client ACKs + // it via vStopped -- then we pop it and send the next message. + // The process repeats until the last message in the queue is ACK-ed, + // in which case the vStopped packet sends an OK response. + + if (m_stop_notification_queue.empty()) + return SendErrorResponse(Status("No pending notification to ack")); + m_stop_notification_queue.pop_front(); + if (!m_stop_notification_queue.empty()) + return SendPacketNoLock(m_stop_notification_queue.front()); + // If this was the last notification and the process exited, terminate + // the server. + if (m_inferior_prev_state == eStateExited) { + m_exit_now = true; + m_mainloop.RequestTermination(); + } + return SendOKResponse(); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_vCtrlC( + StringExtractorGDBRemote &packet) { + if (!m_non_stop) + return SendErrorResponse(Status("vCtrl is only valid in non-stop mode")); + + PacketResult interrupt_res = Handle_interrupt(packet); + // If interrupting the process failed, pass the result through. + if (interrupt_res != PacketResult::Success) + return interrupt_res; + // Otherwise, vCtrlC should issue an OK response (normal interrupts do not). + return SendOKResponse(); +} + void GDBRemoteCommunicationServerLLGS::MaybeCloseInferiorTerminalConnection() { Log *log = GetLog(LLDBLog::Process); @@ -3855,6 +3998,7 @@ "QThreadSuffixSupported+", "QListThreadsInStopReply+", "qXfer:features:read+", + "QNonStop+", }); // report server-only features @@ -3909,6 +4053,11 @@ process.SetEnabledExtensions(flags); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::SendContinueSuccessResponse() { + return m_non_stop ? SendOKResponse() : PacketResult::Success; +} + std::string lldb_private::process_gdb_remote::LLGSArgToURL(llvm::StringRef url_arg, bool reverse_connect) { diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -150,6 +150,11 @@ return eServerPacketType_QMemTags; break; + case 'N': + if (PACKET_STARTS_WITH("QNonStop:")) + return eServerPacketType_QNonStop; + break; + case 'R': if (PACKET_STARTS_WITH("QRestoreRegisterState:")) return eServerPacketType_QRestoreRegisterState; @@ -369,6 +374,12 @@ return eServerPacketType_vCont_actions; if (PACKET_STARTS_WITH("vRun;")) return eServerPacketType_vRun; + if (PACKET_MATCHES("vStopped")) + return eServerPacketType_vStopped; + if (PACKET_MATCHES("vCtrlC")) + return eServerPacketType_vCtrlC; + break; + } break; case '_': diff --git a/lldb/test/API/tools/lldb-server/TestNonStop.py b/lldb/test/API/tools/lldb-server/TestNonStop.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/TestNonStop.py @@ -0,0 +1,165 @@ +from lldbsuite.test.lldbtest import * + +import gdbremote_testcase + + +class LldbGdbServerTestCase(gdbremote_testcase.GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def test_run(self): + self.build() + self.set_inferior_startup_launch() + thread_num = 3 + procs = self.prep_debug_monitor_and_inferior( + inferior_args=["thread:segfault"] + thread_num * ["thread:new"]) + self.test_sequence.add_log_lines( + ["read packet: $QNonStop:1#00", + "send packet: $OK#00", + "read packet: $c#63", + "send packet: $OK#00", + ], True) + self.expect_gdbremote_sequence() + + segv_signo = lldbutil.get_signal_number('SIGSEGV') + all_threads = set() + all_segv_threads = [] + + # we should get segfaults from all the threads + for segv_no in range(thread_num): + # first wait for the notification event + self.reset_test_sequence() + self.test_sequence.add_log_lines( + [{"direction": "send", + "regex": r"^%Stop:(T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);)", + "capture": {1: "packet", 2: "signo", 3: "thread_id"}, + }, + ], True) + m = self.expect_gdbremote_sequence() + del m["O_content"] + threads = [m] + + # then we may get events for the remaining threads + # (but note that not all threads may have been started yet) + while True: + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $vStopped#00", + {"direction": "send", + "regex": r"^\$(OK|T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);)", + "capture": {1: "packet", 2: "signo", 3: "thread_id"}, + }, + ], True) + m = self.expect_gdbremote_sequence() + if m["packet"] == "OK": + break + del m["O_content"] + threads.append(m) + + segv_threads = [] + other_threads = [] + for t in threads: + signo = int(t["signo"], 16) + if signo == segv_signo: + segv_threads.append(t["thread_id"]) + else: + self.assertEqual(signo, 0) + other_threads.append(t["thread_id"]) + + # verify that exactly one thread segfaulted + self.assertEqual(len(segv_threads), 1) + # we should get only one segv from every thread + self.assertNotIn(segv_threads[0], all_segv_threads) + all_segv_threads.extend(segv_threads) + # segv_threads + other_threads should always be a superset + # of all_threads, i.e. we should get states for all threads + # already started + self.assertFalse( + all_threads.difference(other_threads + segv_threads)) + all_threads.update(other_threads + segv_threads) + + # verify that `?` returns the same result + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $?#00", + ], True) + threads_verify = [] + while True: + self.test_sequence.add_log_lines( + [{"direction": "send", + "regex": r"^\$(OK|T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);)", + "capture": {1: "packet", 2: "signo", 3: "thread_id"}, + }, + ], True) + m = self.expect_gdbremote_sequence() + if m["packet"] == "OK": + break + del m["O_content"] + threads_verify.append(m) + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $vStopped#00", + ], True) + + self.assertEqual(threads, threads_verify) + + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $vCont;C{:02x}:{};c#00" + .format(segv_signo, segv_threads[0]), + "send packet: $OK#00", + ], True) + self.expect_gdbremote_sequence() + + # finally, verify that all threads have started + self.assertEqual(len(all_threads), thread_num + 1) + + def test_vCtrlC(self): + self.build() + self.set_inferior_startup_launch() + procs = self.prep_debug_monitor_and_inferior( + inferior_args=["thread:new"]) + self.test_sequence.add_log_lines( + ["read packet: $QNonStop:1#00", + "send packet: $OK#00", + "read packet: $c#63", + "send packet: $OK#00", + "read packet: $vCtrlC#00", + "send packet: $OK#00", + {"direction": "send", + "regex": r"^%Stop:T13", + }, + ], True) + self.expect_gdbremote_sequence() + + def test_exit(self): + self.build() + self.set_inferior_startup_launch() + procs = self.prep_debug_monitor_and_inferior() + self.test_sequence.add_log_lines( + ["read packet: $QNonStop:1#00", + "send packet: $OK#00", + "read packet: $c#63", + "send packet: $OK#00", + "send packet: %Stop:W00#00", + "read packet: $vStopped#00", + "send packet: $OK#00", + ], True) + self.expect_gdbremote_sequence() + + def test_exit_query(self): + self.build() + self.set_inferior_startup_launch() + procs = self.prep_debug_monitor_and_inferior() + self.test_sequence.add_log_lines( + ["read packet: $QNonStop:1#00", + "send packet: $OK#00", + "read packet: $c#63", + "send packet: $OK#00", + "send packet: %Stop:W00#00", + "read packet: $?#00", + "send packet: $W00#00", + "read packet: $vStopped#00", + "send packet: $OK#00", + ], True) + self.expect_gdbremote_sequence()