316 lines
9.5 KiB
C++
316 lines
9.5 KiB
C++
#include "DebuggingServer.hpp"
|
|
|
|
#include "engine/Engine.hpp"
|
|
#include "network/Network.hpp"
|
|
#include "debug/Logger.hpp"
|
|
#include "coders/json.hpp"
|
|
|
|
using namespace devtools;
|
|
|
|
static debug::Logger logger("debug-server");
|
|
|
|
ClientConnection::~ClientConnection() {
|
|
if (auto connection = dynamic_cast<network::ReadableConnection*>(
|
|
network.getConnection(this->connection, true)
|
|
)) {
|
|
connection->close();
|
|
}
|
|
}
|
|
|
|
bool ClientConnection::initiate(network::ReadableConnection* connection) {
|
|
if (connection->available() < 8) {
|
|
return false;
|
|
}
|
|
char buffer[8] {};
|
|
char expected[8] {};
|
|
std::memcpy(expected, VCDBG_MAGIC, sizeof(VCDBG_MAGIC));
|
|
expected[6] = VCDBG_VERSION >> 8;
|
|
expected[7] = VCDBG_VERSION & 0xFF;
|
|
connection->recv(buffer, sizeof(VCDBG_MAGIC));
|
|
|
|
connection->send(expected, sizeof(VCDBG_MAGIC));
|
|
if (std::memcmp(expected, buffer, sizeof(VCDBG_MAGIC)) == 0) {
|
|
initiated = true;
|
|
return false;
|
|
} else {
|
|
connection->close(true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::string ClientConnection::read() {
|
|
auto connection = dynamic_cast<network::ReadableConnection*>(
|
|
network.getConnection(this->connection, true)
|
|
);
|
|
if (connection == nullptr) {
|
|
return "";
|
|
}
|
|
if (!initiated) {
|
|
if (initiate(connection)) {
|
|
return "";
|
|
}
|
|
}
|
|
if (messageLength == 0) {
|
|
if (connection->available() >= sizeof(int32_t)) {
|
|
int32_t length = 0;
|
|
connection->recv(reinterpret_cast<char*>(&length), sizeof(int32_t));
|
|
if (length <= 0) {
|
|
logger.error() << "invalid message length " << length;
|
|
} else {
|
|
logger.info() << "message length " << length;
|
|
messageLength = length;
|
|
}
|
|
}
|
|
} else if (connection->available() >= messageLength) {
|
|
std::string string(messageLength, 0);
|
|
connection->recv(string.data(), messageLength);
|
|
messageLength = 0;
|
|
return string;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
void ClientConnection::send(const dv::value& object) {
|
|
auto connection = dynamic_cast<network::ReadableConnection*>(
|
|
network.getConnection(this->connection, true)
|
|
);
|
|
if (connection == nullptr) {
|
|
return;
|
|
}
|
|
auto message = json::stringify(object, false);
|
|
int32_t length = message.length();
|
|
connection->send(reinterpret_cast<char*>(&length), sizeof(int32_t));
|
|
connection->send(message.data(), length);
|
|
}
|
|
|
|
void ClientConnection::sendResponse(const std::string& type) {
|
|
send(dv::object({{"type", type}}));
|
|
}
|
|
|
|
bool ClientConnection::alive() const {
|
|
return network.getConnection(this->connection, true) != nullptr;
|
|
}
|
|
|
|
static network::Server& create_tcp_server(
|
|
DebuggingServer& dbgServer, Engine& engine, int port
|
|
) {
|
|
auto& network = engine.getNetwork();
|
|
u64id_t serverId = network.openTcpServer(
|
|
port,
|
|
[&network, &dbgServer](u64id_t sid, u64id_t id) {
|
|
auto& connection = dynamic_cast<network::ReadableConnection&>(
|
|
*network.getConnection(id, true)
|
|
);
|
|
connection.setPrivate(true);
|
|
logger.info() << "connected client " << id << ": "
|
|
<< connection.getAddress() << ":"
|
|
<< connection.getPort();
|
|
dbgServer.setClient(id);
|
|
}
|
|
);
|
|
auto& server = *network.getServer(serverId, true);
|
|
server.setPrivate(true);
|
|
|
|
auto& tcpServer = dynamic_cast<network::TcpServer&>(server);
|
|
tcpServer.setMaxClientsConnected(1);
|
|
|
|
logger.info() << "tcp debugging server open at port " << server.getPort();
|
|
|
|
return tcpServer;
|
|
}
|
|
|
|
static network::Server& create_server(
|
|
DebuggingServer& dbgServer, Engine& engine, const std::string& serverString
|
|
) {
|
|
logger.info() << "starting debugging server";
|
|
|
|
size_t sepPos = serverString.find(':');
|
|
if (sepPos == std::string::npos) {
|
|
throw std::runtime_error("invalid debugging server configuration string");
|
|
}
|
|
auto transport = serverString.substr(0, sepPos);
|
|
if (transport == "tcp") {
|
|
int port;
|
|
try {
|
|
port = std::stoi(serverString.substr(sepPos + 1));
|
|
} catch (const std::exception& err) {
|
|
throw std::runtime_error("invalid tcp port");
|
|
}
|
|
return create_tcp_server(dbgServer, engine, port);
|
|
} else {
|
|
throw std::runtime_error(
|
|
"unsupported debugging server transport '" + transport + "'"
|
|
);
|
|
}
|
|
}
|
|
|
|
DebuggingServer::DebuggingServer(
|
|
Engine& engine, const std::string& serverString
|
|
)
|
|
: engine(engine),
|
|
server(create_server(*this, engine, serverString)),
|
|
connection(nullptr) {
|
|
}
|
|
|
|
DebuggingServer::~DebuggingServer() {
|
|
logger.info() << "stopping debugging server";
|
|
server.close();
|
|
}
|
|
|
|
|
|
bool DebuggingServer::update() {
|
|
if (connection == nullptr) {
|
|
return false;
|
|
}
|
|
std::string message = connection->read();
|
|
if (message.empty()) {
|
|
if (!connection->alive()) {
|
|
bool status = performCommand(disconnectAction, dv::object());
|
|
connection.reset();
|
|
return status;
|
|
}
|
|
return false;
|
|
}
|
|
logger.debug() << "received: " << message;
|
|
try {
|
|
auto obj = json::parse(message);
|
|
if (!obj.has("type")) {
|
|
logger.error() << "missing message type";
|
|
return false;
|
|
}
|
|
const auto& type = obj["type"].asString();
|
|
if (performCommand(type, obj)) {
|
|
connection->sendResponse("resumed");
|
|
return true;
|
|
}
|
|
} catch (const std::runtime_error& err) {
|
|
logger.error() << "could not to parse message: " << err.what();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DebuggingServer::performCommand(
|
|
const std::string& type, const dv::value& map
|
|
) {
|
|
if (!connectionEstablished && type == "connect") {
|
|
map.at("disconnect-action").get(disconnectAction);
|
|
connectionEstablished = true;
|
|
logger.info() << "client connection established";
|
|
connection->sendResponse("success");
|
|
}
|
|
if (!connectionEstablished) {
|
|
return false;
|
|
}
|
|
if (type == "terminate") {
|
|
engine.quit();
|
|
connection->sendResponse("success");
|
|
} else if (type == "detach") {
|
|
connection->sendResponse("success");
|
|
connection.reset();
|
|
engine.detachDebugger();
|
|
return false;
|
|
} else if (type == "set-breakpoint" || type == "remove-breakpoint") {
|
|
if (!map.has("source") || !map.has("line"))
|
|
return false;
|
|
breakpointEvents.push_back(DebuggingEvent {
|
|
type[0] == 's'
|
|
? DebuggingEventType::SET_BREAKPOINT
|
|
: DebuggingEventType::REMOVE_BREAKPOINT,
|
|
BreakpointEventDto {
|
|
map["source"].asString(),
|
|
static_cast<int>(map["line"].asInteger()),
|
|
}
|
|
});
|
|
} else if (type == "step" || type == "step-into-function") {
|
|
breakpointEvents.push_back(DebuggingEvent {
|
|
type == "step"
|
|
? DebuggingEventType::STEP
|
|
: DebuggingEventType::STEP_INTO_FUNCTION,
|
|
SignalEventDto {}
|
|
});
|
|
return true;
|
|
} else if (type == "resume") {
|
|
breakpointEvents.push_back(DebuggingEvent {
|
|
DebuggingEventType::RESUME, SignalEventDto {}});
|
|
return true;
|
|
} else if (type == "get-value") {
|
|
if (!map.has("frame") || !map.has("local") || !map.has("path"))
|
|
return false;
|
|
|
|
int frame = map["frame"].asInteger();
|
|
int localIndex = map["local"].asInteger();
|
|
|
|
ValuePath path;
|
|
for (const auto& segment : map["path"]) {
|
|
if (segment.isString()) {
|
|
path.emplace_back(segment.asString());
|
|
} else {
|
|
path.emplace_back(static_cast<int>(segment.asInteger()));
|
|
}
|
|
}
|
|
breakpointEvents.push_back(DebuggingEvent {
|
|
DebuggingEventType::GET_VALUE, GetValueEventDto {
|
|
frame, localIndex, std::move(path)
|
|
}
|
|
});
|
|
return true;
|
|
} else {
|
|
logger.error() << "unsupported command '" << type << "'";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DebuggingServer::pause(
|
|
std::string&& reason, std::string&& message, dv::value&& stackTrace
|
|
) {
|
|
if (connection == nullptr) {
|
|
return;
|
|
}
|
|
auto response = dv::object({{"type", std::string("paused")}});
|
|
if (!reason.empty()) {
|
|
response["reason"] = std::move(reason);
|
|
}
|
|
if (!message.empty()) {
|
|
response["message"] = std::move(message);
|
|
}
|
|
if (stackTrace != nullptr) {
|
|
response["stack"] = std::move(stackTrace);
|
|
}
|
|
connection->send(std::move(response));
|
|
engine.startPauseLoop();
|
|
}
|
|
|
|
void DebuggingServer::sendValue(
|
|
dv::value&& value, int frame, int local, ValuePath&& path
|
|
) {
|
|
auto pathValue = dv::list();
|
|
for (const auto& segment : path) {
|
|
if (auto string = std::get_if<std::string>(&segment)) {
|
|
pathValue.add(*string);
|
|
} else {
|
|
pathValue.add(std::get<int>(segment));
|
|
}
|
|
}
|
|
connection->send(dv::object({
|
|
{"type", std::string("value")},
|
|
{"frame", frame},
|
|
{"local", local},
|
|
{"path", std::move(pathValue)},
|
|
{"value", std::move(value)},
|
|
}));
|
|
}
|
|
|
|
void DebuggingServer::setClient(u64id_t client) {
|
|
connection =
|
|
std::make_unique<ClientConnection>(engine.getNetwork(), client);
|
|
connectionEstablished = false;
|
|
}
|
|
|
|
std::vector<DebuggingEvent> DebuggingServer::pullEvents() {
|
|
return std::move(breakpointEvents);
|
|
}
|
|
|
|
void DebuggingServer::setDisconnectAction(const std::string& action) {
|
|
disconnectAction = action;
|
|
}
|