ImDebugger: Add initial implementation of a watch window

This commit is contained in:
Henrik Rydgård
2025-08-15 21:58:17 +02:00
parent f31a8f08ec
commit d9b7e370f3
6 changed files with 144 additions and 23 deletions

View File

@@ -1002,6 +1002,7 @@
<ClInclude Include="AVIDump.h" />
<ClInclude Include="ConfigValues.h" />
<ClInclude Include="Debugger\MemBlockInfo.h" />
<ClInclude Include="Debugger\Watch.h" />
<ClInclude Include="Debugger\WebSocket.h" />
<ClInclude Include="Debugger\WebSocket\BreakpointSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\ClientConfigSubscriber.h" />

View File

@@ -2241,6 +2241,9 @@
<ClInclude Include="..\ext\loongarch-disasm.h">
<Filter>Ext</Filter>
</ClInclude>
<ClInclude Include="Debugger\Watch.h">
<Filter>Debugger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.TXT" />
@@ -2269,4 +2272,4 @@
<Filter>Ext</Filter>
</Text>
</ItemGroup>
</Project>
</Project>

32
Core/Debugger/Watch.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <string>
#include "Common/Math/expression_parser.h"
#include "Core/MIPS/MIPSDebugInterface.h"
enum class WatchFormat {
HEX,
INT,
FLOAT,
STR,
};
struct WatchInfo {
WatchInfo() = default;
WatchInfo(const std::string &name, const std::string &expr, MIPSDebugInterface *debug, WatchFormat format = WatchFormat::HEX)
: name(name), originalExpression(expr), format(format) {
initExpression(debug, expr.c_str(), expression);
}
void SetExpression(const std::string &expr, MIPSDebugInterface *debug) {
originalExpression = expr;
initExpression(debug, expr.c_str(), expression);
}
std::string name;
std::string originalExpression;
PostfixExpression expression;
WatchFormat format = WatchFormat::HEX;
uint32_t currentValue = 0;
uint32_t lastValue = 0;
int steppingCounter = -1;
bool evaluateFailed = false;
};

View File

@@ -1817,6 +1817,87 @@ static void DrawSymbols(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro
ImGui::End();
}
ImWatchWindow::ImWatchWindow() {}
void ImWatchWindow::Draw(ImConfig &cfg, ImControl &control, MIPSDebugInterface *mipsDebug) {
if (!ImGui::Begin("Watch", &cfg.atracToolOpen) || !g_symbolMap) {
ImGui::End();
return;
}
// Refresh watches
int steppingCounter = Core_GetSteppingCounter();
int changes = false;
for (auto &watch : watches_) {
if (watch.steppingCounter != steppingCounter) {
watch.lastValue = watch.currentValue;
watch.steppingCounter = steppingCounter;
}
uint32_t prevValue = watch.currentValue;
watch.evaluateFailed = !parseExpression(mipsDebug, watch.expression, watch.currentValue);
}
if (ImGui::Button("Add Watch")) {
watches_.push_back(WatchInfo("untitled", "[0x88000000]", mipsDebug));
}
if (ImGui::BeginTable("watches", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Expression", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (int i = 0; i < (int)watches_.size(); i++) {
auto &watch = watches_[i];
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(watch.name.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(watch.originalExpression.c_str());
ImGui::TableNextColumn();
if (watch.evaluateFailed) {
ImGui::TextUnformatted("(Error)");
} else {
const uint32_t value = watch.currentValue;
float valuef = 0.0f;
switch (watch.format) {
case WatchFormat::HEX:
ImGui::Text("%08x", value);
break;
case WatchFormat::INT:
ImGui::Text("%d", value);
break;
case WatchFormat::FLOAT:
memcpy(&valuef, &value, sizeof(valuef));
ImGui::Text("%f", value);
break;
case WatchFormat::STR:
if (Memory::IsValidAddress(value)) {
uint32_t len = Memory::ValidSize(value, 255);
ImGui::Text("%.*s", len, Memory::GetCharPointer(value));
} else {
ImGui::Text("%08x", value);
}
break;
}
}
ImGui::TableNextColumn();
if (ImGui::SmallButton("X")) {
watches_.erase(watches_.begin() + i);
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
}
void ImAtracToolWindow::Load() {
if (File::ReadBinaryFileToString(Path(atracPath_), &data_)) {
track_.reset(new Track());
@@ -2052,6 +2133,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
ImGui::MenuItem("VFPU regs", nullptr, &cfg_.vfpuOpen);
ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen);
ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen);
ImGui::MenuItem("Watch", nullptr, &cfg_.watchOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Symbols")) {
@@ -2305,6 +2387,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
memDumpWindow_.Draw(cfg_, mipsDebug);
}
if (cfg_.watchOpen) {
watchWindow_.Draw(cfg_, control, mipsDebug);
}
for (int i = 0; i < 4; i++) {
if (cfg_.memViewOpen[i]) {
mem_[i].Draw(mipsDebug, cfg_, control, i);
@@ -2475,6 +2561,8 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) {
sync.Sync("logConfigOpen", &logConfigOpen, false);
sync.Sync("luaConsoleOpen", &luaConsoleOpen, false);
sync.Sync("utilityModulesOpen", &utilityModulesOpen, false);
sync.Sync("memDumpOpen", &memDumpOpen, false);
sync.Sync("watchOpen", &watchOpen, false);
sync.Sync("atracToolOpen", &atracToolOpen, false);
for (int i = 0; i < 4; i++) {
char name[64];

View File

@@ -15,6 +15,7 @@
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/DebugInterface.h"
#include "Core/Debugger/Watch.h"
#include "UI/ImDebugger/ImDisasmView.h"
#include "UI/ImDebugger/ImMemView.h"
@@ -133,6 +134,17 @@ public:
std::unique_ptr<Track> track_;
std::string error_;
std::string data_;
int editingWatchIndex_ = -1;
int editingColumn_ = 0;
};
class ImWatchWindow {
public:
ImWatchWindow();
void Draw(ImConfig &cfg, ImControl &control, MIPSDebugInterface *mipsDebug);
private:
std::vector<WatchInfo> watches_;
};
enum class ImCmd {
@@ -186,6 +198,7 @@ private:
ImStructViewer structViewer_;
ImGePixelViewerWindow pixelViewer_;
ImMemDumpWindow memDumpWindow_;
ImWatchWindow watchWindow_;
ImAtracToolWindow atracToolWindow_;
ImConsole luaConsole_;

View File

@@ -1,19 +1,13 @@
#pragma once
#include "../../Core/Debugger/DebugInterface.h"
#include "../../Core/HLE/sceKernelThread.h"
#include "../../Core/Debugger/Breakpoints.h"
#include "../../Core/Debugger/SymbolMap.h"
#include "../../Core/MIPS/MIPSStackWalk.h"
#include "Core/Debugger/DebugInterface.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/Debugger/Watch.h"
#include "Core/MIPS/MIPSStackWalk.h"
#include "Windows/W32Util/Misc.h"
enum class WatchFormat {
HEX,
INT,
FLOAT,
STR,
};
class CtrlThreadList: public GenericListControl
{
public:
@@ -111,16 +105,6 @@ private:
void DeleteWatch(int pos);
bool HasWatchChanged(int pos);
struct WatchInfo {
std::string name;
std::string originalExpression;
PostfixExpression expression;
WatchFormat format = WatchFormat::HEX;
uint32_t currentValue = 0;
uint32_t lastValue = 0;
int steppingCounter = -1;
bool evaluateFailed = false;
};
std::vector<WatchInfo> watches_;
DebugInterface *cpu_;
};