Add GhidraClient

This commit is contained in:
kotcrab 2024-11-11 21:31:26 +01:00
parent 2402eea4b1
commit fdf8ff7d94
9 changed files with 325 additions and 0 deletions

View File

@ -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

View File

@ -591,6 +591,7 @@
<ClInclude Include="Crypto\sha256.h" />
<ClInclude Include="DbgNew.h" />
<ClInclude Include="ExceptionHandlerSetup.h" />
<ClInclude Include="GhidraClient.h" />
<ClInclude Include="GraphicsContext.h" />
<ClInclude Include="Log.h" />
<ClInclude Include="Log\LogManager.h" />
@ -1021,6 +1022,7 @@
<ClCompile Include="GPU\Vulkan\VulkanRenderManager.cpp" />
<ClCompile Include="Input\GestureDetector.cpp" />
<ClCompile Include="Input\InputState.cpp" />
<ClCompile Include="GhidraClient.cpp" />
<ClCompile Include="Log.cpp" />
<ClCompile Include="Math\curves.cpp" />
<ClCompile Include="Math\expression_parser.cpp" />

View File

@ -6,6 +6,7 @@
<ClInclude Include="CommonFuncs.h" />
<ClInclude Include="CommonTypes.h" />
<ClInclude Include="CPUDetect.h" />
<ClInclude Include="GhidraClient.h" />
<ClInclude Include="Log.h" />
<ClInclude Include="MemArena.h" />
<ClInclude Include="MemoryUtil.h" />
@ -705,6 +706,7 @@
<Filter>Serialize</Filter>
</ClCompile>
<ClCompile Include="TimeUtil.cpp" />
<ClCompile Include="GhidraClient.cpp" />
<ClCompile Include="Log.cpp" />
<ClCompile Include="SysError.cpp" />
<ClCompile Include="..\ext\libpng17\png.c">

205
Common/GhidraClient.cpp Normal file
View File

@ -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<std::mutex> 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<std::mutex> lock(mutex_);
host_ = host;
port_ = port;
const bool result = FetchTypes() && FetchSymbols();
status_ = Status::Ready;
return result;
}
void GhidraClient::UpdateResult() {
std::lock_guard<std::mutex> 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;
}

108
Common/GhidraClient.h Normal file
View File

@ -0,0 +1,108 @@
#pragma once
#include <string>
#include <mutex>
#include <vector>
#include <unordered_map>
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<GhidraCompositeMember> compositeMembers;
std::vector<GhidraEnumMember> 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<GhidraSymbol> symbols;
std::unordered_map<std::string, GhidraType> 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> 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);
};

View File

@ -197,6 +197,7 @@
<ClInclude Include="..\..\Common\Crypto\sha256.h" />
<ClInclude Include="..\..\Common\DbgNew.h" />
<ClInclude Include="..\..\Common\ExceptionHandlerSetup.h" />
<ClInclude Include="..\..\Common\GhidraClient.h" />
<ClInclude Include="..\..\Common\GraphicsContext.h" />
<ClInclude Include="..\..\Common\Log.h" />
<ClInclude Include="..\..\Common\Log\LogManager.h" />
@ -360,6 +361,7 @@
<ClCompile Include="..\..\Common\Crypto\sha1.cpp" />
<ClCompile Include="..\..\Common\Crypto\sha256.cpp" />
<ClCompile Include="..\..\Common\ExceptionHandlerSetup.cpp" />
<ClCompile Include="..\..\Common\GhidraClient.cpp" />
<ClCompile Include="..\..\Common\Log.cpp" />
<ClCompile Include="..\..\Common\Log\LogManager.cpp" />
<ClCompile Include="..\..\Common\LogReporting.cpp" />

View File

@ -119,6 +119,7 @@
<ClCompile Include="..\..\Common\CPUDetect.cpp" />
<ClCompile Include="..\..\Common\FakeCPUDetect.cpp" />
<ClCompile Include="..\..\Common\ExceptionHandlerSetup.cpp" />
<ClCompile Include="..\..\Common\GhidraClient.cpp" />
<ClCompile Include="..\..\Common\Log.cpp" />
<ClCompile Include="..\..\Common\Log\LogManager.cpp" />
<ClCompile Include="..\..\Common\LogReporting.cpp" />
@ -538,6 +539,7 @@
<ClInclude Include="..\..\Common\CPUDetect.h" />
<ClInclude Include="..\..\Common\DbgNew.h" />
<ClInclude Include="..\..\Common\ExceptionHandlerSetup.h" />
<ClInclude Include="..\..\Common\GhidraClient.h" />
<ClInclude Include="..\..\Common\GraphicsContext.h" />
<ClInclude Include="..\..\Common\Log.h" />
<ClInclude Include="..\..\Common\Log\LogManager.h" />

View File

@ -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 \

View File

@ -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 \