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 \