Index: lldb/include/lldb/Utility/StringExtractorGDBRemote.h =================================================================== --- lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -174,6 +174,8 @@ eServerPacketType_QMemTags, // write memory tags eServerPacketType_qLLDBSaveCore, + eServerPacketType_QNonStop, + eServerPacketType_vStopped, }; ServerPacketType GetServerPacketType() const; Index: lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ 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): Index: lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py +++ 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 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 @@ -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); 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 @@ -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) { Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ 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 = {}; @@ -115,6 +117,9 @@ PacketResult SendStopReplyPacketForThread(lldb::tid_t tid); + PacketResult SendStopReplyPacketForThread(NativeThreadProtocol &thread, + bool send_notify = true); + PacketResult SendStopReasonForState(lldb::StateType process_state); PacketResult Handle_k(StringExtractorGDBRemote &packet); @@ -217,6 +222,10 @@ PacketResult Handle_qSaveCore(StringExtractorGDBRemote &packet); + PacketResult Handle_QNonStop(StringExtractorGDBRemote &packet); + + PacketResult Handle_vStopped(StringExtractorGDBRemote &packet); + PacketResult Handle_g(StringExtractorGDBRemote &packet); PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet); Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -235,6 +235,13 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_qLLDBSaveCore, &GDBRemoteCommunicationServerLLGS::Handle_qSaveCore); + + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_QNonStop, + &GDBRemoteCommunicationServerLLGS::Handle_QNonStop); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_vStopped, + &GDBRemoteCommunicationServerLLGS::Handle_vStopped); } void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) { @@ -767,25 +774,31 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread( lldb::tid_t tid) { - Log *log = GetLog(LLDBLog::Process | LLDBLog::Thread); - // Ensure we have a debugged process. if (!m_current_process || (m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) return SendErrorResponse(50); - LLDB_LOG(log, "preparing packet for pid {0} tid {1}", - m_current_process->GetID(), tid); - // Ensure we can get info on the given thread. NativeThreadProtocol *thread = m_current_process->GetThreadByID(tid); if (!thread) return SendErrorResponse(51); + return SendStopReplyPacketForThread(*thread); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread( + NativeThreadProtocol &thread, bool send_notify) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Thread); + + LLDB_LOG(log, "preparing packet for pid {0} tid {1}", + m_current_process->GetID(), thread.GetID()); + // Grab the reason this thread stopped. struct ThreadStopInfo tid_stop_info; std::string description; - if (!thread->GetStopReason(tid_stop_info, description)) + if (!thread.GetStopReason(tid_stop_info, description)) return SendErrorResponse(52); // FIXME implement register handling for exec'd inferiors. @@ -801,17 +814,17 @@ LLDB_LOG( log, "pid {0}, tid {1}, got signal signo = {2}, reason = {3}, exc_type = {4}", - m_current_process->GetID(), tid, signum, int(tid_stop_info.reason), - tid_stop_info.details.exception.type); + m_current_process->GetID(), thread.GetID(), signum, + int(tid_stop_info.reason), tid_stop_info.details.exception.type); // Print the signal number. response.PutHex8(signum & 0xff); // Include the tid. - response.Printf("thread:%" PRIx64 ";", tid); + response.Printf("thread:%" PRIx64 ";", 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 +888,7 @@ char delimiter = ':'; for (NativeThreadProtocol *thread; (thread = m_current_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 +919,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 +936,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,7 +987,21 @@ tid_stop_info.details.fork.child_tid); } - return SendPacketNoLock(response.GetString()); + if (m_non_stop) { + PacketResult ret = SendNotificationPacketNoLock( + "Stop", m_stop_notification_queue, response.GetString()); + if (send_notify) { + // Queue notification events for the remaining threads. + uint32_t thread_index = 0; + while (NativeThreadProtocol *listed_thread = + m_current_process->GetThreadAtIndex(thread_index++)) { + if (listed_thread->GetID() != thread.GetID()) + SendStopReplyPacketForThread(*listed_thread, false); + } + } + return ret; + } else + return SendPacketNoLock(response.GetString()); } void GDBRemoteCommunicationServerLLGS::HandleInferiorState_Exited( @@ -3657,6 +3685,33 @@ 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) { + 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()); + return SendOKResponse(); +} + void GDBRemoteCommunicationServerLLGS::MaybeCloseInferiorTerminalConnection() { Log *log = GetLog(LLDBLog::Process); @@ -3840,6 +3895,7 @@ "QThreadSuffixSupported+", "QListThreadsInStopReply+", "qXfer:features:read+", + "QNonStop+", }); // report server-only features Index: lldb/source/Utility/StringExtractorGDBRemote.cpp =================================================================== --- lldb/source/Utility/StringExtractorGDBRemote.cpp +++ lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -148,6 +148,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; @@ -367,6 +372,10 @@ return eServerPacketType_vCont_actions; if (PACKET_STARTS_WITH("vRun;")) return eServerPacketType_vRun; + if (PACKET_MATCHES("vStopped")) + return eServerPacketType_vStopped; + break; + } break; case '_': Index: lldb/test/API/tools/lldb-server/TestLldbGdbServer.py =================================================================== --- lldb/test/API/tools/lldb-server/TestLldbGdbServer.py +++ lldb/test/API/tools/lldb-server/TestLldbGdbServer.py @@ -1410,3 +1410,80 @@ self.assertEqual(decoded[errno_idx], 0) # si_errno self.assertEqual(decoded[code_idx], SEGV_MAPERR) # si_code self.assertEqual(decoded[addr_idx], 0) # si_addr + + def test_QNonStop(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", + ], 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: "signo", 2: "thread_id"}, + }, + ], True) + threads = [self.expect_gdbremote_sequence()] + + # 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 + 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) + + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $vCont;C{:02x}:{};c#00" + .format(segv_signo, segv_threads[0]), + ], True) + self.expect_gdbremote_sequence() + + # finally, verify that all threads have started + self.assertEqual(len(all_threads), thread_num + 1)