From f9703217fc32cf614df34f76d4175ae5157c45a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 18 Aug 2026 11:13:33 +0200 Subject: [PATCH] ImDebugger: right-click menu on memory blocks, with copy and breakpoint The block list in the memory windows was navigation-only - click to jump there, and that was it. Right-clicking a block now offers the two things you actually want once you've found one: - Copy info to clipboard. More than the status bar shows (range, size, the PC that allocated it, ticks, flags, allocated state), since the reason to copy it is to keep it - for a bug report or to compare two runs. - Add memory breakpoint, covering the whole block rather than a single address. That's the point of doing it from this list: you want to catch anything touching the allocation, not one byte of it. Read and write, pause and log. It then opens and focuses the Breakpoints window, via a new SHOW_IN_BREAKPOINTS command so window activation stays with the other ImCmds rather than poking at the config from here. Only on the blocks themselves, not on the synthetic "(start)" and "(end)" entries, which have no block info behind them to copy or watch. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 --- UI/ImDebugger/ImCommand.h | 1 + UI/ImDebugger/ImDebugger.cpp | 6 +++++ UI/ImDebugger/ImMemView.cpp | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/UI/ImDebugger/ImCommand.h b/UI/ImDebugger/ImCommand.h index 978a4eb881..d488de9760 100644 --- a/UI/ImDebugger/ImCommand.h +++ b/UI/ImDebugger/ImCommand.h @@ -10,6 +10,7 @@ enum class ImCmd { SHOW_IN_MEMORY_VIEWER, // param is address, param2 is viewer index SHOW_IN_PIXEL_VIEWER, // param is address, param2 is stride, |0x80000000 if depth, param3 is w/h SHOW_IN_MEMORY_DUMPER, // param is address, param2 is size, param3 is mode + SHOW_IN_BREAKPOINTS, // no params, just opens and focuses the breakpoint list }; struct ImCommand { diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 33304a6f69..df86bb6b5c 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -2934,6 +2934,12 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: ImGui::SetWindowFocus(ImMemWindow::Title(index)); break; } + case ImCmd::SHOW_IN_BREAKPOINTS: + { + cfg_.breakpointsOpen = true; + ImGui::SetWindowFocus("Breakpoints"); + break; + } case ImCmd::SHOW_IN_MEMORY_DUMPER: { cfg_.memDumpOpen = true; diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index ce182b664d..85da5f27ab 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -16,6 +16,8 @@ #include "Core/Reporting.h" #include "Core/RetroAchievements.h" #include "Common/System/Display.h" +#include "Common/System/System.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/System.h" #include "UI/ImDebugger/ImDebugger.h" @@ -587,6 +589,33 @@ void ImMemView::onMouseMove(float x, float y, int button) { } } +// Everything the block list knows about a block, in a form worth pasting into a bug report. +// Deliberately more than the status bar shows - the whole reason to copy it is to keep it. +static std::string DescribeMemBlockInfo(const MemBlockInfo &info) { + std::string flags; + auto addFlag = [&flags, &info](MemBlockFlags flag, const char *name) { + if ((uint32_t)(info.flags & flag) == 0) + return; + if (!flags.empty()) + flags += "|"; + flags += name; + }; + addFlag(MemBlockFlags::ALLOC, "ALLOC"); + addFlag(MemBlockFlags::FREE, "FREE"); + addFlag(MemBlockFlags::SUB_ALLOC, "SUB_ALLOC"); + addFlag(MemBlockFlags::SUB_FREE, "SUB_FREE"); + addFlag(MemBlockFlags::WRITE, "WRITE"); + addFlag(MemBlockFlags::READ, "READ"); + addFlag(MemBlockFlags::TEXTURE, "TEXTURE"); + if (flags.empty()) + flags = "none"; + + return StringFromFormat( + "%s\n%08x-%08x (%d bytes)\nPC: %08x\nTicks: %lld\nFlags: %s\nAllocated: %s\n", + info.tag.c_str(), info.start, info.start + info.size, (int)info.size, info.pc, + (long long)info.ticks, flags.c_str(), info.allocated ? "yes" : "no"); +} + void ImMemView::updateStatusBarText() { std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); char text[512]; @@ -1141,6 +1170,20 @@ void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl & cfg.selectedMemoryBlock = iter.start; GotoAddr(iter.start); } + if (ImGui::BeginPopupContextItem("blockinfo")) { + ImGui::TextUnformatted(iter.tag.c_str()); + ImGui::Separator(); + if (ImGui::MenuItem("Copy info to clipboard")) { + System_CopyStringToClipboard(DescribeMemBlockInfo(iter)); + } + if (ImGui::MenuItem("Add memory breakpoint")) { + // Covers the whole block rather than a single address - the point of doing + // this from the block list is to catch anything touching the allocation. + g_breakpoints.AddMemCheck(iter.start, iter.start + iter.size, MEMCHECK_READWRITE, BREAK_ACTION_PAUSE | BREAK_ACTION_LOG); + control.command = { ImCmd::SHOW_IN_BREAKPOINTS }; + } + ImGui::EndPopup(); + } ImGui::PopID(); } const u32 end = start + len;