You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
10 KiB
315 lines
10 KiB
4 months ago
|
//===-- GDBRemoteCommunicationReplayServer.cpp ----------------------------===//
|
||
|
//
|
||
|
// 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 "llvm/ADT/ScopeExit.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;
|
||
|
|
||
|
/// 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<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);
|
||
|
|
||
|
// 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.
|
||
|
GDBRemotePacket entry = m_packet_history.back();
|
||
|
m_packet_history.pop_back();
|
||
|
|
||
|
// Decode run-length encoding.
|
||
|
const std::string expanded_data =
|
||
|
GDBRemoteCommunication::ExpandRLE(entry.packet.data);
|
||
|
|
||
|
// We've handled the handshake implicitly before. Skip the packet and move
|
||
|
// on.
|
||
|
if (entry.packet.data == "+")
|
||
|
continue;
|
||
|
|
||
|
if (entry.type == GDBRemotePacket::ePacketTypeSend) {
|
||
|
if (unexpected(expanded_data, packet.GetStringRef())) {
|
||
|
LLDB_LOG(log,
|
||
|
"GDBRemoteCommunicationReplayServer expected packet: '{0}'",
|
||
|
expanded_data);
|
||
|
LLDB_LOG(log, "GDBRemoteCommunicationReplayServer actual packet: '{0}'",
|
||
|
packet.GetStringRef());
|
||
|
#ifndef NDEBUG
|
||
|
// This behaves like a regular assert, but prints the expected and
|
||
|
// received packet before aborting.
|
||
|
printf("Reproducer expected packet: '%s'\n", expanded_data.c_str());
|
||
|
printf("Reproducer received packet: '%s'\n",
|
||
|
packet.GetStringRef().data());
|
||
|
llvm::report_fatal_error("Encountered unexpected packet during replay");
|
||
|
#endif
|
||
|
return PacketResult::ErrorSendFailed;
|
||
|
}
|
||
|
|
||
|
// Ignore QEnvironment packets as they're handled earlier.
|
||
|
if (expanded_data.find("QEnvironment") == 1) {
|
||
|
assert(m_packet_history.back().type ==
|
||
|
GDBRemotePacket::ePacketTypeRecv);
|
||
|
m_packet_history.pop_back();
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (entry.type == GDBRemotePacket::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::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).
|
||
|
llvm::Expected<HostThread> async_thread = ThreadLauncher::LaunchThread(
|
||
|
"<lldb.gdb-replay.async>",
|
||
|
GDBRemoteCommunicationReplayServer::AsyncThread, this);
|
||
|
if (!async_thread) {
|
||
|
LLDB_LOG_ERROR(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST),
|
||
|
async_thread.takeError(),
|
||
|
"failed to launch host thread: {}");
|
||
|
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<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;
|
||
|
auto D = make_scope_exit([&]() { server->Disconnect(); });
|
||
|
EventSP event_sp;
|
||
|
bool done = false;
|
||
|
while (!done) {
|
||
|
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 {};
|
||
|
}
|
||
|
|
||
|
Status GDBRemoteCommunicationReplayServer::Connect(
|
||
|
process_gdb_remote::GDBRemoteCommunicationClient &client) {
|
||
|
repro::Loader *loader = repro::Reproducer::Instance().GetLoader();
|
||
|
if (!loader)
|
||
|
return Status("No loader provided.");
|
||
|
|
||
|
static std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
|
||
|
multi_loader = repro::MultiLoader<repro::GDBRemoteProvider>::Create(
|
||
|
repro::Reproducer::Instance().GetLoader());
|
||
|
if (!multi_loader)
|
||
|
return Status("No gdb remote provider found.");
|
||
|
|
||
|
llvm::Optional<std::string> history_file = multi_loader->GetNextFile();
|
||
|
if (!history_file)
|
||
|
return Status("No gdb remote packet log found.");
|
||
|
|
||
|
if (auto error = LoadReplayHistory(FileSpec(*history_file)))
|
||
|
return Status("Unable to load replay history");
|
||
|
|
||
|
if (auto error = GDBRemoteCommunication::ConnectLocally(client, *this))
|
||
|
return Status("Unable to connect to replay server");
|
||
|
|
||
|
return {};
|
||
|
}
|