Add GhidraClient and ImStructViewer docs

Few more code comments and misc clean up
This commit is contained in:
kotcrab 2024-11-19 00:15:14 +01:00
parent 2c49cae1e2
commit e3e831851b
4 changed files with 62 additions and 13 deletions

View File

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

View File

@ -7,6 +7,10 @@
#include <unordered_map>
#include <vector>
/**
* 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; }

View File

@ -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<int>(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();

View File

@ -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<Watch> 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();