#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.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.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(&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.getConnection(this->connection, true) ); if (connection == nullptr) { return; } auto message = json::stringify(object, false); int32_t length = message.length(); connection->send(reinterpret_cast(&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.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(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(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(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(&segment)) { pathValue.add(*string); } else { pathValue.add(std::get(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(engine.getNetwork(), client); connectionEstablished = false; } std::vector DebuggingServer::pullEvents() { return std::move(breakpointEvents); } void DebuggingServer::setDisconnectAction(const std::string& action) { disconnectAction = action; }