mirror of https://github.com/WerWolv/ImHex.git
provider: Added basic GDB Server provider
This commit is contained in:
parent
4a53717676
commit
cc5a437573
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <hex/helpers/socket.hpp>
|
||||
#include <hex/providers/provider.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
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<std::pair<std::string, std::string>> 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<u8, 0x1000> data;
|
||||
};
|
||||
|
||||
std::list<CacheLine> m_cache;
|
||||
|
||||
std::jthread m_cacheUpdateThread;
|
||||
std::mutex m_cacheLock;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <hex.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/views/view.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/event.hpp>
|
||||
|
||||
#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<RequestCreateProvider>([](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<RequestCreateProvider>([](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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
#include "content/providers/gdb_provider.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
|
||||
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<std::string> 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<u8> 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<u8*>(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<std::pair<std::string, std::string>> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<prv::GDBProvider*>(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<prv::GDBProvider*>(provider) != nullptr;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <hex.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/sock.h>
|
||||
#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<u8> readBytes(size_t size = 0x1000) const;
|
||||
|
||||
void writeString(const std::string &string) const;
|
||||
void writeBytes(const std::vector<u8> &bytes) const;
|
||||
|
||||
private:
|
||||
bool m_connected = false;
|
||||
#if defined(OS_WINDOWS)
|
||||
SOCKET m_socket = INVALID_SOCKET;
|
||||
#else
|
||||
int m_socket = -1;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
#include <hex/helpers/socket.hpp>
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
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<u8> &bytes) const {
|
||||
if (!this->isConnected()) return;
|
||||
|
||||
::send(this->m_socket, reinterpret_cast<const char*>(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<u8> Socket::readBytes(size_t size) const {
|
||||
std::vector<u8> data;
|
||||
data.resize(size);
|
||||
|
||||
auto receivedSize = ::recv(this->m_socket, reinterpret_cast<char *>(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<SOCKADDR*>(&client), sizeof(client)) != SOCKET_ERROR;
|
||||
}
|
||||
|
||||
void Socket::disconnect() {
|
||||
if (this->m_socket != INVALID_SOCKET)
|
||||
closesocket(this->m_socket);
|
||||
|
||||
this->m_connected = false;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue