Support copy address and value in Struct viewer

Reorganize add breakpoint menu
Style fixes
This commit is contained in:
kotcrab 2024-11-12 21:05:11 +01:00
parent 3182cc29e4
commit f9d7e426f8
4 changed files with 62 additions and 39 deletions

View File

@ -74,7 +74,7 @@ bool GhidraClient::FetchSymbols() {
return false; return false;
} }
for (const auto pEntry: entries) { for (const auto pEntry : entries) {
JsonGet entry = pEntry->value; JsonGet entry = pEntry->value;
GhidraSymbol symbol; GhidraSymbol symbol;
@ -104,7 +104,7 @@ bool GhidraClient::FetchTypes() {
return false; return false;
} }
for (const auto pEntry: entries) { for (const auto pEntry : entries) {
const JsonGet entry = pEntry->value; const JsonGet entry = pEntry->value;
GhidraType type; GhidraType type;
@ -124,7 +124,7 @@ bool GhidraClient::FetchTypes() {
pendingResult_.error = "missing enum members"; pendingResult_.error = "missing enum members";
return false; return false;
} }
for (const JsonNode* pEnumEntry: enumEntries->value) { for (const JsonNode* pEnumEntry : enumEntries->value) {
JsonGet enumEntry = pEnumEntry->value; JsonGet enumEntry = pEnumEntry->value;
GhidraEnumMember member; GhidraEnumMember member;
member.name = enumEntry.getStringOr("name", ""); member.name = enumEntry.getStringOr("name", "");
@ -153,7 +153,7 @@ bool GhidraClient::FetchTypes() {
pendingResult_.error = "missing composite members"; pendingResult_.error = "missing composite members";
return false; return false;
} }
for (const JsonNode* pCompositeEntry: compositeEntries->value) { for (const JsonNode* pCompositeEntry : compositeEntries->value) {
JsonGet compositeEntry = pCompositeEntry->value; JsonGet compositeEntry = pCompositeEntry->value;
GhidraCompositeMember member; GhidraCompositeMember member;
member.fieldName = compositeEntry.getStringOr("fieldName", ""); member.fieldName = compositeEntry.getStringOr("fieldName", "");

View File

@ -63,13 +63,13 @@ struct GhidraType {
}; };
class GhidraClient { class GhidraClient {
public:
enum class Status { enum class Status {
Idle, Idle,
Pending, Pending,
Ready, Ready,
}; };
public:
struct Result { struct Result {
std::vector<GhidraSymbol> symbols; std::vector<GhidraSymbol> symbols;
std::unordered_map<std::string, GhidraType> types; std::unordered_map<std::string, GhidraType> types;

View File

@ -3,6 +3,7 @@
#include "ext/imgui/imgui.h" #include "ext/imgui/imgui.h"
#include "Common/System/Request.h"
#include "Core/MemMap.h" #include "Core/MemMap.h"
#include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/Breakpoints.h"
#include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSDebugInterface.h"
@ -190,7 +191,7 @@ static constexpr int COLUMN_CONTENT = 2;
void ImStructViewer::Draw(MIPSDebugInterface* mipsDebug, bool* open) { void ImStructViewer::Draw(MIPSDebugInterface* mipsDebug, bool* open) {
mipsDebug_ = mipsDebug; 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()) { if (!ImGui::Begin("Struct viewer", open) || !mipsDebug->isAlive() || !Memory::IsActive()) {
ImGui::End(); ImGui::End();
return; return;
@ -275,7 +276,7 @@ void ImStructViewer::DrawGlobals() {
ImGui::TableSetupColumn("Content"); ImGui::TableSetupColumn("Content");
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (const auto& symbol: ghidraClient_.result.symbols) { for (const auto& symbol : ghidraClient_.result.symbols) {
if (!symbol.label || !symbol.userDefined || symbol.dataTypePathName.empty()) { if (!symbol.label || !symbol.userDefined || symbol.dataTypePathName.empty()) {
continue; continue;
} }
@ -304,7 +305,7 @@ void ImStructViewer::DrawWatch() {
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
int watchIndex = -1; int watchIndex = -1;
for (const auto& watch: watches_) { for (const auto& watch : watches_) {
watchIndex++; watchIndex++;
if (!watchFilter_.PassFilter(watch.name.c_str())) { if (!watchFilter_.PassFilter(watch.name.c_str())) {
continue; continue;
@ -313,10 +314,9 @@ void ImStructViewer::DrawWatch() {
if (!watch.expression.empty()) { if (!watch.expression.empty()) {
u32 val; u32 val;
PostfixExpression postfix; PostfixExpression postfix;
if (mipsDebug_->initExpression(watch.expression.c_str(), postfix)) { if (mipsDebug_->initExpression(watch.expression.c_str(), postfix)
if (mipsDebug_->parseExpression(postfix, val)) { && mipsDebug_->parseExpression(postfix, val)) {
address = val; address = val;
}
} }
} else { } else {
address = watch.address; address = watch.address;
@ -346,7 +346,7 @@ void ImStructViewer::DrawNewWatchEntry() {
ImGui::SetKeyboardFocusHere(0); ImGui::SetKeyboardFocusHere(0);
} }
newWatch_.typeFilter.Draw(); newWatch_.typeFilter.Draw();
for (const auto& entry: ghidraClient_.result.types) { for (const auto& entry : ghidraClient_.result.types) {
const auto& type = entry.second; const auto& type = entry.second;
if (newWatch_.typeFilter.PassFilter(type.displayName.c_str())) { if (newWatch_.typeFilter.PassFilter(type.displayName.c_str())) {
ImGui::PushID(type.pathName.c_str()); ImGui::PushID(type.pathName.c_str());
@ -498,7 +498,7 @@ static void DrawPointerContent(
static std::string FormatEnumValue(const std::vector<GhidraEnumMember>& enumMembers, const u64 value) { static std::string FormatEnumValue(const std::vector<GhidraEnumMember>& enumMembers, const u64 value) {
std::stringstream ss; std::stringstream ss;
bool hasPrevious = false; bool hasPrevious = false;
for (const auto& member: enumMembers) { for (const auto& member : enumMembers) {
if (value & member.value) { if (value & member.value) {
if (hasPrevious) { if (hasPrevious) {
ss << " | "; ss << " | ";
@ -559,12 +559,12 @@ void ImStructViewer::DrawType(
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
// Flags used for nodes that can't be further opened // Flags used for nodes that can't be further opened
const ImGuiTreeNodeFlags leafFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | 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 // Type is missing in fetched types, this can happen e.g. if type used for watch is removed from Ghidra
if (!hasType) { if (!hasType) {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name); 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); ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
DrawTypeColumn("<missing type: %s>", typePathName, base, offset); DrawTypeColumn("<missing type: %s>", typePathName, base, offset);
ImGui::PopStyleColor(); ImGui::PopStyleColor();
@ -580,7 +580,7 @@ void ImStructViewer::DrawType(
// Handle cases where pointers or expressions point to invalid memory // Handle cases where pointers or expressions point to invalid memory
if (!Memory::IsValidAddress(address)) { if (!Memory::IsValidAddress(address)) {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name); 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT); ImGui::TableSetColumnIndex(COLUMN_CONTENT);
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY); ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
@ -596,23 +596,24 @@ void ImStructViewer::DrawType(
switch (type.kind) { switch (type.kind) {
case ENUM: { case ENUM: {
ImGui::TreeNodeEx("Enum", leafFlags, "%s", name); 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT); ImGui::TableSetColumnIndex(COLUMN_CONTENT);
const u64 value = ReadMemoryInt(address, type.length); const std::string enumString = FormatEnumValue(type.enumMembers, enumValue);
const std::string stringValue = FormatEnumValue(type.enumMembers, value); ImGui::Text("= %llx (%s)", enumValue, enumString.c_str());
ImGui::Text("= %llx (%s)", value, stringValue.c_str());
DrawIntBuiltInEditPopup(address, type.length); DrawIntBuiltInEditPopup(address, type.length);
break; break;
} }
case POINTER: { case POINTER: {
const bool nodeOpen = ImGui::TreeNodeEx("Pointer", extraTreeNodeFlags, "%s", name); 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT); ImGui::TableSetColumnIndex(COLUMN_CONTENT);
const u32 pointer = Memory::Read_U32(address);
DrawPointerContent(types, type, pointer); DrawPointerContent(types, type, pointer);
if (nodeOpen) { if (nodeOpen) {
@ -649,7 +650,7 @@ void ImStructViewer::DrawType(
} }
case ARRAY: { case ARRAY: {
const bool nodeOpen = ImGui::TreeNodeEx("Array", extraTreeNodeFlags, "%s", name); 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT); ImGui::TableSetColumnIndex(COLUMN_CONTENT);
@ -669,11 +670,11 @@ void ImStructViewer::DrawType(
case STRUCTURE: case STRUCTURE:
case UNION: { case UNION: {
const bool nodeOpen = ImGui::TreeNodeEx("Composite", extraTreeNodeFlags, "%s", name); 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
if (nodeOpen) { if (nodeOpen) {
for (const auto& member: type.compositeMembers) { for (const auto& member : type.compositeMembers) {
DrawType(base, offset + member.offset, member.typePathName, nullptr, DrawType(base, offset + member.offset, member.typePathName, nullptr,
member.fieldName.c_str(), -1); member.fieldName.c_str(), -1);
} }
@ -683,7 +684,7 @@ void ImStructViewer::DrawType(
} }
case FUNCTION_DEFINITION: case FUNCTION_DEFINITION:
ImGui::TreeNodeEx("Field", leafFlags, "%s", name); 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT); ImGui::TableSetColumnIndex(COLUMN_CONTENT);
@ -691,9 +692,11 @@ void ImStructViewer::DrawType(
break; break;
case BUILT_IN: { case BUILT_IN: {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name); ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
if (knownBuiltIns.count(typePathName)) { 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); DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT); ImGui::TableSetColumnIndex(COLUMN_CONTENT);
DrawBuiltInContent(knownBuiltIns.at(typePathName), address); DrawBuiltInContent(knownBuiltIns.at(typePathName), address);
@ -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 // At this point there is most likely some issue in the Ghidra plugin and the type wasn't
// classified to any category // classified to any category
ImGui::TreeNodeEx("Field", leafFlags, "%s", name); 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); ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
DrawTypeColumn("<not implemented type: %s>", typeDisplayName, base, offset); DrawTypeColumn("<not implemented type: %s>", typeDisplayName, base, offset);
ImGui::PopStyleColor(); ImGui::PopStyleColor();
@ -721,18 +724,33 @@ void ImStructViewer::DrawType(
ImGui::PopID(); 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( void ImStructViewer::DrawContextMenu(
const u32 base, const u32 base,
const u32 offset, const u32 offset,
const int length, const int length,
const std::string& typePathName, const std::string& typePathName,
const char* name, const char* name,
const int watchIndex const int watchIndex,
const u64* value
) { ) {
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
if (ImGui::BeginPopup("context")) { if (ImGui::BeginPopup("context")) {
const u32 address = base + offset; 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 // This might be called when iterating over existing watches so can't modify the watch vector directly here
if (watchIndex < 0) { if (watchIndex < 0) {
if (ImGui::MenuItem("Add watch")) { if (ImGui::MenuItem("Add watch")) {
@ -760,21 +778,25 @@ void ImStructViewer::DrawContextMenu(
CBreakPoints::RemoveMemCheck(address, end); CBreakPoints::RemoveMemCheck(address, end);
} }
} }
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_READ)) { const bool canAddRead = !hasMemCheck || !(memCheck.cond & MEMCHECK_READ);
if (ImGui::MenuItem("Add memory read breakpoint")) { 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); CBreakPoints::AddMemCheck(address, end, MEMCHECK_READ, BREAK_ACTION_PAUSE);
} }
} if (canAddWrite && ImGui::MenuItem("Write")) {
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE)) {
if (ImGui::MenuItem("Add memory write breakpoint")) {
CBreakPoints::AddMemCheck(address, end, MEMCHECK_WRITE, BREAK_ACTION_PAUSE); CBreakPoints::AddMemCheck(address, end, MEMCHECK_WRITE, BREAK_ACTION_PAUSE);
} }
} if (canAddWriteOnChange && ImGui::MenuItem("Write Change")) {
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE_ONCHANGE)) {
if (ImGui::MenuItem("Add memory write on change breakpoint")) {
constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE); constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE);
CBreakPoints::AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE); CBreakPoints::AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE);
} }
ImGui::EndMenu();
} }
} }

View File

@ -67,5 +67,6 @@ private:
int length, int length,
const std::string& typePathName, const std::string& typePathName,
const char* name, const char* name,
int watchIndex); int watchIndex,
const u64* value);
}; };