//===-- GDBRemoteCommunicationReplayServer.cpp ------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #include "lldb/Host/Config.h" #include "GDBRemoteCommunicationReplayServer.h" #include "ProcessGDBRemoteLog.h" // C Includes // C++ Includes #include // Project includes #include "lldb/Host/ThreadLauncher.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/StringExtractorGDBRemote.h" using namespace llvm; using namespace lldb; using namespace lldb_private; using namespace lldb_private::process_gdb_remote; /// Check if the given expected packet matches the actual packet. static bool unexpected(llvm::StringRef expected, llvm::StringRef actual) { // The 'expected' string contains the raw data, including the leading $ and // trailing checksum. The 'actual' string contains only the packet's content. if (expected.contains(actual)) return false; // Contains a PID which might be different. if (expected.contains("vAttach")) return false; // Contains a ascii-hex-path. if (expected.contains("QSetSTD")) return false; // Contains environment values. if (expected.contains("QEnvironment")) return false; return true; } /// Check if we should reply to the given packet. static bool skip(llvm::StringRef data) { assert(!data.empty() && "Empty packet?"); // We've already acknowledge the '+' packet so we're done here. if (data == "+") return true; /// Don't 't reply to ^C. We need this because of stop reply packets, which /// are only returned when the target halts. Reproducers synchronize these /// 'asynchronous' replies, by recording them as a regular replies to the /// previous packet (e.g. vCont). As a result, we should ignore real /// asynchronous requests. if (data.data()[0] == 0x03) return true; return false; } GDBRemoteCommunicationReplayServer::GDBRemoteCommunicationReplayServer() : GDBRemoteCommunication("gdb-replay", "gdb-replay.rx_packet"), m_async_broadcaster(nullptr, "lldb.gdb-replay.async-broadcaster"), m_async_listener_sp( Listener::MakeListener("lldb.gdb-replay.async-listener")), m_async_thread_state_mutex(), m_skip_acks(false) { m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, "async thread continue"); m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, "async thread should exit"); const uint32_t async_event_mask = eBroadcastBitAsyncContinue | eBroadcastBitAsyncThreadShouldExit; m_async_listener_sp->StartListeningForEvents(&m_async_broadcaster, async_event_mask); } GDBRemoteCommunicationReplayServer::~GDBRemoteCommunicationReplayServer() { StopAsyncThread(); } GDBRemoteCommunication::PacketResult GDBRemoteCommunicationReplayServer::GetPacketAndSendResponse( Timeout timeout, Status &error, bool &interrupt, bool &quit) { std::lock_guard guard(m_async_thread_state_mutex); StringExtractorGDBRemote packet; PacketResult packet_result = WaitForPacketNoLock(packet, timeout, false); if (packet_result != PacketResult::Success) { if (!IsConnected()) { error.SetErrorString("lost connection"); quit = true; } else { error.SetErrorString("timeout"); } return packet_result; } m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); // Check if we should reply to this packet. if (skip(packet.GetStringRef())) return PacketResult::Success; // This completes the handshake. Since m_send_acks was true, we can unset it // already. if (packet.GetStringRef() == "QStartNoAckMode") m_send_acks = false; // A QEnvironment packet is sent for every environment variable. If the // number of environment variables is different during replay, the replies // become out of sync. if (packet.GetStringRef().find("QEnvironment") == 0) return SendRawPacketNoLock("$OK#9a"); Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); while (!m_packet_history.empty()) { // Pop last packet from the history. GDBRemoteCommunicationHistory::Entry entry = m_packet_history.back(); m_packet_history.pop_back(); // We've handled the handshake implicitly before. Skip the packet and move // on. if (entry.packet.data == "+") continue; if (entry.type == GDBRemoteCommunicationHistory::ePacketTypeSend) { if (unexpected(entry.packet.data, packet.GetStringRef())) { LLDB_LOG(log, "GDBRemoteCommunicationReplayServer expected packet: '{0}'", entry.packet.data); LLDB_LOG(log, "GDBRemoteCommunicationReplayServer actual packet: '{0}'", packet.GetStringRef()); assert(false && "Encountered unexpected packet during replay"); return PacketResult::ErrorSendFailed; } // Ignore QEnvironment packets as they're handled earlier. if (entry.packet.data.find("QEnvironment") == 1) { assert(m_packet_history.back().type == GDBRemoteCommunicationHistory::ePacketTypeRecv); m_packet_history.pop_back(); } continue; } if (entry.type == GDBRemoteCommunicationHistory::ePacketTypeInvalid) { LLDB_LOG( log, "GDBRemoteCommunicationReplayServer skipped invalid packet: '{0}'", packet.GetStringRef()); continue; } LLDB_LOG(log, "GDBRemoteCommunicationReplayServer replied to '{0}' with '{1}'", packet.GetStringRef(), entry.packet.data); return SendRawPacketNoLock(entry.packet.data); } quit = true; return packet_result; } LLVM_YAML_IS_DOCUMENT_LIST_VECTOR( std::vector< lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry>) llvm::Error GDBRemoteCommunicationReplayServer::LoadReplayHistory(const FileSpec &path) { auto error_or_file = MemoryBuffer::getFile(path.GetPath()); if (auto err = error_or_file.getError()) return errorCodeToError(err); yaml::Input yin((*error_or_file)->getBuffer()); yin >> m_packet_history; if (auto err = yin.error()) return errorCodeToError(err); // We want to manipulate the vector like a stack so we need to reverse the // order of the packets to have the oldest on at the back. std::reverse(m_packet_history.begin(), m_packet_history.end()); return Error::success(); } bool GDBRemoteCommunicationReplayServer::StartAsyncThread() { std::lock_guard guard(m_async_thread_state_mutex); if (!m_async_thread.IsJoinable()) { // Create a thread that watches our internal state and controls which // events make it to clients (into the DCProcess event queue). llvm::Expected async_thread = ThreadLauncher::LaunchThread( "", GDBRemoteCommunicationReplayServer::AsyncThread, this); if (!async_thread) { LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST), "failed to launch host thread: {}", llvm::toString(async_thread.takeError())); return false; } m_async_thread = *async_thread; } // Wait for handshake. m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); return m_async_thread.IsJoinable(); } void GDBRemoteCommunicationReplayServer::StopAsyncThread() { std::lock_guard guard(m_async_thread_state_mutex); if (!m_async_thread.IsJoinable()) return; // Request thread to stop. m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit); // Disconnect client. Disconnect(); // Stop the thread. m_async_thread.Join(nullptr); m_async_thread.Reset(); } void GDBRemoteCommunicationReplayServer::ReceivePacket( GDBRemoteCommunicationReplayServer &server, bool &done) { Status error; bool interrupt; auto packet_result = server.GetPacketAndSendResponse(std::chrono::seconds(1), error, interrupt, done); if (packet_result != GDBRemoteCommunication::PacketResult::Success && packet_result != GDBRemoteCommunication::PacketResult::ErrorReplyTimeout) { done = true; } else { server.m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); } } thread_result_t GDBRemoteCommunicationReplayServer::AsyncThread(void *arg) { GDBRemoteCommunicationReplayServer *server = (GDBRemoteCommunicationReplayServer *)arg; EventSP event_sp; bool done = false; while (true) { if (server->m_async_listener_sp->GetEvent(event_sp, llvm::None)) { const uint32_t event_type = event_sp->GetType(); if (event_sp->BroadcasterIs(&server->m_async_broadcaster)) { switch (event_type) { case eBroadcastBitAsyncContinue: ReceivePacket(*server, done); if (done) return {}; break; case eBroadcastBitAsyncThreadShouldExit: default: return {}; } } } } return {}; }