mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-04-14 12:22:27 +00:00

This fixes the flakiness of the GDB remote reproducer during replay. It was caused by a combination sending one ACK to many from the replay server and the code that "flushes" any queued GDB remote packets in GDBRemoteCommunicationClient::HandshakeWithServer. The spurious ACK was the result of combining both implicit and explicit handling of ACKs in the replay server. The handshake consists of an ACK followed by an QStartNoAckMode. As long as we haven't seen any QStartNoAckMode, we were sending implicit acknowledgments. So the first ACK got acknowledged twice, once implicitly, and once as part of the replay. The reason we didn't notice this was the code in HandshakeWithServer that "waits for any responses that might have been queued up in the remote GDB server and flush them all". A 10ms timeout is used to move on when no packets are left. If the second ACK didn't make it within those 10ms, all packets were offset by one. llvm-svn: 356825
256 lines
8.3 KiB
C++
256 lines
8.3 KiB
C++
//===-- 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 <errno.h>
|
|
|
|
#include "lldb/Host/Config.h"
|
|
|
|
#include "GDBRemoteCommunicationReplayServer.h"
|
|
#include "ProcessGDBRemoteLog.h"
|
|
|
|
// C Includes
|
|
// C++ Includes
|
|
#include <cstring>
|
|
|
|
// 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;
|
|
|
|
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;
|
|
}
|
|
|
|
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<std::micro> timeout, Status &error, bool &interrupt, bool &quit) {
|
|
std::lock_guard<std::recursive_mutex> 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);
|
|
|
|
// If m_send_acks is true, we're before the handshake phase. We've already
|
|
// acknowledge the '+' packet so we're done here.
|
|
if (m_send_acks && 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're 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());
|
|
}
|
|
|
|
// 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<std::recursive_mutex> 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).
|
|
m_async_thread = ThreadLauncher::LaunchThread(
|
|
"<lldb.gdb-replay.async>",
|
|
GDBRemoteCommunicationReplayServer::AsyncThread, this, nullptr);
|
|
}
|
|
|
|
// Wait for handshake.
|
|
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue);
|
|
|
|
return m_async_thread.IsJoinable();
|
|
}
|
|
|
|
void GDBRemoteCommunicationReplayServer::StopAsyncThread() {
|
|
std::lock_guard<std::recursive_mutex> 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 {};
|
|
}
|