While investigating an issue where a different packet was sent during replay I noticed how annoying it is that the existing assert doesn't specify what packet is actually different. It's printed to the log, but enabling logging has the potential to change LLDB's behavior. The same is true when debugging LLDB while it's replaying the reproducer. I replaced the assert with a printf of the unexpected packet followed by a fatal_error wrapped in ifndef NDEBUG. The behavior is the same as the previous assert, just with more/better context.
286 lines
9.5 KiB
C++
286 lines
9.5 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 "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();
|
|
|
|
// 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(entry.packet.data, packet.GetStringRef())) {
|
|
LLDB_LOG(log,
|
|
"GDBRemoteCommunicationReplayServer expected packet: '{0}'",
|
|
entry.packet.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", entry.packet.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 (entry.packet.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(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<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 {};
|
|
}
|