From fdf8ff7d94c31b03be42cc1c92aaebc439ef0fe2 Mon Sep 17 00:00:00 2001 From: kotcrab <4594081+kotcrab@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:31:26 +0100 Subject: [PATCH] Add GhidraClient --- CMakeLists.txt | 2 + Common/Common.vcxproj | 2 + Common/Common.vcxproj.filters | 2 + Common/GhidraClient.cpp | 205 ++++++++++++++++++++++++ Common/GhidraClient.h | 108 +++++++++++++ UWP/CommonUWP/CommonUWP.vcxproj | 2 + UWP/CommonUWP/CommonUWP.vcxproj.filters | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 9 files changed, 325 insertions(+) create mode 100644 Common/GhidraClient.cpp create mode 100644 Common/GhidraClient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c8a8b914f3..8c22ed4a08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -884,6 +884,8 @@ add_library(Common STATIC Common/FakeCPUDetect.cpp Common/ExceptionHandlerSetup.cpp Common/ExceptionHandlerSetup.h + Common/GhidraClient.h + Common/GhidraClient.cpp Common/Log.h Common/Log.cpp Common/Log/ConsoleListener.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index d3c3b77978..f0870fdba6 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -591,6 +591,7 @@ + @@ -1021,6 +1022,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index db662233d3..055a82c5d7 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -6,6 +6,7 @@ + @@ -705,6 +706,7 @@ Serialize + diff --git a/Common/GhidraClient.cpp b/Common/GhidraClient.cpp new file mode 100644 index 0000000000..b26e5a3742 --- /dev/null +++ b/Common/GhidraClient.cpp @@ -0,0 +1,205 @@ +#include "Common/Data/Format/JSONReader.h" +#include "Common/Net/HTTPClient.h" +#include "Common/Thread/ThreadUtil.h" + +#include "Common/GhidraClient.h" + +using namespace json; + +static GhidraTypeKind ResolveTypeKind(const std::string& kind) { + if (kind == "ENUM") return ENUM; + if (kind == "TYPEDEF") return TYPEDEF; + if (kind == "POINTER") return POINTER; + if (kind == "ARRAY") return ARRAY; + if (kind == "STRUCTURE") return STRUCTURE; + if (kind == "UNION") return UNION; + if (kind == "FUNCTION_DEFINITION") return FUNCTION_DEFINITION; + if (kind == "BUILT_IN") return BUILT_IN; + return UNKNOWN; +} + +GhidraClient::~GhidraClient() { + if (thread_.joinable()) { + thread_.join(); + } +} + +void GhidraClient::FetchAll(const std::string& host, const int port) { + std::lock_guard lock(mutex_); + if (status_ != Status::Idle) { + return; + } + status_ = Status::Pending; + thread_ = std::thread([this, host, port] { + SetCurrentThreadName("GhidraClient"); + FetchAllDo(host, port); + }); +} + +bool GhidraClient::FetchAllDo(const std::string& host, const int port) { + std::lock_guard lock(mutex_); + host_ = host; + port_ = port; + const bool result = FetchTypes() && FetchSymbols(); + status_ = Status::Ready; + return result; +} + +void GhidraClient::UpdateResult() { + std::lock_guard lock(mutex_); + if (status_ != Status::Ready) { + return; + } + if (thread_.joinable()) { + thread_.join(); + } + result = std::move(pendingResult_); + pendingResult_ = Result(); + status_ = Status::Idle; +} + +bool GhidraClient::FetchSymbols() { + std::string json; + if (!FetchResource("/v1/symbols", json)) { + return false; + } + JsonReader reader(json.c_str(), json.size()); + if (!reader.ok()) { + pendingResult_.error = "symbols parsing error"; + return false; + } + const JsonValue entries = reader.root().getArray("symbols")->value; + if (entries.getTag() != JSON_ARRAY) { + pendingResult_.error = "symbols is not an array"; + return false; + } + + for (const auto pEntry: entries) { + JsonGet entry = pEntry->value; + + GhidraSymbol symbol; + symbol.address = entry.getInt("address", 0); + symbol.name = entry.getStringOr("name", ""); + symbol.label = strcmp(entry.getStringOr("type", ""), "Label") == 0; + symbol.userDefined = strcmp(entry.getStringOr("source", ""), "USER_DEFINED") == 0; + symbol.dataTypePathName = entry.getStringOr("dataTypePathName", ""); + pendingResult_.symbols.emplace_back(symbol); + } + return true; +} + +bool GhidraClient::FetchTypes() { + std::string json; + if (!FetchResource("/v1/types", json)) { + return false; + } + JsonReader reader(json.c_str(), json.size()); + if (!reader.ok()) { + pendingResult_.error = "types parsing error"; + return false; + } + const JsonValue entries = reader.root().getArray("types")->value; + if (entries.getTag() != JSON_ARRAY) { + pendingResult_.error = "types is not an array"; + return false; + } + + for (const auto pEntry: entries) { + const JsonGet entry = pEntry->value; + + GhidraType type; + type.name = entry.getStringOr("name", ""); + type.displayName = entry.getStringOr("displayName", ""); + type.pathName = entry.getStringOr("pathName", ""); + type.length = entry.getInt("length", 0); + type.alignedLength = entry.getInt("alignedLength", 0); + type.zeroLength = entry.getBool("zeroLength", false); + type.description = entry.getStringOr("description", ""); + type.kind = ResolveTypeKind(entry.getStringOr("kind", "")); + + switch (type.kind) { + case ENUM: { + const JsonNode* enumEntries = entry.getArray("members"); + if (!enumEntries) { + pendingResult_.error = "missing enum members"; + return false; + } + for (const JsonNode* pEnumEntry: enumEntries->value) { + JsonGet enumEntry = pEnumEntry->value; + GhidraEnumMember member; + member.name = enumEntry.getStringOr("name", ""); + member.value = enumEntry.getInt("value", 0); + member.comment = enumEntry.getStringOr("comment", ""); + type.enumMembers.push_back(member); + } + break; + } + case TYPEDEF: + type.typedefTypePathName = entry.getStringOr("typePathName", ""); + type.typedefBaseTypePathName = entry.getStringOr("baseTypePathName", ""); + break; + case POINTER: + type.pointerTypePathName = entry.getStringOr("typePathName", ""); + break; + case ARRAY: + type.arrayTypePathName = entry.getStringOr("typePathName", ""); + type.arrayElementLength = entry.getInt("elementLength", 0); + type.arrayElementCount = entry.getInt("elementCount", 0); + break; + case STRUCTURE: + case UNION: { + const JsonNode* compositeEntries = entry.getArray("members"); + if (!compositeEntries) { + pendingResult_.error = "missing composite members"; + return false; + } + for (const JsonNode* pCompositeEntry: compositeEntries->value) { + JsonGet compositeEntry = pCompositeEntry->value; + GhidraCompositeMember member; + member.fieldName = compositeEntry.getStringOr("fieldName", ""); + member.ordinal = compositeEntry.getInt("ordinal", 0); + member.offset = compositeEntry.getInt("offset", 0); + member.length = compositeEntry.getInt("length", 0); + member.typePathName = compositeEntry.getStringOr("typePathName", ""); + member.comment = compositeEntry.getStringOr("comment", ""); + type.compositeMembers.push_back(member); + } + break; + } + case FUNCTION_DEFINITION: + type.functionPrototypeString = entry.getStringOr("prototypeString", ""); + break; + case BUILT_IN: + type.builtInGroup = entry.getStringOr("group", ""); + break; + default: + continue; + } + + pendingResult_.types.emplace(type.pathName, type); + } + return true; +} + +bool GhidraClient::FetchResource(const std::string& path, std::string& outResult) { + http::Client http; + if (!http.Resolve(host_.c_str(), port_)) { + pendingResult_.error = "can't resolve host"; + return false; + } + bool cancelled = false; + if (!http.Connect(1, 5.0, &cancelled)) { + pendingResult_.error = "can't connect to host"; + return false; + } + net::RequestProgress progress(&cancelled); + Buffer result; + const int code = http.GET(http::RequestParams(path.c_str()), &result, &progress); + http.Disconnect(); + if (code != 200) { + pendingResult_.error = "unsuccessful response code"; + return false; + } + result.TakeAll(&outResult); + return true; +} diff --git a/Common/GhidraClient.h b/Common/GhidraClient.h new file mode 100644 index 0000000000..efaeb00c7c --- /dev/null +++ b/Common/GhidraClient.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include + +struct GhidraSymbol { + u32 address = 0; + std::string name; + bool label; + bool userDefined; + std::string dataTypePathName; +}; + +enum GhidraTypeKind { + ENUM, + TYPEDEF, + POINTER, + ARRAY, + STRUCTURE, + UNION, + FUNCTION_DEFINITION, + BUILT_IN, + UNKNOWN, +}; + +struct GhidraEnumMember { + std::string name; + u64 value = 0; + std::string comment; +}; + +struct GhidraCompositeMember { + std::string fieldName; + u32 ordinal = 0; + u32 offset = 0; + int length = 0; + std::string typePathName; + std::string comment; +}; + +struct GhidraType { + GhidraTypeKind kind; + std::string name; + std::string displayName; + std::string pathName; + int length = 0; + int alignedLength = 0; + bool zeroLength = false; + std::string description; + + std::vector compositeMembers; + std::vector enumMembers; + std::string pointerTypePathName; + std::string typedefTypePathName; + std::string typedefBaseTypePathName; + std::string arrayTypePathName; + int arrayElementLength = 0; + u32 arrayElementCount = 0; + std::string functionPrototypeString; + std::string builtInGroup; +}; + +class GhidraClient { +public: + enum class Status { + Idle, + Pending, + Ready, + }; + + struct Result { + std::vector symbols; + std::unordered_map types; + std::string error; + }; + + Result result; + + ~GhidraClient(); + + void FetchAll(const std::string& host, int port); + + void UpdateResult(); + + bool Idle() const { return status_ == Status::Idle; } + + bool Ready() const { return status_ == Status::Ready; } + + bool Failed() const { return !result.error.empty(); } + +private: + std::thread thread_; + std::mutex mutex_; + std::atomic status_; + Result pendingResult_; + std::string host_; + int port_ = 0; + + bool FetchAllDo(const std::string& host, int port); + + bool FetchSymbols(); + + bool FetchTypes(); + + bool FetchResource(const std::string& path, std::string& outResult); +}; diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 1678bcce43..921c7dc006 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -197,6 +197,7 @@ + @@ -360,6 +361,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index ec247f71b1..47d4699d33 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -119,6 +119,7 @@ + @@ -538,6 +539,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index f431e54666..6ef541a9c8 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -373,6 +373,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/CPUDetect.cpp \ $(SRC)/Common/ExceptionHandlerSetup.cpp \ $(SRC)/Common/FakeCPUDetect.cpp \ + $(SRC)/Common/GhidraClient.cpp \ $(SRC)/Common/Log.cpp \ $(SRC)/Common/Log/LogManager.cpp \ $(SRC)/Common/LogReporting.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 1a3fa09324..cb1c6419a9 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -483,6 +483,7 @@ SOURCES_CXX += \ $(COMMONDIR)/Log/StdioListener.cpp \ $(COMMONDIR)/ExceptionHandlerSetup.cpp \ $(COMMONDIR)/FakeCPUDetect.cpp \ + $(COMMONDIR)/GhidraClient.cpp \ $(COMMONDIR)/Log.cpp \ $(COMMONDIR)/Log/LogManager.cpp \ $(COMMONDIR)/OSVersion.cpp \