Skip to content

Commit

Permalink
Support copy address and value in Struct viewer
Browse files Browse the repository at this point in the history
Reorganize add breakpoint menu
Style fixes
  • Loading branch information
kotcrab committed Nov 12, 2024
1 parent b3fe162 commit e21c471
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 39 deletions.
8 changes: 4 additions & 4 deletions Common/GhidraClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ bool GhidraClient::FetchSymbols() {
return false;
}

for (const auto pEntry: entries) {
for (const auto pEntry : entries) {
JsonGet entry = pEntry->value;

GhidraSymbol symbol;
Expand Down Expand Up @@ -104,7 +104,7 @@ bool GhidraClient::FetchTypes() {
return false;
}

for (const auto pEntry: entries) {
for (const auto pEntry : entries) {
const JsonGet entry = pEntry->value;

GhidraType type;
Expand All @@ -124,7 +124,7 @@ bool GhidraClient::FetchTypes() {
pendingResult_.error = "missing enum members";
return false;
}
for (const JsonNode* pEnumEntry: enumEntries->value) {
for (const JsonNode* pEnumEntry : enumEntries->value) {
JsonGet enumEntry = pEnumEntry->value;
GhidraEnumMember member;
member.name = enumEntry.getStringOr("name", "");
Expand Down Expand Up @@ -153,7 +153,7 @@ bool GhidraClient::FetchTypes() {
pendingResult_.error = "missing composite members";
return false;
}
for (const JsonNode* pCompositeEntry: compositeEntries->value) {
for (const JsonNode* pCompositeEntry : compositeEntries->value) {
JsonGet compositeEntry = pCompositeEntry->value;
GhidraCompositeMember member;
member.fieldName = compositeEntry.getStringOr("fieldName", "");
Expand Down
2 changes: 1 addition & 1 deletion Common/GhidraClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ struct GhidraType {
};

class GhidraClient {
public:
enum class Status {
Idle,
Pending,
Ready,
};

public:
struct Result {
std::vector<GhidraSymbol> symbols;
std::unordered_map<std::string, GhidraType> types;
Expand Down
88 changes: 55 additions & 33 deletions UI/ImDebugger/ImStructViewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "ext/imgui/imgui.h"

#include "Common/System/Request.h"
#include "Core/MemMap.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/MIPS/MIPSDebugInterface.h"
Expand Down Expand Up @@ -190,7 +191,7 @@ static constexpr int COLUMN_CONTENT = 2;

void ImStructViewer::Draw(MIPSDebugInterface* mipsDebug, bool* open) {
mipsDebug_ = mipsDebug;
ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(750, 550), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Struct viewer", open) || !mipsDebug->isAlive() || !Memory::IsActive()) {
ImGui::End();
return;
Expand Down Expand Up @@ -275,7 +276,7 @@ void ImStructViewer::DrawGlobals() {
ImGui::TableSetupColumn("Content");
ImGui::TableHeadersRow();

for (const auto& symbol: ghidraClient_.result.symbols) {
for (const auto& symbol : ghidraClient_.result.symbols) {
if (!symbol.label || !symbol.userDefined || symbol.dataTypePathName.empty()) {
continue;
}
Expand Down Expand Up @@ -304,7 +305,7 @@ void ImStructViewer::DrawWatch() {
ImGui::TableHeadersRow();

int watchIndex = -1;
for (const auto& watch: watches_) {
for (const auto& watch : watches_) {
watchIndex++;
if (!watchFilter_.PassFilter(watch.name.c_str())) {
continue;
Expand All @@ -313,10 +314,9 @@ void ImStructViewer::DrawWatch() {
if (!watch.expression.empty()) {
u32 val;
PostfixExpression postfix;
if (mipsDebug_->initExpression(watch.expression.c_str(), postfix)) {
if (mipsDebug_->parseExpression(postfix, val)) {
address = val;
}
if (mipsDebug_->initExpression(watch.expression.c_str(), postfix)
&& mipsDebug_->parseExpression(postfix, val)) {
address = val;
}
} else {
address = watch.address;
Expand Down Expand Up @@ -346,7 +346,7 @@ void ImStructViewer::DrawNewWatchEntry() {
ImGui::SetKeyboardFocusHere(0);
}
newWatch_.typeFilter.Draw();
for (const auto& entry: ghidraClient_.result.types) {
for (const auto& entry : ghidraClient_.result.types) {
const auto& type = entry.second;
if (newWatch_.typeFilter.PassFilter(type.displayName.c_str())) {
ImGui::PushID(type.pathName.c_str());
Expand Down Expand Up @@ -498,7 +498,7 @@ static void DrawPointerContent(
static std::string FormatEnumValue(const std::vector<GhidraEnumMember>& enumMembers, const u64 value) {
std::stringstream ss;
bool hasPrevious = false;
for (const auto& member: enumMembers) {
for (const auto& member : enumMembers) {
if (value & member.value) {
if (hasPrevious) {
ss << " | ";
Expand Down Expand Up @@ -559,12 +559,12 @@ void ImStructViewer::DrawType(
ImGui::AlignTextToFramePadding();
// Flags used for nodes that can't be further opened
const ImGuiTreeNodeFlags leafFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_Bullet | extraTreeNodeFlags;
extraTreeNodeFlags;

// Type is missing in fetched types, this can happen e.g. if type used for watch is removed from Ghidra
if (!hasType) {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
DrawContextMenu(base, offset, 0, typePathName, name, watchIndex);
DrawContextMenu(base, offset, 0, typePathName, name, watchIndex, nullptr);
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
DrawTypeColumn("<missing type: %s>", typePathName, base, offset);
ImGui::PopStyleColor();
Expand All @@ -580,7 +580,7 @@ void ImStructViewer::DrawType(
// Handle cases where pointers or expressions point to invalid memory
if (!Memory::IsValidAddress(address)) {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, nullptr);
DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
Expand All @@ -596,23 +596,24 @@ void ImStructViewer::DrawType(
switch (type.kind) {
case ENUM: {
ImGui::TreeNodeEx("Enum", leafFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
const u64 enumValue = ReadMemoryInt(address, type.length);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, &enumValue);
DrawTypeColumn("%s", typeDisplayName, base, offset);

ImGui::TableSetColumnIndex(COLUMN_CONTENT);
const u64 value = ReadMemoryInt(address, type.length);
const std::string stringValue = FormatEnumValue(type.enumMembers, value);
ImGui::Text("= %llx (%s)", value, stringValue.c_str());
const std::string enumString = FormatEnumValue(type.enumMembers, enumValue);
ImGui::Text("= %llx (%s)", enumValue, enumString.c_str());
DrawIntBuiltInEditPopup(address, type.length);
break;
}
case POINTER: {
const bool nodeOpen = ImGui::TreeNodeEx("Pointer", extraTreeNodeFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
const u32 pointer = Memory::Read_U32(address);
const u64 pointer64 = pointer;
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, &pointer64);
DrawTypeColumn("%s", typeDisplayName, base, offset);

ImGui::TableSetColumnIndex(COLUMN_CONTENT);
const u32 pointer = Memory::Read_U32(address);
DrawPointerContent(types, type, pointer);

if (nodeOpen) {
Expand Down Expand Up @@ -649,7 +650,7 @@ void ImStructViewer::DrawType(
}
case ARRAY: {
const bool nodeOpen = ImGui::TreeNodeEx("Array", extraTreeNodeFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, nullptr);
DrawTypeColumn("%s", typeDisplayName, base, offset);

ImGui::TableSetColumnIndex(COLUMN_CONTENT);
Expand All @@ -669,11 +670,11 @@ void ImStructViewer::DrawType(
case STRUCTURE:
case UNION: {
const bool nodeOpen = ImGui::TreeNodeEx("Composite", extraTreeNodeFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, nullptr);
DrawTypeColumn("%s", typeDisplayName, base, offset);

if (nodeOpen) {
for (const auto& member: type.compositeMembers) {
for (const auto& member : type.compositeMembers) {
DrawType(base, offset + member.offset, member.typePathName, nullptr,
member.fieldName.c_str(), -1);
}
Expand All @@ -683,17 +684,19 @@ void ImStructViewer::DrawType(
}
case FUNCTION_DEFINITION:
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, nullptr);
DrawTypeColumn("%s", typeDisplayName, base, offset);

ImGui::TableSetColumnIndex(COLUMN_CONTENT);
ImGui::Text("<function definition>"); // TODO could be go to in disassembler here
break;
case BUILT_IN: {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);

if (knownBuiltIns.count(typePathName)) {
// This will copy float as int, but we can live with that for now
const u64 value = ReadMemoryInt(address, type.alignedLength);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, &value);
DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
DrawBuiltInContent(knownBuiltIns.at(typePathName), address);
Expand All @@ -709,7 +712,7 @@ void ImStructViewer::DrawType(
// At this point there is most likely some issue in the Ghidra plugin and the type wasn't
// classified to any category
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex, nullptr);
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
DrawTypeColumn("<not implemented type: %s>", typeDisplayName, base, offset);
ImGui::PopStyleColor();
Expand All @@ -721,18 +724,33 @@ void ImStructViewer::DrawType(
ImGui::PopID();
}

static void CopyHexNumberToClipboard(u64 value) {
std::stringstream ss;
ss << std::hex << value;
const std::string valueString = ss.str();
System_CopyStringToClipboard(valueString);
}

void ImStructViewer::DrawContextMenu(
const u32 base,
const u32 offset,
const int length,
const std::string& typePathName,
const char* name,
const int watchIndex
const int watchIndex,
const u64* value
) {
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
if (ImGui::BeginPopup("context")) {
const u32 address = base + offset;

if (ImGui::MenuItem("Copy address")) {
CopyHexNumberToClipboard(address);
}
if (value && ImGui::MenuItem("Copy value")) {
CopyHexNumberToClipboard(*value);
}

// This might be called when iterating over existing watches so can't modify the watch vector directly here
if (watchIndex < 0) {
if (ImGui::MenuItem("Add watch")) {
Expand Down Expand Up @@ -760,21 +778,25 @@ void ImStructViewer::DrawContextMenu(
CBreakPoints::RemoveMemCheck(address, end);
}
}
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_READ)) {
if (ImGui::MenuItem("Add memory read breakpoint")) {
const bool canAddRead = !hasMemCheck || !(memCheck.cond & MEMCHECK_READ);
const bool canAddWrite = !hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE);
const bool canAddWriteOnChange = !hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE_ONCHANGE);
if ((canAddRead || canAddWrite || canAddWriteOnChange) && ImGui::BeginMenu("Add memory breakpoint")) {
if (canAddRead && canAddWrite && ImGui::MenuItem("Read/Write")) {
constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_READ | MEMCHECK_WRITE);
CBreakPoints::AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE);
}
if (canAddRead && ImGui::MenuItem("Read")) {
CBreakPoints::AddMemCheck(address, end, MEMCHECK_READ, BREAK_ACTION_PAUSE);
}
}
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE)) {
if (ImGui::MenuItem("Add memory write breakpoint")) {
if (canAddWrite && ImGui::MenuItem("Write")) {
CBreakPoints::AddMemCheck(address, end, MEMCHECK_WRITE, BREAK_ACTION_PAUSE);
}
}
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE_ONCHANGE)) {
if (ImGui::MenuItem("Add memory write on change breakpoint")) {
if (canAddWriteOnChange && ImGui::MenuItem("Write Change")) {
constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE);
CBreakPoints::AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE);
}
ImGui::EndMenu();
}
}

Expand Down
3 changes: 2 additions & 1 deletion UI/ImDebugger/ImStructViewer.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,6 @@ class ImStructViewer {
int length,
const std::string& typePathName,
const char* name,
int watchIndex);
int watchIndex,
const u64* value);
};

0 comments on commit e21c471

Please sign in to comment.