diff --git a/plugins/builtin/include/content/providers/gdb_provider.hpp b/plugins/builtin/include/content/providers/gdb_provider.hpp new file mode 100644 index 000000000..5cb5aa9be --- /dev/null +++ b/plugins/builtin/include/content/providers/gdb_provider.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace hex::plugin::builtin::prv { + +class GDBProvider : public hex::prv::Provider { + public: + explicit GDBProvider(); + ~GDBProvider() override; + + bool isAvailable() const override; + bool isReadable() const override; + bool isWritable() const override; + bool isResizable() const override; + bool isSavable() const override; + + void read(u64 offset, void *buffer, size_t size, bool overlays) override; + void write(u64 offset, const void *buffer, size_t size) override; + void resize(ssize_t newSize) override; + + void readRaw(u64 offset, void *buffer, size_t size) override; + void writeRaw(u64 offset, const void *buffer, size_t size) override; + size_t getActualSize() const override; + + void save() override; + void saveAs(const std::string &path) override; + + [[nodiscard]] std::string getName() const override; + [[nodiscard]] std::vector> getDataInformation() const override; + + void connect(const std::string &address, u16 port); + void disconnect(); + bool isConnected(); + + private: + hex::Socket m_socket; + + std::string m_ipAddress; + u16 m_port; + + struct CacheLine { + u64 address; + + std::array data; + }; + + std::list m_cache; + + std::jthread m_cacheUpdateThread; + std::mutex m_cacheLock; + }; + +} \ No newline at end of file diff --git a/plugins/builtin/include/content/views/view_gdb.hpp b/plugins/builtin/include/content/views/view_gdb.hpp new file mode 100644 index 000000000..42447eaba --- /dev/null +++ b/plugins/builtin/include/content/views/view_gdb.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +#include +#include + +namespace hex::plugin::builtin { + + class ViewGDB : public hex::View { + public: + ViewGDB(); + + void drawContent() override; + + bool hasViewMenuItemEntry() override; + + bool isAvailable(); + + private: + std::string m_address; + int m_port = 0; + }; + +} \ No newline at end of file diff --git a/plugins/builtin/source/content/providers.cpp b/plugins/builtin/source/content/providers.cpp new file mode 100644 index 000000000..184c10da8 --- /dev/null +++ b/plugins/builtin/source/content/providers.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include "content/providers/file_provider.hpp" +#include "content/providers/gdb_provider.hpp" + +namespace hex::plugin::builtin { + + void registerProviders() { + ContentRegistry::Provider::add("hex.builtin.provider.gdb"); + + (void) EventManager::subscribe([](const std::string &unlocalizedName, hex::prv::Provider **provider){ + if (unlocalizedName != "hex.builtin.provider.file") return; + + auto newProvider = new prv::FileProvider(); + + hex::ImHexApi::Provider::add(newProvider); + + if (provider != nullptr) + *provider = newProvider; + }); + + (void) EventManager::subscribe([](const std::string &unlocalizedName, hex::prv::Provider **provider){ + if (unlocalizedName != "hex.builtin.provider.gdb") return; + + auto newProvider = new prv::GDBProvider(); + + hex::ImHexApi::Provider::add(newProvider); + + if (provider != nullptr) + *provider = newProvider; + }); + } + +} \ No newline at end of file diff --git a/plugins/builtin/source/content/providers/gdb_provider.cpp b/plugins/builtin/source/content/providers/gdb_provider.cpp new file mode 100644 index 000000000..6546ff9d0 --- /dev/null +++ b/plugins/builtin/source/content/providers/gdb_provider.cpp @@ -0,0 +1,265 @@ +#include "content/providers/gdb_provider.hpp" + +#include +#include +#include + +#include +#include + +namespace hex::plugin::builtin::prv { + + using namespace std::chrono_literals; + + namespace gdb { + + namespace { + + u8 calculateChecksum(const std::string &data) { + u64 checksum = 0; + + for (const auto &c : data) + checksum += c; + + return checksum & 0xFF; + } + + std::string createPacket(const std::string &data) { + return hex::format("${}#{:02x}", data, calculateChecksum(data)); + } + + std::optional parsePacket(const std::string &packet) { + if (packet.length() < 4) + return std::nullopt; + + if (!packet.starts_with('$')) + return std::nullopt; + + if (packet[packet.length() - 3] != '#') + return std::nullopt; + + std::string data = packet.substr(1, packet.length() - 4); + std::string checksum = packet.substr(packet.length() - 2, 2); + + if (checksum.length() != 2 || crypt::decode16(checksum)[0] != calculateChecksum(data)) + return std::nullopt; + + return data; + } + + } + + void sendAck(Socket &socket) { + socket.writeString("+"); + } + + std::vector readMemory(Socket &socket, u64 address, size_t size) { + std::string packet = createPacket(hex::format("m{:X},{:X}", address, size)); + + socket.writeString(packet); + + auto receivedPacket = socket.readString(size * 2 + 4); + + if (receivedPacket.empty()) + return { }; + + auto receivedData = parsePacket(receivedPacket); + if (!receivedData.has_value()) + return { }; + + if (receivedData->size() == 3 && receivedData->starts_with("E")) + return { }; + + auto data = crypt::decode16(receivedData.value()); + + data.resize(size); + + return data; + } + + bool enableNoAckMode(Socket &socket) { + socket.writeString(createPacket("QStartNoAckMode")); + + auto ack = socket.readString(1); + + if (ack.empty() || ack[0] != '+') + return {}; + + auto receivedPacket = socket.readString(6); + + auto receivedData = parsePacket(receivedPacket); + + if (receivedData && *receivedData == "OK") { + sendAck(socket); + return true; + } else { + return false; + } + } + + } + + GDBProvider::GDBProvider() : Provider() { + + } + + GDBProvider::~GDBProvider() { + this->disconnect(); + } + + + bool GDBProvider::isAvailable() const { + return true; + } + + bool GDBProvider::isReadable() const { + return this->m_socket.isConnected(); + } + + bool GDBProvider::isWritable() const { + return false; + } + + bool GDBProvider::isResizable() const { + return false; + } + + bool GDBProvider::isSavable() const { + return false; + } + + + void GDBProvider::read(u64 offset, void *buffer, size_t size, bool overlays) { + if ((offset - this->getBaseAddress()) > (this->getSize() - size) || buffer == nullptr || size == 0) + return; + + u64 alignedOffset = offset - (offset & 0xFFF); + + { + std::scoped_lock lock(this->m_cacheLock); + + const auto &cacheLine = std::find_if(this->m_cache.begin(), this->m_cache.end(), [&](auto &line){ + return line.address == alignedOffset; + }); + + if (cacheLine != this->m_cache.end()) { + // Cache hit + + } else { + // Cache miss + + this->m_cache.push_back({ alignedOffset, { 0 } }); + } + + if (cacheLine != this->m_cache.end()) + std::memcpy(buffer, &cacheLine->data[0] + (offset & 0xFFF), size); + } + + for (u64 i = 0; i < size; i++) + if (getPatches().contains(offset + i)) + reinterpret_cast(buffer)[i] = getPatches()[offset + PageSize * this->m_currPage + i]; + + if (overlays) + this->applyOverlays(offset, buffer, size); + } + + void GDBProvider::write(u64 offset, const void *buffer, size_t size) { + if (((offset - this->getBaseAddress()) + size) > this->getSize() || buffer == nullptr || size == 0) + return; + + addPatch(offset, buffer, size); + } + + void GDBProvider::readRaw(u64 offset, void *buffer, size_t size) { + offset -= this->getBaseAddress(); + + if ((offset + size) > this->getSize() || buffer == nullptr || size == 0) + return; + + auto data = gdb::readMemory(this->m_socket, offset, size); + std::memcpy(buffer, &data[0], data.size()); + } + + void GDBProvider::writeRaw(u64 offset, const void *buffer, size_t size) { + offset -= this->getBaseAddress(); + + if ((offset + size) > this->getSize() || buffer == nullptr || size == 0) + return; + } + + void GDBProvider::save() { + this->applyPatches(); + } + + void GDBProvider::saveAs(const std::string &path) { + + } + + void GDBProvider::resize(ssize_t newSize) { + + } + + size_t GDBProvider::getActualSize() const { + return 0xFFFF'FFFF; + } + + std::string GDBProvider::getName() const { + return hex::format("hex.builtin.provider.gdb.name"_lang, this->m_ipAddress, this->m_port); + } + + std::vector> GDBProvider::getDataInformation() const { + return { }; + } + + void GDBProvider::connect(const std::string &address, u16 port) { + this->m_socket.connect(address, port); + if (!gdb::enableNoAckMode(this->m_socket)) { + this->m_socket.disconnect(); + return; + } + + this->m_ipAddress = address; + this->m_port = port; + + if (this->m_socket.isConnected()) { + this->m_cacheUpdateThread = std::jthread([this](const std::stop_token& stopToken) { + auto cacheLine = this->m_cache.begin(); + while (!stopToken.stop_requested()) { + { + std::scoped_lock lock(this->m_cacheLock); + + if (cacheLine != this->m_cache.end()) { + auto data = gdb::readMemory(this->m_socket, cacheLine->address, 0x1000); + + while (this->m_cache.size() > 10) { + this->m_cache.pop_front(); + cacheLine = this->m_cache.begin(); + } + + std::memcpy(cacheLine->data.data(), data.data(), data.size()); + } + + cacheLine++; + if (cacheLine == this->m_cache.end()) + cacheLine = this->m_cache.begin(); + } + std::this_thread::sleep_for(100ms); + } + }); + } + } + + void GDBProvider::disconnect() { + this->m_socket.disconnect(); + + if (this->m_cacheUpdateThread.joinable()) { + this->m_cacheUpdateThread.request_stop(); + this->m_cacheUpdateThread.join(); + } + } + + bool GDBProvider::isConnected() { + return this->m_socket.isConnected(); + } + +} \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_gdb.cpp b/plugins/builtin/source/content/views/view_gdb.cpp new file mode 100644 index 000000000..05c42cc72 --- /dev/null +++ b/plugins/builtin/source/content/views/view_gdb.cpp @@ -0,0 +1,46 @@ +#include "content/views/view_gdb.hpp" + +#include "content/providers/gdb_provider.hpp" + +namespace hex::plugin::builtin { + + ViewGDB::ViewGDB() : hex::View("hex.builtin.view.gdb.name") { + this->m_address.reserve(3 * 4 + 4); + this->m_port = 0; + } + + void ViewGDB::drawContent() { + if (ImGui::Begin(View::toWindowName("hex.builtin.view.gdb.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { + ImGui::Header("hex.builtin.view.gdb.settings"_lang); + ImGui::InputText("hex.builtin.view.gdb.ip"_lang, this->m_address.data(), this->m_address.capacity(), ImGuiInputTextFlags_CallbackEdit, ImGui::UpdateStringSizeCallback, &this->m_address); + ImGui::InputInt("hex.builtin.view.gdb.port"_lang, &this->m_port, 1, 0xFFFF); + + ImGui::NewLine(); + + auto provider = dynamic_cast(hex::ImHexApi::Provider::get()); + if (provider != nullptr) { + if (!provider->isConnected()) { + if (ImGui::Button("hex.builtin.view.gdb.connect"_lang)) { + provider->connect(this->m_address, this->m_port); + } + } else { + if (ImGui::Button("hex.builtin.view.gdb.disconnect"_lang)) { + provider->disconnect(); + } + } + } + } + ImGui::End(); + } + + bool ViewGDB::hasViewMenuItemEntry() { + return this->isAvailable(); + } + + bool ViewGDB::isAvailable() { + auto provider = hex::ImHexApi::Provider::get(); + + return provider != nullptr && dynamic_cast(provider) != nullptr; + } + +} \ No newline at end of file diff --git a/plugins/libimhex/include/hex/helpers/socket.hpp b/plugins/libimhex/include/hex/helpers/socket.hpp new file mode 100644 index 000000000..ff5d60970 --- /dev/null +++ b/plugins/libimhex/include/hex/helpers/socket.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include + +#if defined(OS_WINDOWS) + #include + #include +#else + #include + #include +#endif + +namespace hex { + + class Socket { + public: + Socket() = default; + Socket(const Socket&) = delete; + Socket(Socket &&other); + + Socket(const std::string &address, u16 port); + ~Socket(); + + void connect(const std::string &address, u16 port); + void disconnect(); + + [[nodiscard]] + bool isConnected() const; + + std::string readString(size_t size = 0x1000) const; + std::vector readBytes(size_t size = 0x1000) const; + + void writeString(const std::string &string) const; + void writeBytes(const std::vector &bytes) const; + + private: + bool m_connected = false; + #if defined(OS_WINDOWS) + SOCKET m_socket = INVALID_SOCKET; + #else + int m_socket = -1; + #endif + }; + +} \ No newline at end of file diff --git a/plugins/libimhex/source/helpers/socket.cpp b/plugins/libimhex/source/helpers/socket.cpp new file mode 100644 index 000000000..1337cec4a --- /dev/null +++ b/plugins/libimhex/source/helpers/socket.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include + +#include + +namespace hex { + + Socket::Socket(const std::string &address, u16 port) { + #if defined(OS_WINDOWS) + FIRST_TIME { + WSAData wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + }; + + FINAL_CLEANUP { + WSACleanup(); + }; + #endif + + this->connect(address, port); + } + + Socket::Socket(Socket &&other) { + this->m_socket = other.m_socket; + this->m_connected = other.m_connected; + + other.m_socket = INVALID_SOCKET; + } + + Socket::~Socket() { + this->disconnect(); + } + + void Socket::writeBytes(const std::vector &bytes) const { + if (!this->isConnected()) return; + + ::send(this->m_socket, reinterpret_cast(bytes.data()), bytes.size(), 0); + } + + void Socket::writeString(const std::string &string) const { + if (!this->isConnected()) return; + + ::send(this->m_socket, string.c_str(), string.length(), 0); + } + + std::vector Socket::readBytes(size_t size) const { + std::vector data; + data.resize(size); + + auto receivedSize = ::recv(this->m_socket, reinterpret_cast(data.data()), size, 0); + + if (receivedSize < 0) + return { }; + + data.resize(receivedSize); + + return data; + } + + std::string Socket::readString(size_t size) const { + auto bytes = readBytes(size); + + std::string result; + std::copy(bytes.begin(), bytes.end(), std::back_inserter(result)); + + return result; + } + + bool Socket::isConnected() const { + return this->m_connected; + } + + void Socket::connect(const std::string &address, u16 port) { + this->m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (this->m_socket == INVALID_SOCKET) + return; + + sockaddr_in client = { 0 }; + + client.sin_family = AF_INET; + client.sin_addr.S_un.S_addr = ::inet_addr(address.c_str()); + client.sin_port = ::htons(port); + + this->m_connected = ::connect(this->m_socket, reinterpret_cast(&client), sizeof(client)) != SOCKET_ERROR; + } + + void Socket::disconnect() { + if (this->m_socket != INVALID_SOCKET) + closesocket(this->m_socket); + + this->m_connected = false; + } + +} \ No newline at end of file