1 //===-- GDBRemoteCommunicationReplayServer.cpp ------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
11 #include "lldb/Host/Config.h"
13 #include "GDBRemoteCommunicationReplayServer.h"
14 #include "ProcessGDBRemoteLog.h"
21 #include "lldb/Host/ThreadLauncher.h"
22 #include "lldb/Utility/ConstString.h"
23 #include "lldb/Utility/Event.h"
24 #include "lldb/Utility/FileSpec.h"
25 #include "lldb/Utility/StreamString.h"
26 #include "lldb/Utility/StringExtractorGDBRemote.h"
30 using namespace lldb_private;
31 using namespace lldb_private::process_gdb_remote;
33 /// Check if the given expected packet matches the actual packet.
34 static bool unexpected(llvm::StringRef expected, llvm::StringRef actual) {
35 // The 'expected' string contains the raw data, including the leading $ and
36 // trailing checksum. The 'actual' string contains only the packet's content.
37 if (expected.contains(actual))
39 // Contains a PID which might be different.
40 if (expected.contains("vAttach"))
42 // Contains a ascii-hex-path.
43 if (expected.contains("QSetSTD"))
45 // Contains environment values.
46 if (expected.contains("QEnvironment"))
52 /// Check if we should reply to the given packet.
53 static bool skip(llvm::StringRef data) {
54 assert(!data.empty() && "Empty packet?");
56 // We've already acknowledge the '+' packet so we're done here.
60 /// Don't 't reply to ^C. We need this because of stop reply packets, which
61 /// are only returned when the target halts. Reproducers synchronize these
62 /// 'asynchronous' replies, by recording them as a regular replies to the
63 /// previous packet (e.g. vCont). As a result, we should ignore real
64 /// asynchronous requests.
65 if (data.data()[0] == 0x03)
71 GDBRemoteCommunicationReplayServer::GDBRemoteCommunicationReplayServer()
72 : GDBRemoteCommunication("gdb-replay", "gdb-replay.rx_packet"),
73 m_async_broadcaster(nullptr, "lldb.gdb-replay.async-broadcaster"),
75 Listener::MakeListener("lldb.gdb-replay.async-listener")),
76 m_async_thread_state_mutex(), m_skip_acks(false) {
77 m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue,
78 "async thread continue");
79 m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit,
80 "async thread should exit");
82 const uint32_t async_event_mask =
83 eBroadcastBitAsyncContinue | eBroadcastBitAsyncThreadShouldExit;
84 m_async_listener_sp->StartListeningForEvents(&m_async_broadcaster,
88 GDBRemoteCommunicationReplayServer::~GDBRemoteCommunicationReplayServer() {
92 GDBRemoteCommunication::PacketResult
93 GDBRemoteCommunicationReplayServer::GetPacketAndSendResponse(
94 Timeout<std::micro> timeout, Status &error, bool &interrupt, bool &quit) {
95 std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex);
97 StringExtractorGDBRemote packet;
98 PacketResult packet_result = WaitForPacketNoLock(packet, timeout, false);
100 if (packet_result != PacketResult::Success) {
101 if (!IsConnected()) {
102 error.SetErrorString("lost connection");
105 error.SetErrorString("timeout");
107 return packet_result;
110 m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue);
112 // Check if we should reply to this packet.
113 if (skip(packet.GetStringRef()))
114 return PacketResult::Success;
116 // This completes the handshake. Since m_send_acks was true, we can unset it
118 if (packet.GetStringRef() == "QStartNoAckMode")
121 // A QEnvironment packet is sent for every environment variable. If the
122 // number of environment variables is different during replay, the replies
123 // become out of sync.
124 if (packet.GetStringRef().find("QEnvironment") == 0)
125 return SendRawPacketNoLock("$OK#9a");
127 Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
128 while (!m_packet_history.empty()) {
129 // Pop last packet from the history.
130 GDBRemoteCommunicationHistory::Entry entry = m_packet_history.back();
131 m_packet_history.pop_back();
133 // We've handled the handshake implicitly before. Skip the packet and move
135 if (entry.packet.data == "+")
138 if (entry.type == GDBRemoteCommunicationHistory::ePacketTypeSend) {
139 if (unexpected(entry.packet.data, packet.GetStringRef())) {
141 "GDBRemoteCommunicationReplayServer expected packet: '{0}'",
143 LLDB_LOG(log, "GDBRemoteCommunicationReplayServer actual packet: '{0}'",
144 packet.GetStringRef());
145 assert(false && "Encountered unexpected packet during replay");
146 return PacketResult::ErrorSendFailed;
149 // Ignore QEnvironment packets as they're handled earlier.
150 if (entry.packet.data.find("QEnvironment") == 1) {
151 assert(m_packet_history.back().type ==
152 GDBRemoteCommunicationHistory::ePacketTypeRecv);
153 m_packet_history.pop_back();
159 if (entry.type == GDBRemoteCommunicationHistory::ePacketTypeInvalid) {
162 "GDBRemoteCommunicationReplayServer skipped invalid packet: '{0}'",
163 packet.GetStringRef());
168 "GDBRemoteCommunicationReplayServer replied to '{0}' with '{1}'",
169 packet.GetStringRef(), entry.packet.data);
170 return SendRawPacketNoLock(entry.packet.data);
175 return packet_result;
178 LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(
180 lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry>)
183 GDBRemoteCommunicationReplayServer::LoadReplayHistory(const FileSpec &path) {
184 auto error_or_file = MemoryBuffer::getFile(path.GetPath());
185 if (auto err = error_or_file.getError())
186 return errorCodeToError(err);
188 yaml::Input yin((*error_or_file)->getBuffer());
189 yin >> m_packet_history;
191 if (auto err = yin.error())
192 return errorCodeToError(err);
194 // We want to manipulate the vector like a stack so we need to reverse the
195 // order of the packets to have the oldest on at the back.
196 std::reverse(m_packet_history.begin(), m_packet_history.end());
198 return Error::success();
201 bool GDBRemoteCommunicationReplayServer::StartAsyncThread() {
202 std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex);
203 if (!m_async_thread.IsJoinable()) {
204 // Create a thread that watches our internal state and controls which
205 // events make it to clients (into the DCProcess event queue).
206 llvm::Expected<HostThread> async_thread = ThreadLauncher::LaunchThread(
207 "<lldb.gdb-replay.async>",
208 GDBRemoteCommunicationReplayServer::AsyncThread, this);
210 LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST),
211 "failed to launch host thread: {}",
212 llvm::toString(async_thread.takeError()));
215 m_async_thread = *async_thread;
218 // Wait for handshake.
219 m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue);
221 return m_async_thread.IsJoinable();
224 void GDBRemoteCommunicationReplayServer::StopAsyncThread() {
225 std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex);
227 if (!m_async_thread.IsJoinable())
230 // Request thread to stop.
231 m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit);
233 // Disconnect client.
237 m_async_thread.Join(nullptr);
238 m_async_thread.Reset();
241 void GDBRemoteCommunicationReplayServer::ReceivePacket(
242 GDBRemoteCommunicationReplayServer &server, bool &done) {
245 auto packet_result = server.GetPacketAndSendResponse(std::chrono::seconds(1),
246 error, interrupt, done);
247 if (packet_result != GDBRemoteCommunication::PacketResult::Success &&
249 GDBRemoteCommunication::PacketResult::ErrorReplyTimeout) {
252 server.m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue);
256 thread_result_t GDBRemoteCommunicationReplayServer::AsyncThread(void *arg) {
257 GDBRemoteCommunicationReplayServer *server =
258 (GDBRemoteCommunicationReplayServer *)arg;
264 if (server->m_async_listener_sp->GetEvent(event_sp, llvm::None)) {
265 const uint32_t event_type = event_sp->GetType();
266 if (event_sp->BroadcasterIs(&server->m_async_broadcaster)) {
267 switch (event_type) {
268 case eBroadcastBitAsyncContinue:
269 ReceivePacket(*server, done);
273 case eBroadcastBitAsyncThreadShouldExit: