Files
ppsspp/UI/ImDebugger/ImDisasmView.h
Henrik RydgårdandClaude Opus 5 daa18fc25a ImDebugger: fix stale symbol list after a game is reloaded
The disasm window cached the flattened symbol list and only rebuilt it when one
of three menu items said so. Nothing marked it dirty when a game booted or
exited, and a new SymbolMap is allocated per boot, so the list kept showing the
previous game's functions.

Give SymbolMap a version counter that every mutator bumps, and let the window
compare against it instead. The counter is process-wide rather than per-map, so
a fresh map can't hand out a version a cached copy already holds.

Also re-find the selected symbol by address after a rebuild (the index means
something else afterwards), and drop the unused symbol cache members in
ImMemWindow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 10:43:08 +02:00

235 lines
6.4 KiB
C++

#pragma once
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include "ext/imgui/imgui.h"
#include "Common/CommonTypes.h"
#include "Common/Math/geom2d.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/MIPS/MIPSDebugInterface.h"
struct ImConfig;
struct ImControl;
class MIPSState;
// Corresponds to CtrlDisAsmView
// TODO: Fold out common code.
class ImDisasmView {
public:
ImDisasmView();
~ImDisasmView();
// Public variables bounds to imgui checkboxes
bool followPC_ = true;
void Draw(ImDrawList *drawList, ImControl &control);
void PopupMenu(MIPSState *mips, ImControl &control);
void NotifyStep();
void ScrollRelative(int amount);
void onChar(int c);
void onMouseDown(float x, float y, int button);
void onMouseUp(float x, float y, int button);
void onMouseMove(float x, float y, int button);
void scrollAddressIntoView();
bool curAddressIsVisible();
void ScanVisibleFunctions();
void clearFunctions() { g_disassemblyManager.clear(); };
void getOpcodeText(u32 address, char *dest, int bufsize, bool insertSymbols);
u32 yToAddress(float y);
void setDebugger(MIPSDebugInterface *deb) {
if (debugger_ != deb) {
debugger_ = deb;
curAddress_ = debugger_->GetPC();
g_disassemblyManager.setCpu(deb);
}
}
MIPSDebugInterface *getDebugger() {
return debugger_;
}
void scrollStepping(u32 newPc);
void gotoAddr(unsigned int addr) {
if (positionLocked_ != 0)
return;
u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_);
u32 newAddress = g_disassemblyManager.getStartAddress(addr);
if (newAddress < windowStart_ || newAddress >= windowEnd) {
windowStart_ = g_disassemblyManager.getNthPreviousAddress(newAddress, visibleRows_ / 2);
}
setCurAddress(newAddress);
ScanVisibleFunctions();
}
void GotoPC() {
gotoAddr(debugger_->GetPC());
}
void GotoRA() {
gotoAddr(debugger_->GetRA());
}
u32 getSelection() {
return curAddress_;
}
void setShowMode(bool s) {
showHex_ = s;
}
void toggleBreakpoint(bool toggleEnabled = false);
void editBreakpoint(ImConfig &cfg);
void setCurAddress(u32 newAddress, bool extend = false) {
newAddress = g_disassemblyManager.getStartAddress(newAddress);
const u32 after = g_disassemblyManager.getNthNextAddress(newAddress, 1);
curAddress_ = newAddress;
selectRangeStart_ = extend ? std::min(selectRangeStart_, newAddress) : newAddress;
selectRangeEnd_ = extend ? std::max(selectRangeEnd_, after) : after;
updateStatusBarText();
}
void LockPosition() {
positionLocked_++;
}
void UnlockPosition() {
positionLocked_--;
_assert_(positionLocked_ >= 0);
}
void Search(std::string_view needle);
void SearchNext(bool forward);
// Check these every frame!
const std::string &StatusBarText() const {
return statusBarText_;
}
bool SymbolMapReloaded() {
bool retval = mapReloaded_;
mapReloaded_ = false;
return retval;
}
private:
enum class CopyInstructionsMode {
OPCODES,
DISASM,
ADDRESSES,
};
void ProcessKeyboardShortcuts(bool focused);
// Plants the one-shot "run to cursor" breakpoint and resumes. With nextFrame, hits are ignored
// until the next vblank, so the rest of the current frame is skipped over.
void RunToAddress(u32 address, bool nextFrame);
// Requests the assemble popup - the actual input happens in PopupMenu(), since ImGui popups
// can only be opened and drawn from inside the frame that owns them.
void assembleOpcode(u32 address, std::string_view defaultText, bool selectAll = false);
// Same, prefilled with the instruction that's already there, selected so typing replaces it.
void assembleCurrentOpcode(u32 address);
// Applies what was typed into that popup. Returns false and fills in assembleError_ if it
// couldn't be assembled, so the popup can stay open and show why.
bool applyAssembly(u32 address, std::string_view op);
std::string disassembleRange(u32 start, u32 size);
void disassembleToFile();
void FollowBranch();
void calculatePixelPositions();
bool getDisasmAddressText(u32 address, char *dest, size_t bufSize, bool abbreviateLabels, bool showData);
void updateStatusBarText();
void drawBranchLine(ImDrawList *list, Bounds rc, std::map<u32, float> &addressPositions, const BranchLine &line);
void CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode);
std::set<std::string> getSelectedLineArguments();
void drawArguments(ImDrawList *list, Bounds rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set<std::string> &currentArguments);
u32 curAddress_ = 0;
u32 selectRangeStart_ = 0;
u32 selectRangeEnd_ = 0;
float rowHeight_ = 0.f;
float charWidth_ = 0.f;
bool bpPopup_ = false;
bool hasFocus_ = true;
bool showHex_ = false;
MIPSDebugInterface *debugger_ = nullptr;
u32 windowStart_ = 0;
int visibleRows_ = 1;
bool displaySymbols_ = true;
struct {
int addressStart;
int opcodeStart;
int argumentsStart;
int arrowsStart;
} pixelPositions_{};
std::vector<u32> jumpStack_;
std::string searchQuery_;
int matchAddress_;
bool searching_ = false;
bool keyTaken = false;
bool mapReloaded_ = false;
int positionLocked_ = 0;
std::string statusBarText_;
u32 funcBegin_ = 0;
char funcNameTemp_[128]{};
bool assemblePopup_ = false;
u32 assembleAddress_ = 0;
char assembleTemp_[256]{};
bool assembleSelectAll_ = false;
std::string assembleError_;
};
// Corresponds to the CDisasm dialog
class ImDisasmWindow {
public:
void Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState);
ImDisasmView &View() {
return disasmView_;
}
void NotifyStep() {
disasmView_.NotifyStep();
}
const char *Title() const {
return "CPU Debugger";
}
private:
// We just keep the state directly in the window. Can refactor later.
enum {
INVALID_ADDR = 0xFFFFFFFF,
};
u32 gotoAddr_ = 0x08800000;
// Symbol cache. Rebuilt whenever the symbol map's version no longer matches the one the
// cache was built from - that covers the map being replaced wholesale when a game boots or
// exits, not just edits made from here.
std::vector<SymbolEntry> symCache_;
uint32_t symCacheVersion_ = 0;
int selectedSymbol_ = -1;
char selectedSymbolName_[128];
// Filter over symCache_. Held as indices into it rather than a filtered copy, so
// selectedSymbol_ keeps meaning the same thing whether or not a filter is active.
char symFilter_[64]{};
std::vector<int> symMatches_;
bool symMatchesDirty_ = true;
ImDisasmView disasmView_;
char searchTerm_[64]{};
};