From e3e831851b680c8ddd898391188f7fad3dec52a8 Mon Sep 17 00:00:00 2001 From: kotcrab <4594081+kotcrab@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:15:14 +0100 Subject: [PATCH] Add GhidraClient and ImStructViewer docs Few more code comments and misc clean up --- Common/GhidraClient.cpp | 3 +-- Common/GhidraClient.h | 34 +++++++++++++++++++++++++++++++- UI/ImDebugger/ImStructViewer.cpp | 18 +++++++++++------ UI/ImDebugger/ImStructViewer.h | 20 +++++++++++++++---- 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/Common/GhidraClient.cpp b/Common/GhidraClient.cpp index e0c5bbe3ca..a7e21c6df3 100644 --- a/Common/GhidraClient.cpp +++ b/Common/GhidraClient.cpp @@ -108,7 +108,6 @@ bool GhidraClient::FetchTypes() { 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); @@ -197,7 +196,7 @@ bool GhidraClient::FetchResource(const std::string& path, std::string& outResult const int code = http.GET(http::RequestParams(path.c_str()), &result, &progress); http.Disconnect(); if (code != 200) { - pendingResult_.error = "unsuccessful response code"; + pendingResult_.error = "unsuccessful response code from API endpoint"; return false; } result.TakeAll(&outResult); diff --git a/Common/GhidraClient.h b/Common/GhidraClient.h index 2746c99f6e..334c46fd99 100644 --- a/Common/GhidraClient.h +++ b/Common/GhidraClient.h @@ -7,6 +7,10 @@ #include #include +/** + * Represents symbol from a Ghidra's program. + * A symbol can be for example a data label, instruction label or a function. + */ struct GhidraSymbol { u32 address = 0; std::string name; @@ -15,6 +19,7 @@ struct GhidraSymbol { std::string dataTypePathName; }; +/** Possible kinds of data types, such as enum, structures or built-ins (Ghidra's primitive types). */ enum GhidraTypeKind { ENUM, TYPEDEF, @@ -27,12 +32,14 @@ enum GhidraTypeKind { UNKNOWN, }; +/** Describes single member of an enum type. */ struct GhidraEnumMember { std::string name; u64 value = 0; std::string comment; }; +/** Describes single member of a composite (structure or union) type. */ struct GhidraCompositeMember { std::string fieldName; u32 ordinal = 0; @@ -42,9 +49,13 @@ struct GhidraCompositeMember { std::string comment; }; +/** + * Describes data type from Ghidra. Note that some fields of this structure will only be populated depending on the + * type's kind. Each type has a display name that is suitable for displaying to the user and a path name that + * unambiguously identifies this type. + */ struct GhidraType { GhidraTypeKind kind; - std::string name; std::string displayName; std::string pathName; int length = 0; @@ -64,6 +75,23 @@ struct GhidraType { std::string builtInGroup; }; +/** + * GhidraClient implements fetching data (such as symbols or types) from a remote Ghidra project. + * + * This client uses unofficial API provided by the third party "ghidra-rest-api" extension. The extension is + * available at https://github.com/kotcrab/ghidra-rest-api. + * + * This class doesn't fetch data from every possible endpoint, only those that are actually used by PPSSPP. + * + * How to use: + * 1. The client is created in the Idle status. + * 2. To start fetching data call the FetchAll() method. The client goes to Pending status and the data is fetched + * in a background thread so your code remains unblocked. + * 3. Periodically check with the Ready() method is the operation has completed. (i.e. check if the client + is in the Ready status) + * 4. If the client is ready call UpdateResult() to update result field with new data. + * 5. The client is now back to Idle status, and you can call FetchAll() again later if needed. + */ class GhidraClient { enum class Status { Idle, @@ -78,12 +106,16 @@ public: std::string error; }; + /** Current result of the client. Your thread is safe to access this regardless of client status. */ Result result; ~GhidraClient(); + /** If client is idle then asynchronously starts fetching data from Ghidra. */ void FetchAll(const std::string& host, int port); + /** If client is ready then updates the result field with newly fetched data. This must be called from the thread + * using the result. */ void UpdateResult(); bool Idle() const { return status_ == Status::Idle; } diff --git a/UI/ImDebugger/ImStructViewer.cpp b/UI/ImDebugger/ImStructViewer.cpp index 03246831ec..d277af8154 100644 --- a/UI/ImDebugger/ImStructViewer.cpp +++ b/UI/ImDebugger/ImStructViewer.cpp @@ -216,9 +216,15 @@ void ImStructViewer::Draw(MIPSDebugInterface* mipsDebug, bool* open) { } void ImStructViewer::DrawConnectionSetup() { - ImGui::TextWrapped("Struct viewer visualizes data in memory using types from your Ghidra project."); - ImGui::TextWrapped("To get started install the ghidra-rest-api plugin and start the Rest API server."); - ImGui::TextWrapped("When ready press the connect button below."); + ImGui::TextWrapped(R"(Struct viewer visualizes data in game memory using types from your Ghidra project. +It also allows to set memory breakpoints and edit field values which is helpful when reverse engineering unknown types. +To get started: + 1. In Ghidra install the ghidra-rest-api extension by Kotcrab. + 2. After installing the extension enable the RestApiPlugin in the Miscellaneous plugins configuration window. + 3. Open your Ghidra project and click "Start Rest API Server" in the "Tools" menu bar. + 4. Press the connect button below. +)"); + ImGui::Dummy(ImVec2(1, 6)); ImGui::BeginDisabled(!ghidraClient_.Idle()); ImGui::PushItemWidth(120); @@ -552,7 +558,7 @@ void ImStructViewer::DrawType( const u32 address = base + offset; ImGui::PushID(static_cast(address)); - ImGui::PushID(watchIndex); + ImGui::PushID(watchIndex); // We push watch index too as it's possible to have multiple watches on the same address // Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing // to make the tree lines equal high. @@ -711,7 +717,7 @@ void ImStructViewer::DrawType( break; } default: { - // At this point there is most likely some issue in the Ghidra plugin and the type wasn't + // At this point there is most likely some issue in the Ghidra extension and the type wasn't // classified to any category ImGui::TreeNodeEx("Field", leafFlags, "%s", name); DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, nullptr); @@ -726,7 +732,7 @@ void ImStructViewer::DrawType( ImGui::PopID(); } -static void CopyHexNumberToClipboard(u64 value) { +static void CopyHexNumberToClipboard(const u64 value) { std::stringstream ss; ss << std::hex << value; const std::string valueString = ss.str(); diff --git a/UI/ImDebugger/ImStructViewer.h b/UI/ImDebugger/ImStructViewer.h index 808e92cbd2..289025b64a 100644 --- a/UI/ImDebugger/ImStructViewer.h +++ b/UI/ImDebugger/ImStructViewer.h @@ -5,6 +5,18 @@ #include "Common/GhidraClient.h" #include "Core/MIPS/MIPSDebugInterface.h" +/** + * Struct viewer visualizes objects data in game memory using types and symbols fetched from a Ghidra project. + * It also allows to set memory breakpoints and edit field values which is helpful when reverse engineering unknown + * types. + * + * To use this you will need to install an unofficial Ghidra extension "ghidra-rest-api" by Kotcrab. + * (available at https://github.com/kotcrab/ghidra-rest-api). After installing the extension and starting the API + * server in Ghidra you can open the Struct viewer window and press the "Connect" button to start using it. + * + * See the original pull request https://github.com/hrydgard/ppsspp/pull/19629 for a screenshot and how to test this + * without the need to set up Ghidra. + */ class ImStructViewer { struct Watch { std::string expression; @@ -35,12 +47,12 @@ private: GhidraClient ghidraClient_; char ghidraHost_[128] = "localhost"; int ghidraPort_ = 18489; - bool fetchedAtLeastOnce_ = false; + bool fetchedAtLeastOnce_ = false; // True if fetched from Ghidra successfully at least once std::vector watches_; - int removeWatchIndex_ = -1; - Watch addWatch_; - NewWatch newWatch_; + int removeWatchIndex_ = -1; // Watch index entry to be removed on next draw + Watch addWatch_; // Temporary variable to store watch entry added from the Globals tab + NewWatch newWatch_; // State for the new watch entry UI void DrawConnectionSetup();