mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-28 07:47:03 -05:00
339 lines
10 KiB
C++
339 lines
10 KiB
C++
#include "content/providers/gdb_provider.hpp"
|
|
|
|
#include <cstring>
|
|
#include <thread>
|
|
#include <chrono>
|
|
|
|
#include <imgui.h>
|
|
#include <hex/ui/imgui_imhex_extensions.h>
|
|
|
|
#include <hex/helpers/fmt.hpp>
|
|
#include <hex/helpers/crypto.hpp>
|
|
#include <hex/api/localization_manager.hpp>
|
|
#include <hex/helpers/logger.hpp>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
namespace hex::plugin::builtin {
|
|
|
|
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 fmt::format("${}#{:02x}", data, calculateChecksum(data));
|
|
}
|
|
|
|
std::optional<std::string> parsePacket(const std::string &packet) {
|
|
if (packet.length() < 4 || packet[0] != '$')
|
|
return std::nullopt;
|
|
|
|
size_t hashPos = packet.find('#');
|
|
if (hashPos == std::string::npos || hashPos + 2 >= packet.size())
|
|
return std::nullopt;
|
|
|
|
std::string data = packet.substr(1, hashPos - 1);
|
|
std::string checksumStr = packet.substr(hashPos + 1, 2);
|
|
|
|
auto decodedChecksum = crypt::decode16(checksumStr);
|
|
if (decodedChecksum.empty() || decodedChecksum[0] != calculateChecksum(data))
|
|
return std::nullopt;
|
|
|
|
return data;
|
|
}
|
|
|
|
void sendAck(const wolv::net::SocketClient &socket) {
|
|
socket.writeString("+");
|
|
}
|
|
|
|
char readCharacter(const wolv::net::SocketClient &socket) {
|
|
auto data = socket.readBytes(1);
|
|
return data.empty() ? '\0' : static_cast<char>(data[0]);
|
|
}
|
|
|
|
std::string sendReceivePackage(const wolv::net::SocketClient &socket, const std::string &packet) {
|
|
socket.writeString(packet);
|
|
|
|
i32 retries = 20;
|
|
|
|
std::string buffer;
|
|
while (true) {
|
|
char c = readCharacter(socket);
|
|
if (c == '+') {
|
|
// ACK response, continue reading
|
|
continue;
|
|
} else if (c == '$') {
|
|
// Start of response packet
|
|
buffer += c;
|
|
|
|
// Read until the end of the packet
|
|
while (true) {
|
|
c = readCharacter(socket);
|
|
buffer += c;
|
|
if (c == '#')
|
|
break;
|
|
}
|
|
|
|
// Read the checksum
|
|
buffer += readCharacter(socket);
|
|
buffer += readCharacter(socket);
|
|
break;
|
|
} else if (c == '-') {
|
|
// NAK response, retry sending the packet
|
|
socket.writeString(packet);
|
|
retries -= 1;
|
|
} else if (c == 0x00) {
|
|
// No response, wait a bit and retry receiving data
|
|
std::this_thread::sleep_for(10ms);
|
|
retries -= 1;
|
|
}
|
|
|
|
if (retries <= 0) {
|
|
log::error("No response from GDB server after multiple retries");
|
|
return {};
|
|
}
|
|
}
|
|
|
|
if (!buffer.empty())
|
|
sendAck(socket);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
std::vector<u8> decodeMemoryResponse(const std::string &response) {
|
|
std::string expanded;
|
|
size_t i = 0;
|
|
|
|
while (i < response.size()) {
|
|
char c = response[i];
|
|
|
|
if (i + 2 < response.size() && response[i + 1] == '*') {
|
|
char repeatChar = response[i + 2];
|
|
int repeatCount = static_cast<unsigned char>(repeatChar) - 29;
|
|
|
|
if (repeatCount <= 0)
|
|
return {};
|
|
|
|
expanded.append(repeatCount, c);
|
|
i += 3;
|
|
} else {
|
|
expanded.push_back(c);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (expanded.size() % 2 != 0)
|
|
return {};
|
|
|
|
std::vector<u8> decoded;
|
|
for (size_t j = 0; j < expanded.size(); j += 2) {
|
|
std::string byteStr = expanded.substr(j, 2);
|
|
try {
|
|
u8 byte = static_cast<u8>(std::stoul(byteStr, nullptr, 16));
|
|
decoded.push_back(byte);
|
|
} catch (...) {
|
|
return { };
|
|
}
|
|
}
|
|
|
|
return decoded;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
std::vector<u8> readMemory(const wolv::net::SocketClient &socket, u64 address, size_t size) {
|
|
std::string packet = createPacket(fmt::format("m{:X},{:X}", address, size));
|
|
|
|
std::string receivedPacket = sendReceivePackage(socket, packet);
|
|
|
|
auto receivedData = parsePacket(receivedPacket);
|
|
if (!receivedData.has_value())
|
|
return {};
|
|
|
|
sendAck(socket);
|
|
|
|
if (receivedData->size() == 3 && receivedData->starts_with("E"))
|
|
return {};
|
|
|
|
auto data = decodeMemoryResponse(*receivedData);
|
|
data.resize(size);
|
|
return data;
|
|
}
|
|
|
|
bool writeMemory(const wolv::net::SocketClient &socket, u64 address, const void *buffer, size_t size) {
|
|
std::vector<u8> bytes(size);
|
|
std::memcpy(bytes.data(), buffer, size);
|
|
std::string byteString = crypt::encode16(bytes);
|
|
|
|
std::string packet = createPacket(fmt::format("M{:X},{:X}:{}", address, size, byteString));
|
|
|
|
std::string receivedPacket = sendReceivePackage(socket, packet);
|
|
auto receivedData = parsePacket(receivedPacket);
|
|
if (!receivedData.has_value() || *receivedData != "OK")
|
|
return false;
|
|
|
|
sendAck(socket);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
GDBProvider::GDBProvider() : m_size(0xFFFF'FFFF) {
|
|
}
|
|
|
|
bool GDBProvider::isAvailable() const {
|
|
return m_socket.isConnected();
|
|
}
|
|
|
|
bool GDBProvider::isReadable() const {
|
|
return m_socket.isConnected();
|
|
}
|
|
|
|
bool GDBProvider::isWritable() const {
|
|
return true;
|
|
}
|
|
|
|
bool GDBProvider::isResizable() const {
|
|
return false;
|
|
}
|
|
|
|
bool GDBProvider::isSavable() const {
|
|
return false;
|
|
}
|
|
|
|
void GDBProvider::readFromSource(u64 offset, void *buffer, size_t size) {
|
|
std::scoped_lock lock(m_mutex);
|
|
if (!m_socket.isConnected())
|
|
return;
|
|
|
|
auto data = gdb::readMemory(m_socket, offset, size);
|
|
if (!data.empty())
|
|
std::memcpy(buffer, data.data(), data.size());
|
|
}
|
|
|
|
void GDBProvider::writeToSource(u64 offset, const void *buffer, size_t size) {
|
|
std::scoped_lock lock(m_mutex);
|
|
if (!m_socket.isConnected())
|
|
return;
|
|
|
|
gdb::writeMemory(m_socket, offset, buffer, size);
|
|
}
|
|
|
|
void GDBProvider::save() {
|
|
Provider::save();
|
|
}
|
|
|
|
u64 GDBProvider::getSourceSize() const {
|
|
return m_size;
|
|
}
|
|
|
|
std::string GDBProvider::getName() const {
|
|
std::string address = "-";
|
|
std::string port = "-";
|
|
|
|
if (this->isConnected()) {
|
|
address = m_ipAddress;
|
|
port = std::to_string(m_port);
|
|
}
|
|
|
|
return fmt::format("hex.builtin.provider.gdb.name"_lang, address, port);
|
|
}
|
|
|
|
std::vector<GDBProvider::Description> GDBProvider::getDataDescription() const {
|
|
return {
|
|
{"hex.builtin.provider.gdb.server"_lang, fmt::format("{}:{}", m_ipAddress, m_port)},
|
|
};
|
|
}
|
|
|
|
prv::Provider::OpenResult GDBProvider::open() {
|
|
CachedProvider::open();
|
|
std::scoped_lock lock(m_mutex);
|
|
|
|
CachedProvider::open();
|
|
m_socket = wolv::net::SocketClient(wolv::net::SocketClient::Type::TCP, false);
|
|
m_socket.connect(m_ipAddress, m_port);
|
|
|
|
gdb::sendReceivePackage(m_socket, gdb::createPacket("!"));
|
|
gdb::sendReceivePackage(m_socket, gdb::createPacket("Hg0"));
|
|
|
|
if (!m_socket.isConnected()) {
|
|
return OpenResult::failure("hex.builtin.provider.gdb.server.error.not_connected"_lang);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void GDBProvider::close() {
|
|
std::scoped_lock lock(m_mutex);
|
|
|
|
CachedProvider::close();
|
|
m_socket.disconnect();
|
|
|
|
CachedProvider::close();
|
|
}
|
|
|
|
bool GDBProvider::isConnected() const {
|
|
return m_socket.isConnected();
|
|
}
|
|
|
|
|
|
bool GDBProvider::drawLoadInterface() {
|
|
ImGui::InputText("hex.builtin.provider.gdb.ip"_lang, m_ipAddress);
|
|
ImGui::InputInt("hex.builtin.provider.gdb.port"_lang, &m_port, 0, 0);
|
|
|
|
ImGui::Separator();
|
|
|
|
ImGuiExt::InputHexadecimal("hex.ui.common.size"_lang, &m_size, ImGuiInputTextFlags_CharsHexadecimal);
|
|
|
|
if (m_port < 0)
|
|
m_port = 0;
|
|
else if (m_port > 0xFFFF)
|
|
m_port = 0xFFFF;
|
|
|
|
return !m_ipAddress.empty() && m_port != 0;
|
|
}
|
|
|
|
void GDBProvider::loadSettings(const nlohmann::json &settings) {
|
|
Provider::loadSettings(settings);
|
|
|
|
m_ipAddress = settings.at("ip").get<std::string>();
|
|
m_port = settings.at("port").get<int>();
|
|
m_size = settings.at("size").get<size_t>();
|
|
}
|
|
|
|
nlohmann::json GDBProvider::storeSettings(nlohmann::json settings) const {
|
|
settings["ip"] = m_ipAddress;
|
|
settings["port"] = m_port;
|
|
settings["size"] = m_size;
|
|
|
|
return Provider::storeSettings(settings);
|
|
}
|
|
|
|
std::pair<Region, bool> GDBProvider::getRegionValidity(u64 address) const {
|
|
address -= this->getBaseAddress();
|
|
|
|
if (address < this->getActualSize())
|
|
return { Region { .address=this->getBaseAddress() + address, .size=this->getActualSize() - address }, true };
|
|
else
|
|
return { Region::Invalid(), false };
|
|
}
|
|
|
|
std::variant<std::string, i128> GDBProvider::queryInformation(const std::string &category, const std::string &argument) {
|
|
if (category == "ip")
|
|
return m_ipAddress;
|
|
else if (category == "port")
|
|
return m_port;
|
|
else
|
|
return Provider::queryInformation(category, argument);
|
|
}
|
|
|
|
} |