mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-26 23:10:38 +00:00
Add struct viewer debugging tool
This commit is contained in:
parent
fdf8ff7d94
commit
3182cc29e4
@ -1525,6 +1525,8 @@ list(APPEND NativeAppSource
|
|||||||
UI/ImDebugger/ImDebugger.h
|
UI/ImDebugger/ImDebugger.h
|
||||||
UI/ImDebugger/ImDisasmView.cpp
|
UI/ImDebugger/ImDisasmView.cpp
|
||||||
UI/ImDebugger/ImDisasmView.h
|
UI/ImDebugger/ImDisasmView.h
|
||||||
|
UI/ImDebugger/ImStructViewer.cpp
|
||||||
|
UI/ImDebugger/ImStructViewer.h
|
||||||
UI/DiscordIntegration.cpp
|
UI/DiscordIntegration.cpp
|
||||||
UI/NativeApp.cpp
|
UI/NativeApp.cpp
|
||||||
UI/BackgroundAudio.h
|
UI/BackgroundAudio.h
|
||||||
|
@ -377,6 +377,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug) {
|
|||||||
ImGui::Checkbox("HLE Modules", &cfg_.modulesOpen);
|
ImGui::Checkbox("HLE Modules", &cfg_.modulesOpen);
|
||||||
ImGui::Checkbox("HLE Threads", &cfg_.threadsOpen);
|
ImGui::Checkbox("HLE Threads", &cfg_.threadsOpen);
|
||||||
ImGui::Checkbox("sceAtrac", &cfg_.atracOpen);
|
ImGui::Checkbox("sceAtrac", &cfg_.atracOpen);
|
||||||
|
ImGui::Checkbox("Struct viewer", &cfg_.structViewerOpen);
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
ImGui::EndMainMenuBar();
|
ImGui::EndMainMenuBar();
|
||||||
@ -411,6 +412,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DrawHLEModules(cfg_);
|
DrawHLEModules(cfg_);
|
||||||
|
|
||||||
|
if (&cfg_.structViewerOpen) {
|
||||||
|
structViewer_.Draw(mipsDebug, &cfg_.structViewerOpen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, CoreState coreState) {
|
void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, CoreState coreState) {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "Core/Debugger/DebugInterface.h"
|
#include "Core/Debugger/DebugInterface.h"
|
||||||
|
|
||||||
#include "UI/ImDebugger/ImDisasmView.h"
|
#include "UI/ImDebugger/ImDisasmView.h"
|
||||||
|
#include "UI/ImDebugger/ImStructViewer.h"
|
||||||
|
|
||||||
// This is the main state container of the whole Dear ImGUI-based in-game cross-platform debugger.
|
// This is the main state container of the whole Dear ImGUI-based in-game cross-platform debugger.
|
||||||
//
|
//
|
||||||
@ -57,6 +58,7 @@ struct ImConfig {
|
|||||||
bool modulesOpen = true;
|
bool modulesOpen = true;
|
||||||
bool hleModulesOpen = false;
|
bool hleModulesOpen = false;
|
||||||
bool atracOpen = true;
|
bool atracOpen = true;
|
||||||
|
bool structViewerOpen = false;
|
||||||
|
|
||||||
// HLE explorer settings
|
// HLE explorer settings
|
||||||
// bool filterByUsed = true;
|
// bool filterByUsed = true;
|
||||||
@ -71,6 +73,7 @@ struct ImDebugger {
|
|||||||
|
|
||||||
ImDisasmWindow disasm_;
|
ImDisasmWindow disasm_;
|
||||||
ImLuaConsole luaConsole_;
|
ImLuaConsole luaConsole_;
|
||||||
|
ImStructViewer structViewer_;
|
||||||
|
|
||||||
// Open variables.
|
// Open variables.
|
||||||
ImConfig cfg_;
|
ImConfig cfg_;
|
||||||
|
783
UI/ImDebugger/ImStructViewer.cpp
Normal file
783
UI/ImDebugger/ImStructViewer.cpp
Normal file
@ -0,0 +1,783 @@
|
|||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "ext/imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "Core/MemMap.h"
|
||||||
|
#include "Core/Debugger/Breakpoints.h"
|
||||||
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
||||||
|
|
||||||
|
#include "UI/ImDebugger/ImStructViewer.h"
|
||||||
|
|
||||||
|
static auto COLOR_GRAY = ImVec4(0.45f, 0.45f, 0.45f, 1);
|
||||||
|
static auto COLOR_RED = ImVec4(1, 0, 0, 1);
|
||||||
|
|
||||||
|
enum class BuiltInType {
|
||||||
|
Bool,
|
||||||
|
Char,
|
||||||
|
Int8,
|
||||||
|
Int16,
|
||||||
|
Int32,
|
||||||
|
Int64,
|
||||||
|
TerminatedString,
|
||||||
|
Float,
|
||||||
|
Void,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BuiltIn {
|
||||||
|
BuiltInType type;
|
||||||
|
ImGuiDataType imGuiType;
|
||||||
|
const char* hexFormat;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::unordered_map<std::string, BuiltIn> knownBuiltIns = {
|
||||||
|
{"/bool", {BuiltInType::Bool, ImGuiDataType_U8, "%hhx"}},
|
||||||
|
|
||||||
|
{"/char", {BuiltInType::Char, ImGuiDataType_S8, "%hhx"}},
|
||||||
|
{"/uchar", {BuiltInType::Char, ImGuiDataType_U8, "%hhx"}},
|
||||||
|
|
||||||
|
{"/byte", {BuiltInType::Int8, ImGuiDataType_U8, "%hhx"}},
|
||||||
|
{"/sbyte", {BuiltInType::Int8, ImGuiDataType_S8, "%hhx"}},
|
||||||
|
{"/undefined1", {BuiltInType::Int8, ImGuiDataType_U8, "%hhx"}},
|
||||||
|
|
||||||
|
{"/word", {BuiltInType::Int16, ImGuiDataType_U16, "%hx"}},
|
||||||
|
{"/sword", {BuiltInType::Int16, ImGuiDataType_S16, "%hx"}},
|
||||||
|
{"/ushort", {BuiltInType::Int16, ImGuiDataType_U16, "%hx"}},
|
||||||
|
{"/short", {BuiltInType::Int16, ImGuiDataType_S16, "%hx"}},
|
||||||
|
{"/undefined2", {BuiltInType::Int16, ImGuiDataType_U16, "%hx"}},
|
||||||
|
|
||||||
|
{"/dword", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
|
||||||
|
{"/sdword", {BuiltInType::Int32, ImGuiDataType_S32, "%lx"}},
|
||||||
|
{"/uint", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
|
||||||
|
{"/int", {BuiltInType::Int32, ImGuiDataType_S32, "%lx"}},
|
||||||
|
{"/ulong", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
|
||||||
|
{"/long", {BuiltInType::Int32, ImGuiDataType_S32, "%lx"}},
|
||||||
|
{"/undefined4", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
|
||||||
|
|
||||||
|
{"/qword", {BuiltInType::Int64, ImGuiDataType_U64, "%llx"}},
|
||||||
|
{"/sqword", {BuiltInType::Int64, ImGuiDataType_S64, "%llx"}},
|
||||||
|
{"/ulonglong", {BuiltInType::Int64, ImGuiDataType_U64, "%llx"}},
|
||||||
|
{"/longlong", {BuiltInType::Int64, ImGuiDataType_S64, "%llx"}},
|
||||||
|
{"/undefined8", {BuiltInType::Int64, ImGuiDataType_U64, "%llx"}},
|
||||||
|
|
||||||
|
{"/TerminatedCString", {BuiltInType::TerminatedString, -1, nullptr}},
|
||||||
|
|
||||||
|
{"/float", {BuiltInType::Float, ImGuiDataType_Float, nullptr}},
|
||||||
|
{"/float4", {BuiltInType::Float, ImGuiDataType_Float, nullptr}},
|
||||||
|
|
||||||
|
{"/void", {BuiltInType::Void, -1, nullptr}},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void DrawBuiltInEditPopup(const BuiltIn& builtIn, const u32 address) {
|
||||||
|
if (builtIn.imGuiType == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::OpenPopupOnItemClick("edit", ImGuiPopupFlags_MouseButtonRight);
|
||||||
|
if (ImGui::BeginPopup("edit")) {
|
||||||
|
if (ImGui::Selectable("Set to zero")) {
|
||||||
|
switch (builtIn.type) {
|
||||||
|
case BuiltInType::Bool:
|
||||||
|
case BuiltInType::Char:
|
||||||
|
case BuiltInType::Int8:
|
||||||
|
Memory::Write_U8(0, address);
|
||||||
|
break;
|
||||||
|
case BuiltInType::Int16:
|
||||||
|
Memory::Write_U16(0, address);
|
||||||
|
break;
|
||||||
|
case BuiltInType::Int32:
|
||||||
|
Memory::Write_U32(0, address);
|
||||||
|
break;
|
||||||
|
case BuiltInType::Int64:
|
||||||
|
Memory::Write_U64(0, address);
|
||||||
|
break;
|
||||||
|
case BuiltInType::Float:
|
||||||
|
Memory::Write_Float(0, address);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void* data = Memory::GetPointerWriteUnchecked(address);
|
||||||
|
if (builtIn.hexFormat) {
|
||||||
|
ImGui::DragScalar("Value (hex)", builtIn.imGuiType, data, 0.2f, nullptr, nullptr, builtIn.hexFormat);
|
||||||
|
}
|
||||||
|
ImGui::DragScalar("Value", builtIn.imGuiType, data, 0.2f);
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawIntBuiltInEditPopup(const u32 address, const u32 length) {
|
||||||
|
switch (length) {
|
||||||
|
case 1:
|
||||||
|
DrawBuiltInEditPopup(knownBuiltIns.at("/byte"), address);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
DrawBuiltInEditPopup(knownBuiltIns.at("/word"), address);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
DrawBuiltInEditPopup(knownBuiltIns.at("/dword"), address);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
DrawBuiltInEditPopup(knownBuiltIns.at("/qword"), address);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawBuiltInContent(const BuiltIn& builtIn, const u32 address) {
|
||||||
|
switch (builtIn.type) {
|
||||||
|
case BuiltInType::Bool:
|
||||||
|
ImGui::Text("= %s", Memory::Read_U8(address) ? "true" : "false");
|
||||||
|
break;
|
||||||
|
case BuiltInType::Char: {
|
||||||
|
const u8 value = Memory::Read_U8(address);
|
||||||
|
if (std::isprint(value)) {
|
||||||
|
ImGui::Text("= %x '%c'", value, value);
|
||||||
|
} else {
|
||||||
|
ImGui::Text("= %x", value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BuiltInType::Int8:
|
||||||
|
ImGui::Text("= %x", Memory::Read_U8(address));
|
||||||
|
break;
|
||||||
|
case BuiltInType::Int16:
|
||||||
|
ImGui::Text("= %x", Memory::Read_U16(address));
|
||||||
|
break;
|
||||||
|
case BuiltInType::Int32:
|
||||||
|
ImGui::Text("= %x", Memory::Read_U32(address));
|
||||||
|
break;
|
||||||
|
case BuiltInType::Int64:
|
||||||
|
ImGui::Text("= %llx", Memory::Read_U64(address));
|
||||||
|
break;
|
||||||
|
case BuiltInType::TerminatedString:
|
||||||
|
if (Memory::IsValidNullTerminatedString(address)) {
|
||||||
|
ImGui::Text("= \"%s\"", Memory::GetCharPointerUnchecked(address));
|
||||||
|
} else {
|
||||||
|
ImGui::Text("= %x <invalid string @ %x>", Memory::Read_U8(address), address);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BuiltInType::Float:
|
||||||
|
ImGui::Text("= %f", Memory::Read_Float(address));
|
||||||
|
break;
|
||||||
|
case BuiltInType::Void:
|
||||||
|
ImGui::Text("<void type>");
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DrawBuiltInEditPopup(builtIn, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadMemoryInt(const u32 address, const u32 length) {
|
||||||
|
switch (length) {
|
||||||
|
case 1:
|
||||||
|
return Memory::Read_U8(address);
|
||||||
|
case 2:
|
||||||
|
return Memory::Read_U16(address);
|
||||||
|
case 4:
|
||||||
|
return Memory::Read_U32(address);
|
||||||
|
case 8:
|
||||||
|
return Memory::Read_U64(address);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr int COLUMN_NAME = 0;
|
||||||
|
static constexpr int COLUMN_TYPE = 1;
|
||||||
|
static constexpr int COLUMN_CONTENT = 2;
|
||||||
|
|
||||||
|
void ImStructViewer::Draw(MIPSDebugInterface* mipsDebug, bool* open) {
|
||||||
|
mipsDebug_ = mipsDebug;
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Struct viewer", open) || !mipsDebug->isAlive() || !Memory::IsActive()) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ghidraClient_.Ready()) {
|
||||||
|
ghidraClient_.UpdateResult();
|
||||||
|
if (!fetchedAtLeastOnce_ && !ghidraClient_.Failed()) {
|
||||||
|
fetchedAtLeastOnce_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
|
||||||
|
if (fetchedAtLeastOnce_) {
|
||||||
|
DrawStructViewer();
|
||||||
|
} else {
|
||||||
|
DrawConnectionSetup();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::BeginDisabled(!ghidraClient_.Idle());
|
||||||
|
ImGui::PushItemWidth(120);
|
||||||
|
ImGui::InputText("Host", ghidraHost_, IM_ARRAYSIZE(ghidraHost_));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputInt("Port", &ghidraPort_, 0);
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Connect")) {
|
||||||
|
ghidraClient_.FetchAll(ghidraHost_, ghidraPort_);
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
if (ghidraClient_.Idle() && ghidraClient_.Failed()) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
|
||||||
|
ImGui::TextWrapped("Error: %s", ghidraClient_.result.error.c_str());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImStructViewer::DrawStructViewer() {
|
||||||
|
ImGui::BeginDisabled(!ghidraClient_.Idle());
|
||||||
|
if (ImGui::Button("Refresh data types")) {
|
||||||
|
ghidraClient_.FetchAll(ghidraHost_, ghidraPort_);
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
if (ghidraClient_.Idle() && ghidraClient_.Failed()) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextWrapped("Error: %s", ghidraClient_.result.error.c_str());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_Reorderable)) {
|
||||||
|
if (ImGui::BeginTabItem("Globals")) {
|
||||||
|
DrawGlobals();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginTabItem("Watch")) {
|
||||||
|
DrawWatch();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImStructViewer::DrawGlobals() {
|
||||||
|
globalFilter_.Draw();
|
||||||
|
if (ImGui::BeginTable("##globals", 3,
|
||||||
|
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
|
||||||
|
ImGuiTableFlags_RowBg)) {
|
||||||
|
ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
ImGui::TableSetupColumn("Field");
|
||||||
|
ImGui::TableSetupColumn("Type");
|
||||||
|
ImGui::TableSetupColumn("Content");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& symbol: ghidraClient_.result.symbols) {
|
||||||
|
if (!symbol.label || !symbol.userDefined || symbol.dataTypePathName.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!globalFilter_.PassFilter(symbol.name.c_str())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DrawType(symbol.address, 0, symbol.dataTypePathName, nullptr, symbol.name.c_str(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImStructViewer::DrawWatch() {
|
||||||
|
DrawNewWatchEntry();
|
||||||
|
ImGui::Dummy(ImVec2(1, 6));
|
||||||
|
|
||||||
|
watchFilter_.Draw();
|
||||||
|
if (ImGui::BeginTable("##watch", 3,
|
||||||
|
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
|
||||||
|
ImGuiTableFlags_RowBg)) {
|
||||||
|
ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
ImGui::TableSetupColumn("Field");
|
||||||
|
ImGui::TableSetupColumn("Type");
|
||||||
|
ImGui::TableSetupColumn("Content");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
int watchIndex = -1;
|
||||||
|
for (const auto& watch: watches_) {
|
||||||
|
watchIndex++;
|
||||||
|
if (!watchFilter_.PassFilter(watch.name.c_str())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
u32 address = 0;
|
||||||
|
if (!watch.expression.empty()) {
|
||||||
|
u32 val;
|
||||||
|
PostfixExpression postfix;
|
||||||
|
if (mipsDebug_->initExpression(watch.expression.c_str(), postfix)) {
|
||||||
|
if (mipsDebug_->parseExpression(postfix, val)) {
|
||||||
|
address = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
address = watch.address;
|
||||||
|
}
|
||||||
|
DrawType(address, 0, watch.typePathName, nullptr, watch.name.c_str(), watchIndex);
|
||||||
|
}
|
||||||
|
if (removeWatchIndex_ != -1) {
|
||||||
|
watches_.erase(watches_.begin() + removeWatchIndex_);
|
||||||
|
removeWatchIndex_ = -1;
|
||||||
|
}
|
||||||
|
if (addWatch_.address != 0) {
|
||||||
|
watches_.push_back(addWatch_);
|
||||||
|
addWatch_ = Watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImStructViewer::DrawNewWatchEntry() {
|
||||||
|
ImGui::PushItemWidth(150);
|
||||||
|
ImGui::InputText("Name", newWatch_.name, IM_ARRAYSIZE(newWatch_.name));
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::BeginCombo("Type", newWatch_.typeDisplayName.c_str())) {
|
||||||
|
if (ImGui::IsWindowAppearing()) {
|
||||||
|
ImGui::SetKeyboardFocusHere(0);
|
||||||
|
}
|
||||||
|
newWatch_.typeFilter.Draw();
|
||||||
|
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());
|
||||||
|
if (ImGui::Selectable(type.displayName.c_str(), newWatch_.typePathName == type.pathName)) {
|
||||||
|
newWatch_.typePathName = type.pathName;
|
||||||
|
newWatch_.typeDisplayName = type.displayName;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip) && ImGui::BeginTooltip()) {
|
||||||
|
ImGui::Text("%s (%s)", type.displayName.c_str(), type.pathName.c_str());
|
||||||
|
ImGui::Text("Length: %x (aligned: %x)", type.length, type.alignedLength);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputText("Expression", newWatch_.expression, IM_ARRAYSIZE(newWatch_.expression));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Dynamic", &newWatch_.dynamic);
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal))
|
||||||
|
ImGui::SetTooltip("When checked the expression will be\nre-evaluated on each frame.");
|
||||||
|
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Add watch")) {
|
||||||
|
u32 val;
|
||||||
|
PostfixExpression postfix;
|
||||||
|
if (newWatch_.typePathName.empty()) {
|
||||||
|
newWatch_.error = "type can't be empty";
|
||||||
|
} else if (!mipsDebug_->initExpression(newWatch_.expression, postfix)
|
||||||
|
|| !mipsDebug_->parseExpression(postfix, val)) {
|
||||||
|
newWatch_.error = "invalid expression";
|
||||||
|
} else {
|
||||||
|
std::string watchName = newWatch_.name;
|
||||||
|
if (watchName.empty()) {
|
||||||
|
watchName = "<watch>";
|
||||||
|
}
|
||||||
|
watches_.emplace_back(Watch{
|
||||||
|
newWatch_.dynamic ? newWatch_.expression : "",
|
||||||
|
newWatch_.dynamic ? 0 : val,
|
||||||
|
newWatch_.typePathName,
|
||||||
|
newWatch_.dynamic ? watchName + " (" + newWatch_.expression + ")" : watchName
|
||||||
|
});
|
||||||
|
memset(newWatch_.name, 0, sizeof(newWatch_.name));
|
||||||
|
memset(newWatch_.expression, 0, sizeof(newWatch_.name));
|
||||||
|
newWatch_.dynamic = false;
|
||||||
|
newWatch_.error = "";
|
||||||
|
newWatch_.typeFilter.Clear();
|
||||||
|
// Not clearing the actual selected type on purpose here, user will have to reselect one anyway and maybe
|
||||||
|
// there is a chance they will reuse the current one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!newWatch_.error.empty()) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
|
||||||
|
ImGui::TextWrapped("Error: %s", newWatch_.error.c_str());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawTypeColumn(
|
||||||
|
const std::string& format,
|
||||||
|
const std::string& typeDisplayName,
|
||||||
|
const u32 base,
|
||||||
|
const u32 offset
|
||||||
|
) {
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_TYPE);
|
||||||
|
ImGui::Text(format.c_str(), typeDisplayName.c_str());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (offset != 0) {
|
||||||
|
ImGui::Text("@ %x+%x", base, offset);
|
||||||
|
} else {
|
||||||
|
ImGui::Text("@ %x", base);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawArrayContent(
|
||||||
|
const std::unordered_map<std::string, GhidraType>& types,
|
||||||
|
const GhidraType& type,
|
||||||
|
const u32 address
|
||||||
|
) {
|
||||||
|
if (type.arrayElementLength != 1 || !types.count(type.arrayTypePathName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& arrayType = types.at(type.arrayTypePathName);
|
||||||
|
bool charElement = false;
|
||||||
|
if (arrayType.kind == TYPEDEF && types.count(arrayType.typedefBaseTypePathName)) {
|
||||||
|
const auto& baseArrayType = types.at(arrayType.typedefBaseTypePathName);
|
||||||
|
charElement = baseArrayType.pathName == "/char";
|
||||||
|
} else {
|
||||||
|
charElement = arrayType.pathName == "/char";
|
||||||
|
}
|
||||||
|
if (!charElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char* charPointer = Memory::GetCharPointerUnchecked(address);
|
||||||
|
std::string text(charPointer, charPointer + type.arrayElementCount);
|
||||||
|
text = std::regex_replace(text, std::regex("\n"), "\\n");
|
||||||
|
ImGui::Text("= \"%s\"", text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawPointerText(const u32 value) {
|
||||||
|
if (Memory::IsValidAddress(value)) {
|
||||||
|
ImGui::Text("* %x", value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
|
||||||
|
if (value == 0) {
|
||||||
|
ImGui::Text("* NULL");
|
||||||
|
} else {
|
||||||
|
ImGui::Text("* <invalid pointer: %x>", value);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawPointerContent(
|
||||||
|
const std::unordered_map<std::string, GhidraType>& types,
|
||||||
|
const GhidraType& type,
|
||||||
|
const u32 value
|
||||||
|
) {
|
||||||
|
if (!types.count(type.pointerTypePathName)) {
|
||||||
|
DrawPointerText(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& pointedType = types.at(type.pointerTypePathName);
|
||||||
|
bool charPointerElement = false;
|
||||||
|
if (pointedType.kind == TYPEDEF && types.count(pointedType.typedefBaseTypePathName)) {
|
||||||
|
const auto& basePointedType = types.at(pointedType.typedefBaseTypePathName);
|
||||||
|
charPointerElement = basePointedType.pathName == "/char";
|
||||||
|
} else {
|
||||||
|
charPointerElement = pointedType.pathName == "/char";
|
||||||
|
}
|
||||||
|
if (!charPointerElement || !Memory::IsValidNullTerminatedString(value)) {
|
||||||
|
DrawPointerText(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char* charPointer = Memory::GetCharPointerUnchecked(value);
|
||||||
|
std::string text(charPointer);
|
||||||
|
text = std::regex_replace(text, std::regex("\n"), "\\n");
|
||||||
|
ImGui::Text("= \"%s\"", text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatting enum value to a nice string as it would look in code with 'or' operator (e.g. "ALIGN_TOP | ALIGN_LEFT")
|
||||||
|
static std::string FormatEnumValue(const std::vector<GhidraEnumMember>& enumMembers, const u64 value) {
|
||||||
|
std::stringstream ss;
|
||||||
|
bool hasPrevious = false;
|
||||||
|
for (const auto& member: enumMembers) {
|
||||||
|
if (value & member.value) {
|
||||||
|
if (hasPrevious) {
|
||||||
|
ss << " | ";
|
||||||
|
}
|
||||||
|
ss << member.name;
|
||||||
|
hasPrevious = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will be potentially called a lot of times in a frame so not using string here
|
||||||
|
static void FormatIndexedMember(char* buffer, const size_t bufferSize, const std::string& name, const u32 index) {
|
||||||
|
snprintf(buffer, bufferSize, "%s[%x]", name.c_str(), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImStructViewer::DrawType(
|
||||||
|
const u32 base,
|
||||||
|
const u32 offset,
|
||||||
|
const std::string& typePathName,
|
||||||
|
const char* typeDisplayNameOverride,
|
||||||
|
const char* name,
|
||||||
|
const int watchIndex,
|
||||||
|
const ImGuiTreeNodeFlags extraTreeNodeFlags
|
||||||
|
) {
|
||||||
|
const auto& types = ghidraClient_.result.types;
|
||||||
|
// Generic pointer is not included in the type listing, need to resolve it manually to void*
|
||||||
|
if (typePathName == "/pointer") {
|
||||||
|
DrawType(base, offset, "/void *", "pointer", name, watchIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Undefined itself doesn't exist as a type, let's just display first byte in that case
|
||||||
|
if (typePathName == "/undefined") {
|
||||||
|
DrawType(base, offset, "/undefined1", "undefined", name, watchIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hasType = types.count(typePathName) != 0;
|
||||||
|
|
||||||
|
// Resolve typedefs as early as possible
|
||||||
|
if (hasType) {
|
||||||
|
const auto& type = types.at(typePathName);
|
||||||
|
if (type.kind == TYPEDEF) {
|
||||||
|
DrawType(base, offset, type.typedefBaseTypePathName, type.displayName.c_str(), name,
|
||||||
|
watchIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 address = base + offset;
|
||||||
|
ImGui::PushID(static_cast<int>(address));
|
||||||
|
ImGui::PushID(watchIndex);
|
||||||
|
|
||||||
|
// Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing
|
||||||
|
// to make the tree lines equal high.
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_NAME);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
// Flags used for nodes that can't be further opened
|
||||||
|
const ImGuiTreeNodeFlags leafFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
||||||
|
ImGuiTreeNodeFlags_Bullet | 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);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
|
||||||
|
DrawTypeColumn("<missing type: %s>", typePathName, base, offset);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::PopID();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& type = types.at(typePathName);
|
||||||
|
const std::string typeDisplayName =
|
||||||
|
typeDisplayNameOverride == nullptr ? type.displayName : typeDisplayNameOverride;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
DrawTypeColumn("%s", typeDisplayName, base, offset);
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
|
||||||
|
ImGui::Text("<invalid address: %x>", address);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::PopID();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each type we create tree node with the field name and fill type column
|
||||||
|
// Content column and edit popup is only set for types where it makes sense
|
||||||
|
switch (type.kind) {
|
||||||
|
case ENUM: {
|
||||||
|
ImGui::TreeNodeEx("Enum", leafFlags, "%s", name);
|
||||||
|
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
|
||||||
|
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());
|
||||||
|
DrawIntBuiltInEditPopup(address, type.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case POINTER: {
|
||||||
|
const bool nodeOpen = ImGui::TreeNodeEx("Pointer", extraTreeNodeFlags, "%s", name);
|
||||||
|
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
|
||||||
|
DrawTypeColumn("%s", typeDisplayName, base, offset);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
|
||||||
|
const u32 pointer = Memory::Read_U32(address);
|
||||||
|
DrawPointerContent(types, type, pointer);
|
||||||
|
|
||||||
|
if (nodeOpen) {
|
||||||
|
if (types.count(type.pointerTypePathName)) {
|
||||||
|
const auto& pointedType = types.at(type.pointerTypePathName);
|
||||||
|
|
||||||
|
const auto countStateId = ImGui::GetID("PointerElementCount");
|
||||||
|
const int pointerElementCount = ImGui::GetStateStorage()->GetInt(countStateId, 1);
|
||||||
|
|
||||||
|
// A pointer to unsized type (e.g. function or void) can't have more than one element
|
||||||
|
if (pointedType.alignedLength > 0) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_NAME);
|
||||||
|
if (ImGui::Button("Show more")) {
|
||||||
|
ImGui::GetStateStorage()->SetInt(countStateId, pointerElementCount + 1);
|
||||||
|
}
|
||||||
|
if (pointerElementCount > 1) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("(showing %x)", pointerElementCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pointerElementCount; i++) {
|
||||||
|
char nameBuffer[256];
|
||||||
|
FormatIndexedMember(nameBuffer, sizeof(nameBuffer), name, i);
|
||||||
|
// A pointer always creates extra node in the tree so using DefaultOpen to spare user one click
|
||||||
|
DrawType(pointer, i * pointedType.alignedLength, type.pointerTypePathName,
|
||||||
|
nullptr, nameBuffer, -1, ImGuiTreeNodeFlags_DefaultOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ARRAY: {
|
||||||
|
const bool nodeOpen = ImGui::TreeNodeEx("Array", extraTreeNodeFlags, "%s", name);
|
||||||
|
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
|
||||||
|
DrawTypeColumn("%s", typeDisplayName, base, offset);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
|
||||||
|
DrawArrayContent(types, type, address);
|
||||||
|
|
||||||
|
if (nodeOpen) {
|
||||||
|
for (int i = 0; i < type.arrayElementCount; i++) {
|
||||||
|
char nameBuffer[256];
|
||||||
|
FormatIndexedMember(nameBuffer, sizeof(nameBuffer), name, i);
|
||||||
|
DrawType(base, offset + i * type.arrayElementLength, type.arrayTypePathName,
|
||||||
|
nullptr, nameBuffer, -1);
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STRUCTURE:
|
||||||
|
case UNION: {
|
||||||
|
const bool nodeOpen = ImGui::TreeNodeEx("Composite", extraTreeNodeFlags, "%s", name);
|
||||||
|
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
|
||||||
|
DrawTypeColumn("%s", typeDisplayName, base, offset);
|
||||||
|
|
||||||
|
if (nodeOpen) {
|
||||||
|
for (const auto& member: type.compositeMembers) {
|
||||||
|
DrawType(base, offset + member.offset, member.typePathName, nullptr,
|
||||||
|
member.fieldName.c_str(), -1);
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FUNCTION_DEFINITION:
|
||||||
|
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
|
||||||
|
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchIndex);
|
||||||
|
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)) {
|
||||||
|
DrawTypeColumn("%s", typeDisplayName, base, offset);
|
||||||
|
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
|
||||||
|
DrawBuiltInContent(knownBuiltIns.at(typePathName), address);
|
||||||
|
} else {
|
||||||
|
// Some built in types are rather obscure so we don't handle every possible one
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
|
||||||
|
DrawTypeColumn("<unsupported built in: %s>", typePathName, base, offset);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// 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);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
|
||||||
|
DrawTypeColumn("<not implemented type: %s>", typeDisplayName, base, offset);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImStructViewer::DrawContextMenu(
|
||||||
|
const u32 base,
|
||||||
|
const u32 offset,
|
||||||
|
const int length,
|
||||||
|
const std::string& typePathName,
|
||||||
|
const char* name,
|
||||||
|
const int watchIndex
|
||||||
|
) {
|
||||||
|
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
|
||||||
|
if (ImGui::BeginPopup("context")) {
|
||||||
|
const u32 address = base + offset;
|
||||||
|
|
||||||
|
// 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")) {
|
||||||
|
addWatch_.address = address;
|
||||||
|
addWatch_.typePathName = typePathName;
|
||||||
|
addWatch_.name = name;
|
||||||
|
}
|
||||||
|
} else if (watchIndex < watches_.size()) {
|
||||||
|
if (ImGui::MenuItem("Remove watch")) {
|
||||||
|
removeWatchIndex_ = watchIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (ImGui::MenuItem("Go to in memory view")) {
|
||||||
|
// TODO imgui memory view not yet implemented
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Memory breakpoints are only possible for sized types
|
||||||
|
if (length > 0) {
|
||||||
|
const u32 end = address + length;
|
||||||
|
MemCheck memCheck;
|
||||||
|
const bool hasMemCheck = CBreakPoints::GetMemCheck(address, end, &memCheck);
|
||||||
|
if (hasMemCheck) {
|
||||||
|
if (ImGui::MenuItem("Remove memory breakpoint")) {
|
||||||
|
CBreakPoints::RemoveMemCheck(address, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_READ)) {
|
||||||
|
if (ImGui::MenuItem("Add memory read breakpoint")) {
|
||||||
|
CBreakPoints::AddMemCheck(address, end, MEMCHECK_READ, BREAK_ACTION_PAUSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE)) {
|
||||||
|
if (ImGui::MenuItem("Add memory write breakpoint")) {
|
||||||
|
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")) {
|
||||||
|
constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE);
|
||||||
|
CBreakPoints::AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
71
UI/ImDebugger/ImStructViewer.h
Normal file
71
UI/ImDebugger/ImStructViewer.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ext/imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "Common/GhidraClient.h"
|
||||||
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
||||||
|
|
||||||
|
class ImStructViewer {
|
||||||
|
struct Watch {
|
||||||
|
std::string expression;
|
||||||
|
u32 address = 0;
|
||||||
|
std::string typePathName;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NewWatch {
|
||||||
|
char name[256] = {};
|
||||||
|
std::string typeDisplayName;
|
||||||
|
std::string typePathName;
|
||||||
|
char expression[256] = {};
|
||||||
|
bool dynamic = false;
|
||||||
|
std::string error;
|
||||||
|
ImGuiTextFilter typeFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Draw(MIPSDebugInterface* mipsDebug, bool* open);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MIPSDebugInterface* mipsDebug_ = nullptr;
|
||||||
|
|
||||||
|
ImGuiTextFilter globalFilter_;
|
||||||
|
ImGuiTextFilter watchFilter_;
|
||||||
|
|
||||||
|
GhidraClient ghidraClient_;
|
||||||
|
char ghidraHost_[128] = "localhost";
|
||||||
|
int ghidraPort_ = 18489;
|
||||||
|
bool fetchedAtLeastOnce_ = false;
|
||||||
|
|
||||||
|
std::vector<Watch> watches_;
|
||||||
|
int removeWatchIndex_ = -1;
|
||||||
|
Watch addWatch_;
|
||||||
|
NewWatch newWatch_;
|
||||||
|
|
||||||
|
void DrawConnectionSetup();
|
||||||
|
|
||||||
|
void DrawStructViewer();
|
||||||
|
|
||||||
|
void DrawGlobals();
|
||||||
|
|
||||||
|
void DrawWatch();
|
||||||
|
|
||||||
|
void DrawNewWatchEntry();
|
||||||
|
|
||||||
|
void DrawType(
|
||||||
|
u32 base,
|
||||||
|
u32 offset,
|
||||||
|
const std::string& typePathName,
|
||||||
|
const char* typeDisplayNameOverride,
|
||||||
|
const char* name,
|
||||||
|
int watchIndex,
|
||||||
|
ImGuiTreeNodeFlags extraTreeNodeFlags = 0);
|
||||||
|
|
||||||
|
void DrawContextMenu(
|
||||||
|
u32 base,
|
||||||
|
u32 offset,
|
||||||
|
int length,
|
||||||
|
const std::string& typePathName,
|
||||||
|
const char* name,
|
||||||
|
int watchIndex);
|
||||||
|
};
|
@ -53,6 +53,7 @@
|
|||||||
<ClCompile Include="GPUDriverTestScreen.cpp" />
|
<ClCompile Include="GPUDriverTestScreen.cpp" />
|
||||||
<ClCompile Include="ImDebugger\ImDebugger.cpp" />
|
<ClCompile Include="ImDebugger\ImDebugger.cpp" />
|
||||||
<ClCompile Include="ImDebugger\ImDisasmView.cpp" />
|
<ClCompile Include="ImDebugger\ImDisasmView.cpp" />
|
||||||
|
<ClCompile Include="ImDebugger\ImStructViewer.cpp" />
|
||||||
<ClCompile Include="JitCompareScreen.cpp" />
|
<ClCompile Include="JitCompareScreen.cpp" />
|
||||||
<ClCompile Include="JoystickHistoryView.cpp" />
|
<ClCompile Include="JoystickHistoryView.cpp" />
|
||||||
<ClCompile Include="MainScreen.cpp" />
|
<ClCompile Include="MainScreen.cpp" />
|
||||||
@ -95,6 +96,7 @@
|
|||||||
<ClInclude Include="GPUDriverTestScreen.h" />
|
<ClInclude Include="GPUDriverTestScreen.h" />
|
||||||
<ClInclude Include="ImDebugger\ImDebugger.h" />
|
<ClInclude Include="ImDebugger\ImDebugger.h" />
|
||||||
<ClInclude Include="ImDebugger\ImDisasmView.h" />
|
<ClInclude Include="ImDebugger\ImDisasmView.h" />
|
||||||
|
<ClInclude Include="ImDebugger\ImStructViewer.h" />
|
||||||
<ClInclude Include="JitCompareScreen.h" />
|
<ClInclude Include="JitCompareScreen.h" />
|
||||||
<ClInclude Include="JoystickHistoryView.h" />
|
<ClInclude Include="JoystickHistoryView.h" />
|
||||||
<ClInclude Include="MainScreen.h" />
|
<ClInclude Include="MainScreen.h" />
|
||||||
|
@ -104,6 +104,9 @@
|
|||||||
<ClCompile Include="ImDebugger\ImDisasmView.cpp">
|
<ClCompile Include="ImDebugger\ImDisasmView.cpp">
|
||||||
<Filter>ImDebugger</Filter>
|
<Filter>ImDebugger</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="ImDebugger\ImStructViewer.cpp">
|
||||||
|
<Filter>ImDebugger</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="GameInfoCache.h" />
|
<ClInclude Include="GameInfoCache.h" />
|
||||||
@ -208,6 +211,9 @@
|
|||||||
<ClInclude Include="ImDebugger\ImDisasmView.h">
|
<ClInclude Include="ImDebugger\ImDisasmView.h">
|
||||||
<Filter>ImDebugger</Filter>
|
<Filter>ImDebugger</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="ImDebugger\ImStructViewer.h">
|
||||||
|
<Filter>ImDebugger</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="Screens">
|
<Filter Include="Screens">
|
||||||
|
@ -128,6 +128,7 @@
|
|||||||
<ClInclude Include="..\..\UI\GPUDriverTestScreen.h" />
|
<ClInclude Include="..\..\UI\GPUDriverTestScreen.h" />
|
||||||
<ClInclude Include="..\..\UI\ImDebugger\ImDebugger.h" />
|
<ClInclude Include="..\..\UI\ImDebugger\ImDebugger.h" />
|
||||||
<ClInclude Include="..\..\UI\ImDebugger\ImDisasmView.h" />
|
<ClInclude Include="..\..\UI\ImDebugger\ImDisasmView.h" />
|
||||||
|
<ClInclude Include="..\..\UI\ImDebugger\ImStructViewer.h" />
|
||||||
<ClInclude Include="..\..\UI\InstallZipScreen.h" />
|
<ClInclude Include="..\..\UI\InstallZipScreen.h" />
|
||||||
<ClInclude Include="..\..\UI\JitCompareScreen.h" />
|
<ClInclude Include="..\..\UI\JitCompareScreen.h" />
|
||||||
<ClInclude Include="..\..\UI\JoystickHistoryView.h" />
|
<ClInclude Include="..\..\UI\JoystickHistoryView.h" />
|
||||||
@ -170,6 +171,7 @@
|
|||||||
<ClCompile Include="..\..\UI\GPUDriverTestScreen.cpp" />
|
<ClCompile Include="..\..\UI\GPUDriverTestScreen.cpp" />
|
||||||
<ClCompile Include="..\..\UI\ImDebugger\ImDebugger.cpp" />
|
<ClCompile Include="..\..\UI\ImDebugger\ImDebugger.cpp" />
|
||||||
<ClCompile Include="..\..\UI\ImDebugger\ImDisasmView.cpp" />
|
<ClCompile Include="..\..\UI\ImDebugger\ImDisasmView.cpp" />
|
||||||
|
<ClCompile Include="..\..\UI\ImDebugger\ImStructViewer.cpp" />
|
||||||
<ClCompile Include="..\..\UI\InstallZipScreen.cpp" />
|
<ClCompile Include="..\..\UI\InstallZipScreen.cpp" />
|
||||||
<ClCompile Include="..\..\UI\JitCompareScreen.cpp" />
|
<ClCompile Include="..\..\UI\JitCompareScreen.cpp" />
|
||||||
<ClCompile Include="..\..\UI\JoystickHistoryView.cpp" />
|
<ClCompile Include="..\..\UI\JoystickHistoryView.cpp" />
|
||||||
|
@ -45,6 +45,9 @@
|
|||||||
<ClCompile Include="..\..\UI\ImDebugger\ImDisasmView.cpp">
|
<ClCompile Include="..\..\UI\ImDebugger\ImDisasmView.cpp">
|
||||||
<Filter>ImDebugger</Filter>
|
<Filter>ImDebugger</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\UI\ImDebugger\ImStructViewer.cpp">
|
||||||
|
<Filter>ImDebugger</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
@ -91,6 +94,9 @@
|
|||||||
<ClInclude Include="..\..\UI\ImDebugger\ImDisasmView.h">
|
<ClInclude Include="..\..\UI\ImDebugger\ImDisasmView.h">
|
||||||
<Filter>ImDebugger</Filter>
|
<Filter>ImDebugger</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\UI\ImDebugger\ImStructViewer.h">
|
||||||
|
<Filter>ImDebugger</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="ImDebugger">
|
<Filter Include="ImDebugger">
|
||||||
|
@ -877,6 +877,7 @@ LOCAL_SRC_FILES := \
|
|||||||
$(SRC)/android/jni/OpenSLContext.cpp \
|
$(SRC)/android/jni/OpenSLContext.cpp \
|
||||||
$(SRC)/UI/ImDebugger/ImDebugger.cpp \
|
$(SRC)/UI/ImDebugger/ImDebugger.cpp \
|
||||||
$(SRC)/UI/ImDebugger/ImDisasmView.cpp \
|
$(SRC)/UI/ImDebugger/ImDisasmView.cpp \
|
||||||
|
$(SRC)/UI/ImDebugger/ImStructViewer.cpp \
|
||||||
$(SRC)/UI/AudioCommon.cpp \
|
$(SRC)/UI/AudioCommon.cpp \
|
||||||
$(SRC)/UI/BackgroundAudio.cpp \
|
$(SRC)/UI/BackgroundAudio.cpp \
|
||||||
$(SRC)/UI/DiscordIntegration.cpp \
|
$(SRC)/UI/DiscordIntegration.cpp \
|
||||||
|
Loading…
Reference in New Issue
Block a user