openMSX
ImGuiDebugger.cc
Go to the documentation of this file.
1#include "ImGuiDebugger.hh"
2
4#include "ImGuiBreakPoints.hh"
5#include "ImGuiCharacter.hh"
6#include "ImGuiCpp.hh"
7#include "ImGuiManager.hh"
8#include "ImGuiPalette.hh"
10#include "ImGuiSymbols.hh"
11#include "ImGuiUtils.hh"
12#include "ImGuiVdpRegs.hh"
13#include "ImGuiWatchExpr.hh"
14
15#include "CPURegs.hh"
16#include "Dasm.hh"
17#include "Debuggable.hh"
18#include "Debugger.hh"
19#include "MSXCPU.hh"
20#include "MSXCPUInterface.hh"
22#include "MSXMotherBoard.hh"
23#include "RomPlain.hh"
24#include "SymbolManager.hh"
25
26#include "narrow.hh"
27#include "join.hh"
28#include "ranges.hh"
29#include "stl.hh"
30#include "strCat.hh"
31#include "StringOp.hh"
32#include "view.hh"
33
34#include <imgui.h>
35#include <imgui_stdlib.h>
36
37#include <cstdint>
38#include <vector>
39
40using namespace std::literals;
41
42namespace openmsx {
43
45 : ImGuiPart(manager_)
46 , symbolManager(manager.getReactor().getSymbolManager())
47{
48}
49
51
52// TODO quick and dirty, doesn't look right yet
53// Created from a .ttf file via:
54// binary_to_compressed_c openmsx-debugger-icons.ttf myIcons > myfont.cpp
55// https://github.com/ocornut/imgui/blob/master/misc/fonts/binary_to_compressed_c.cpp
56static const char icons_compressed_base85[1815+1] =
57 "7])#######;9u/('/###W),##.f1$#Q6>##%[n42)[KU%G5)=-<NE/1aNV=BZrPSb]->>#ICi9.o`(*HGsO2(b6O3L+lQS%,5LsCC,H3AnAMeNA*&#Gb';9Cs3BMNvSN`sr1dD4Eo3R/"
58 "w/)8eXHkG2V3dW.;h^`I@cj`W)a4&>ZW%q/o6Q<Bou@GMQuuoJH`;-*0iR/Go%fr6jMWVRReK?-qmnUCC'Hv'<J]p]`;`Y5@C=GH?o6O+j#)*Mp]G&#70lQ#;0xfLQ7wj#O?DX-@1>F%"
59 "5[)<#8JFgL6SgV-xew@'uB5X7If]8QR3O%b'kS`a)%vW-iXx6*BfW>-'lB#$1fs-$R'Mk+(lB#$ckf&,peu.:J/PQ'-MYY#jFs9)*lXJgaDe8Qw(Y:v47Y&#*r[w'WQ1x'1/V:m^U4+3"
60 "7GJcM604,NJ:-##O]-x-e:k(NX@$##]jP]4ACh^X0(^fLHl/o#g]L7#N'+&#L)Qv$t@0+*I5^+4]77<.aQ9k0$g`,3$+h,3Pqji06rs-$=uG=3tq$>MOAQY>@'.`&K8P>#g>AvL9q*<#"
61 ")EEjLr^PA#m.Us-KRb'4g)Cb4%IuD#GKRL2KkRP/N7R20t3wK#-8/$jJA5U;0viS&[5AC>uIVS%3^oh24GbM(hIL>#xP%U#WE323lpd].B[A30GK_+,sX/D=dn5q&0####GBO&#.1d7$"
62 "3g1$#prgo.-<Tv->;gF4LZ>x#<)]L(/p^I*^nr?#&Y@C#cF&@'wvPJ(?nU^#pRpQ0gF*j0EPtdVN'+9%k*l)*0qi<%>=+dVKrdx0Iu(kLgk4DWX?/YGZv9dMb/q8/+fMO#W+WBOM>#`Q"
63 "xR--3bO1F*.D,G4rFuG-hwhH-AI7q.3B3I):[(cJ[pl6&]3KI%)B1se0fv]M6T3kuGSoBJNCZY#q+er?Y,8v,5cNY5*`$s$#]YV-[@i?#=@[s$G+TC4P#l8./=]s$1Pev6jlje3&-Xf-"
64 "e](jr145d3?;Rv$ZUvC%h5%fqxC$Y@8^CY$O,@H),W'Z-W-Rh27<C[Krsf;$.wg<L9br,4%.6v,>=+dVIIx:/@JJH)Z:Nu.iUkJ2tpPm:t0(Q$@OEp%Upn;%j&5QCa)iS0%5YY#IX$_]"
65 "2^x^]Zrju5Fhf-*i+YV-6'PA#(cK+*mtq;/qwkj1?f6<.0MbI),VW/23>%&4m$ie3%&AA4l'eq7+jkA#dlfj-&$+&^S8VTd.^AN-,CeM0l,'hoodPir`IofLX=$##5C.%T=UYF%^Yk*$"
66 "/)m<-]'LS-&%.20'>uu#OYU7#euQ['GPSw8P`&:)AF14+&YF&#WW`>$Mbk-$I&l-$Bp0.$eo4wLW(I3#Ys%(#U-4&#MXN1#EIc>#ik*$M0xSfLRq)$#@>[A-$Mg;-'Spr-gN<;Ni;-##"
67 "Jer=--`/,Mv_d##tg`=-5X`=-8sG<-*fG<-+C`T.3f1$#mrG<-8X`=-/#XN0Ht*A#e5>##_RFgLX%1kLF=`T.3xL$#)6xU.R,_'#RA`T.T5$C#<rG<-M2(@-xrG<-DKx>-#EfT%8nT`3"
68 "]kU.G-i@+H=DpTCuAo6D<B?hD-rI+HXhdxFL7-AFY'auGt$EJ1m,(@'un@VHdt6L#s[DYGB0-gD.X`'/4+auGpkn+H5-xF-^%/(#B_=?-kQU@-QRt7/uJ6(#Cm[*b[^es/c:@bHdN4/#"
69 "d>?L-NNei.Fn@-#afh]%mxtLF//5UCvs.>B,(rE-i<cM:^0[eF/1u'#.mk.#[5C/#-)IqLR`4rL;wXrLX.lrL1x.qLf.K-#J#`5/%$S-#c7]nLNM-X.Q####F/LMTxS&=u<7L8#";
70
71static constexpr ImWchar DEBUGGER_ICON_MIN = 0xead1;
72static constexpr ImWchar DEBUGGER_ICON_MAX = 0xeb8f;
73static constexpr ImWchar DEBUGGER_ICON_RUN = 0xead3;
74static constexpr ImWchar DEBUGGER_ICON_BREAK = 0xead1;
75static constexpr ImWchar DEBUGGER_ICON_STEP_IN = 0xead4;
76static constexpr ImWchar DEBUGGER_ICON_STEP_OUT = 0xead5;
77static constexpr ImWchar DEBUGGER_ICON_STEP_OVER = 0xead6;
78static constexpr ImWchar DEBUGGER_ICON_STEP_BACK = 0xeb8f;
79
81{
82 auto& io = ImGui::GetIO();
83 ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true;
84 static const ImWchar ranges[] = { DEBUGGER_ICON_MIN, DEBUGGER_ICON_MAX, 0 };
85 io.Fonts->AddFontFromMemoryCompressedBase85TTF(icons_compressed_base85, 20.0f, &icons_config, ranges);
86}
87
89{
90 syncDisassemblyWithPC = true;
91}
92
93void ImGuiDebugger::save(ImGuiTextBuffer& buf)
94{
95 savePersistent(buf, *this, persistentElements);
96
97 // TODO in the future use c++23 std::chunk_by
98 auto it = hexEditors.begin();
99 auto et = hexEditors.end();
100 while (it != et) {
101 int count = 0;
102 auto name = std::string((*it)->getDebuggableName());
103 do {
104 ++it;
105 ++count;
106 } while (it != et && (*it)->getDebuggableName() == name);
107 buf.appendf("hexEditor.%s=%d\n", name.c_str(), count);
108 }
109
110}
111
113{
114 hexEditors.clear();
115}
116
117void ImGuiDebugger::loadLine(std::string_view name, zstring_view value)
118{
119 static constexpr std::string_view prefix = "hexEditor.";
120
121 if (loadOnePersistent(name, value, *this, persistentElements)) {
122 // already handled
123 } else if (name.starts_with(prefix)) {
124 if (auto r = StringOp::stringTo<unsigned>(value)) {
125 auto debuggableName = std::string(name.substr(prefix.size()));
126 auto [b, e] = ranges::equal_range(hexEditors, debuggableName, {}, &DebuggableEditor::getDebuggableName);
127 auto index = std::distance(b, e); // expected to be 0, but be robust against imgui.ini changes
128 for (auto i : xrange(*r)) {
129 e = hexEditors.insert(e, std::make_unique<DebuggableEditor>(manager, debuggableName, index + i));
130 ++e;
131 }
132 }
133 }
134}
135
137{
138 auto createHexEditor = [&](const std::string& name) {
139 // prefer to reuse a previously closed editor
140 auto [b, e] = ranges::equal_range(hexEditors, name, {}, &DebuggableEditor::getDebuggableName);
141 for (auto it = b; it != e; ++it) {
142 if (!(*it)->open) {
143 (*it)->open = true;
144 return;
145 }
146 }
147 // or create a new one
148 auto index = std::distance(b, e);
149 auto it = hexEditors.insert(e, std::make_unique<DebuggableEditor>(manager, name, index));
150 (*it)->open = true;
151 };
152
153 im::Menu("Debugger", motherBoard != nullptr, [&]{
154 ImGui::MenuItem("Tool bar", nullptr, &showControl);
155 ImGui::MenuItem("Disassembly", nullptr, &showDisassembly);
156 ImGui::MenuItem("CPU registers", nullptr, &showRegisters);
157 ImGui::MenuItem("CPU flags", nullptr, &showFlags);
158 ImGui::MenuItem("Slots", nullptr, &showSlots);
159 ImGui::MenuItem("Stack", nullptr, &showStack);
160 auto it = ranges::lower_bound(hexEditors, "memory", {}, &DebuggableEditor::getDebuggableName);
161 bool memoryOpen = (it != hexEditors.end()) && (*it)->open;
162 if (ImGui::MenuItem("Memory", nullptr, &memoryOpen)) {
163 if (memoryOpen) {
164 createHexEditor("memory");
165 } else {
166 assert(it != hexEditors.end());
167 (*it)->open = false;
168 }
169 }
170 ImGui::Separator();
171 ImGui::MenuItem("Breakpoints", nullptr, &manager.breakPoints->show);
172 ImGui::MenuItem("Symbol manager", nullptr, &manager.symbols->show);
173 ImGui::MenuItem("Watch expression", nullptr, &manager.watchExpr->show);
174 ImGui::Separator();
175 ImGui::MenuItem("VDP bitmap viewer", nullptr, &manager.bitmap->showBitmapViewer);
176 ImGui::MenuItem("VDP tile viewer", nullptr, &manager.character->show);
177 ImGui::MenuItem("VDP sprite viewer", nullptr, &manager.sprite->show);
178 ImGui::MenuItem("VDP register viewer", nullptr, &manager.vdpRegs->show);
179 ImGui::MenuItem("Palette editor", nullptr, &manager.palette->window.open);
180 ImGui::Separator();
181 im::Menu("Add hex editor", [&]{
182 auto& debugger = motherBoard->getDebugger();
183 auto debuggables = to_vector<std::pair<std::string, Debuggable*>>(debugger.getDebuggables());
184 ranges::sort(debuggables, StringOp::caseless{}, [](const auto& p) { return p.first; }); // sort on name
185 for (const auto& [name, debuggable] : debuggables) {
186 if (ImGui::Selectable(strCat(name, " ...").c_str())) {
187 createHexEditor(name);
188 }
189 }
190 });
191 });
192}
193
195{
196 if (!motherBoard) return;
197
198 auto& regs = motherBoard->getCPU().getRegisters();
199 auto& cpuInterface = motherBoard->getCPUInterface();
200 auto& debugger = motherBoard->getDebugger();
201 auto time = motherBoard->getCurrentTime();
202 drawControl(cpuInterface);
203 drawDisassembly(regs, cpuInterface, debugger, time);
204 drawSlots(cpuInterface, debugger);
205 drawStack(regs, cpuInterface, time);
206 drawRegisters(regs);
207 drawFlags(regs);
208}
209
210void ImGuiDebugger::drawControl(MSXCPUInterface& cpuInterface)
211{
212 if (!showControl) return;
213 im::Window("Debugger tool bar", &showControl, [&]{
214 auto ButtonGlyph = [](const char* id, ImWchar c) {
215 const auto* font = ImGui::GetFont();
216 auto texId = font->ContainerAtlas->TexID;
217 const auto* g = font->FindGlyph(c);
218 bool result = ImGui::ImageButton(id, texId, {g->X1 - g->X0, g->Y1 - g->Y0}, {g->U0, g->V0}, {g->U1, g->V1});
219 simpleToolTip(id);
220 return result;
221 };
222
223 bool breaked = cpuInterface.isBreaked();
224 if (breaked) {
225 if (ButtonGlyph("run", DEBUGGER_ICON_RUN)) {
226 cpuInterface.doContinue();
227 }
228 } else {
229 if (ButtonGlyph("break", DEBUGGER_ICON_BREAK)) {
230 cpuInterface.doBreak();
231 }
232 }
233 ImGui::SameLine();
234 ImGui::SetCursorPosX(50.0f);
235
236 im::Disabled(!breaked, [&]{
237 if (ButtonGlyph("step-in", DEBUGGER_ICON_STEP_IN)) {
238 cpuInterface.doStep();
239 }
240 ImGui::SameLine();
241
242 if (ButtonGlyph("step-over", DEBUGGER_ICON_STEP_OVER)) {
243 manager.executeDelayed(TclObject("step_over"));
244 }
245 ImGui::SameLine();
246
247 if (ButtonGlyph("step-out", DEBUGGER_ICON_STEP_OUT)) {
248 manager.executeDelayed(TclObject("step_out"));
249 }
250 ImGui::SameLine();
251
252 if (ButtonGlyph("step-back", DEBUGGER_ICON_STEP_BACK)) {
253 manager.executeDelayed(TclObject("step_back"),
254 [&](const TclObject&) { syncDisassemblyWithPC = true; });
255 }
256 });
257 });
258}
259
261 int ps;
262 std::optional<int> ss;
263 std::optional<int> seg;
264};
265[[nodiscard]] static CurrentSlot getCurrentSlot(
266 MSXCPUInterface& cpuInterface, Debugger& debugger,
267 uint16_t addr, bool wantSs = true, bool wantSeg = true)
268{
269 CurrentSlot result;
270 int page = addr / 0x4000;
271 result.ps = cpuInterface.getPrimarySlot(page);
272
273 if (wantSs && cpuInterface.isExpanded(result.ps)) {
274 result.ss = cpuInterface.getSecondarySlot(page);
275 }
276 if (wantSeg) {
277 const auto* device = cpuInterface.getVisibleMSXDevice(page);
278 Debuggable* romBlocks = nullptr;
279 if (const auto* mapper = dynamic_cast<const MSXMemoryMapperBase*>(device)) {
280 result.seg = mapper->getSelectedSegment(narrow<uint8_t>(page));
281 } else if (!dynamic_cast<const RomPlain*>(device) &&
282 (romBlocks = debugger.findDebuggable(device->getName() + " romblocks"))) {
283 result.seg = romBlocks->read(addr);
284 }
285 }
286 return result;
287}
288
289[[nodiscard]] static TclObject toTclExpression(const CurrentSlot& slot)
290{
291 std::string result = strCat("[pc_in_slot ", slot.ps);
292 if (slot.ss) {
293 strAppend(result, ' ', *slot.ss);
294 } else {
295 if (slot.seg) strAppend(result, " X");
296 }
297 if (slot.seg) strAppend(result, ' ', *slot.seg);
298 strAppend(result, ']');
299 return TclObject(result);
300}
301
302[[nodiscard]] static bool addrInSlot(
303 const ParsedSlotCond& slot, MSXCPUInterface& cpuInterface, Debugger& debugger, uint16_t addr)
304{
305 if (!slot.hasPs) return true; // no ps specified -> always ok
306
307 auto current = getCurrentSlot(cpuInterface, debugger, addr, slot.hasSs, slot.hasSeg);
308 if (slot.ps != current.ps) return false;
309 if (slot.hasSs && current.ss && (slot.ss != current.ss)) return false;
310 if (slot.hasSeg && current.seg && (slot.seg != current.seg)) return false;
311 return true;
312}
313
314void ImGuiDebugger::drawDisassembly(CPURegs& regs, MSXCPUInterface& cpuInterface, Debugger& debugger, EmuTime::param time)
315{
316 if (!showDisassembly) return;
317 ImGui::SetNextWindowSize({340, 540}, ImGuiCond_FirstUseEver);
318 im::Window("Disassembly", &showDisassembly, [&]{
319 std::optional<BreakPoint> addBp;
320 std::optional<unsigned> removeBpId;
321
322 auto pc = regs.getPC();
323 if (followPC && !cpuInterface.isBreaked()) {
324 gotoTarget = pc;
325 }
326
327 auto widthOpcode = ImGui::CalcTextSize("12 34 56 78"sv).x;
328 int flags = ImGuiTableFlags_RowBg |
329 ImGuiTableFlags_BordersV |
330 ImGuiTableFlags_BordersOuterV |
331 ImGuiTableFlags_Resizable |
332 ImGuiTableFlags_Hideable |
333 ImGuiTableFlags_Reorderable |
334 ImGuiTableFlags_ScrollY |
335 ImGuiTableFlags_ScrollX;
336 im::Table("table", 4, flags, [&]{
337 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
338 ImGui::TableSetupColumn("bp", ImGuiTableColumnFlags_WidthFixed);
339 ImGui::TableSetupColumn("address", ImGuiTableColumnFlags_NoHide);
340 ImGui::TableSetupColumn("opcode", ImGuiTableColumnFlags_WidthFixed, widthOpcode);
341 ImGui::TableSetupColumn("mnemonic", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHide);
342 ImGui::TableHeadersRow();
343
344 const auto& breakPoints = cpuInterface.getBreakPoints();
345 auto bpEt = breakPoints.end();
346 auto textSize = ImGui::GetTextLineHeight();
347
348 std::string mnemonic;
349 std::string opcodesStr;
350 std::array<uint8_t, 4> opcodes;
351 ImGuiListClipper clipper; // only draw the actually visible rows
352 clipper.Begin(0x10000);
353 if (gotoTarget) {
354 clipper.IncludeItemsByIndex(*gotoTarget, *gotoTarget + 4);
355 }
356 std::optional<unsigned> nextGotoTarget;
357 while (clipper.Step()) {
358 auto bpIt = ranges::lower_bound(breakPoints, clipper.DisplayStart, {}, &BreakPoint::getAddress);
359 auto addr16 = instructionBoundary(cpuInterface, narrow<uint16_t>(clipper.DisplayStart), time);
360 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
361 unsigned addr = addr16;
362 ImGui::TableNextRow();
363 if (addr >= 0x10000) continue;
364 im::ID(narrow<int>(addr), [&]{
365 if (gotoTarget && addr >= *gotoTarget) {
366 gotoTarget = {};
367 ImGui::SetScrollHereY(0.25f);
368 }
369
370 bool rowAtPc = !syncDisassemblyWithPC && (addr == pc);
371 if (rowAtPc) {
372 ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, getColor(imColor::YELLOW_BG));
373 }
374 if (ImGui::TableNextColumn()) { // bp
375 while ((bpIt != bpEt) && (bpIt->getAddress() < addr)) ++bpIt;
376 bool hasBp = (bpIt != bpEt) && (bpIt->getAddress() == addr);
377 bool multiBp = hasBp && ((bpIt + 1) != bpEt) && ((bpIt + 1)->getAddress() == addr);
378 bool simpleBp = !multiBp;
379 if (hasBp) {
380 ParsedSlotCond bpSlot("pc_in_slot", bpIt->getCondition());
381 bool inSlot = addrInSlot(bpSlot, cpuInterface, debugger, addr16);
382 bool defaultBp = bpSlot.rest.empty() && (bpIt->getCommand() == "debug break");
383 simpleBp &= defaultBp;
384
385 auto [r,g,b] = simpleBp ? std::tuple(0xE0, 0x00, 0x00)
386 : std::tuple(0xE0, 0xE0, 0x00);
387 auto a = inSlot ? 0xFF : 0x80;
388 auto col = IM_COL32(r, g, b, a);
389
390 auto* drawList = ImGui::GetWindowDrawList();
391 gl::vec2 scrn = ImGui::GetCursorScreenPos();
392 auto center = scrn + gl::vec2(textSize * 0.5f);
393 drawList->AddCircleFilled(center, textSize * 0.4f, col);
394 }
395 if (ImGui::InvisibleButton("##bp-button", {-FLT_MIN, textSize})) {
396 if (hasBp) {
397 // only allow to remove 'simple' breakpoints,
398 // others can be edited via the breakpoint viewer
399 if (simpleBp) {
400 removeBpId = bpIt->getId(); // schedule removal
401 }
402 } else {
403 // schedule creation of new bp
404 auto slot = getCurrentSlot(cpuInterface, debugger, addr16);
405 addBp.emplace(addr, TclObject("debug break"), toTclExpression(slot), false);
406 }
407 }
408 }
409
410 mnemonic.clear();
411 std::optional<uint16_t> mnemonicAddr;
412 std::span<const Symbol* const> mnemonicLabels;
413 auto len = dasm(cpuInterface, addr16, opcodes, mnemonic, time,
414 [&](std::string& output, uint16_t a) {
415 mnemonicAddr = a;
416 mnemonicLabels = symbolManager.lookupValue(a);
417 if (!mnemonicLabels.empty()) {
418 strAppend(output, mnemonicLabels[cycleLabelsCounter % mnemonicLabels.size()]->name);
419 } else {
420 appendAddrAsHex(output, a);
421 }
422 });
423 assert(len >= 1);
424 if ((addr < pc) && (pc < (addr + len))) {
425 // pc is strictly inside current instruction,
426 // replace the just disassembled instruction with "db #..."
427 mnemonicAddr.reset();
428 mnemonicLabels = {};
429 len = pc - addr;
430 assert((1 <= len) && (len <= 3));
431 mnemonic = strCat("db ", join(
433 [&](unsigned i) { return strCat('#', hex_string<2>(opcodes[i])); }),
434 ','));
435 }
436
437 if (ImGui::TableNextColumn()) { // addr
438 // do the full-row-selectable stuff in a column that cannot be hidden
439 auto pos = ImGui::GetCursorPos();
440 ImGui::Selectable("##row", false,
441 ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
442 if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
443 ImGui::OpenPopup("disassembly-context");
444 }
445 auto addrStr = tmpStrCat(hex_string<4>(addr));
446 im::Popup("disassembly-context", [&]{
447 auto addrToolTip = [&](const std::string& str) {
448 simpleToolTip([&]{
449 if (auto a = symbolManager.parseSymbolOrValue(str)) {
450 return strCat("0x", hex_string<4>(*a));
451 }
452 return std::string{};
453 });
454 };
455
456 if (ImGui::MenuItem("Scroll to PC")) {
457 nextGotoTarget = pc;
458 }
459
460 im::Indent([&]{
461 ImGui::Checkbox("Follow PC while running", &followPC);
462 });
463
464 ImGui::AlignTextToFramePadding();
465 ImGui::TextUnformatted("Scroll to address:"sv);
466 ImGui::SameLine();
467 if (ImGui::InputText("##goto", &gotoAddr, ImGuiInputTextFlags_EnterReturnsTrue)) {
468 if (auto a = symbolManager.parseSymbolOrValue(gotoAddr)) {
469 nextGotoTarget = *a;
470 }
471 }
472 addrToolTip(gotoAddr);
473
474 ImGui::Separator();
475
476 auto runTo = strCat("Run to address 0x", addrStr);
477 if (ImGui::MenuItem(runTo.c_str())) {
478 manager.executeDelayed(makeTclList("run_to", addr));
479 }
480
481 ImGui::AlignTextToFramePadding();
482 ImGui::TextUnformatted("Run to address:"sv);
483 ImGui::SameLine();
484 if (ImGui::InputText("##run", &runToAddr, ImGuiInputTextFlags_EnterReturnsTrue)) {
485 if (auto a = symbolManager.parseSymbolOrValue(runToAddr)) {
486 manager.executeDelayed(makeTclList("run_to", *a));
487 }
488 }
489 addrToolTip(runToAddr);
490
491 ImGui::Separator();
492
493 auto setPc = strCat("Set PC to 0x", addrStr);
494 if (ImGui::MenuItem(setPc.c_str())) {
495 regs.setPC(addr16);
496 }
497 });
498
499 auto addrLabels = symbolManager.lookupValue(addr16);
500 std::string_view displayAddr = addrLabels.empty()
501 ? std::string_view(addrStr)
502 : std::string_view(addrLabels[cycleLabelsCounter % addrLabels.size()]->name);
503 ImGui::SetCursorPos(pos);
505 ImGui::TextUnformatted(displayAddr);
506 });
507 if (!addrLabels.empty()) {
508 simpleToolTip([&]{
509 std::string tip(addrStr);
510 if (addrLabels.size() > 1) {
511 strAppend(tip, "\nmultiple possibilities (click to cycle):\n",
512 join(view::transform(addrLabels, &Symbol::name), ' '));
513 }
514 return tip;
515 });
516 ImGui::SetCursorPos(pos);
517 if (ImGui::InvisibleButton("##addrButton", {-FLT_MIN, textSize})) {
518 ++cycleLabelsCounter;
519 }
520 }
521 }
522
523 if (ImGui::TableNextColumn()) { // opcode
524 opcodesStr.clear();
525 for (auto i : xrange(len)) {
526 strAppend(opcodesStr, hex_string<2>(opcodes[i]), ' ');
527 }
529 ImGui::TextUnformatted(opcodesStr.data(), opcodesStr.data() + 3 * len - 1);
530 });
531 }
532
533 if (ImGui::TableNextColumn()) { // mnemonic
534 auto pos = ImGui::GetCursorPos();
536 ImGui::TextUnformatted(mnemonic);
537 });
538 if (mnemonicAddr) {
539 ImGui::SetCursorPos(pos);
540 if (ImGui::InvisibleButton("##mnemonicButton", {-FLT_MIN, textSize})) {
541 if (!mnemonicLabels.empty()) {
542 ++cycleLabelsCounter;
543 }
544 }
545 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
546 nextGotoTarget = *mnemonicAddr;
547 }
548 if (!mnemonicLabels.empty()) {
549 simpleToolTip([&]{
550 auto tip = strCat('#', hex_string<4>(*mnemonicAddr));
551 if (mnemonicLabels.size() > 1) {
552 strAppend(tip, "\nmultiple possibilities (click to cycle):\n",
553 join(view::transform(mnemonicLabels, &Symbol::name), ' '));
554 }
555 return tip;
556 });
557 }
558 }
559 }
560 addr16 += len;
561 });
562 }
563 }
564 gotoTarget = nextGotoTarget;
565 if (syncDisassemblyWithPC) {
566 syncDisassemblyWithPC = false; // only once
567
568 auto itemHeight = ImGui::GetTextLineHeightWithSpacing();
569 auto winHeight = ImGui::GetWindowHeight();
570 auto lines = std::max(1, int(winHeight / itemHeight) - 1); // approx
571
572 auto topAddr = nInstructionsBefore(cpuInterface, pc, time, narrow<int>(lines / 4) + 1);
573
574 ImGui::SetScrollY(topAddr * itemHeight);
575 }
576 });
577 // only add/remove bp's after drawing (can't change list of bp's while iterating over it)
578 if (addBp) {
579 cpuInterface.insertBreakPoint(std::move(*addBp));
580 }
581 if (removeBpId) {
582 cpuInterface.removeBreakPoint(*removeBpId);
583 }
584 });
585}
586
587void ImGuiDebugger::drawSlots(MSXCPUInterface& cpuInterface, Debugger& debugger)
588{
589 if (!showSlots) return;
590 im::Window("Slots", &showSlots, [&]{
591 int flags = ImGuiTableFlags_BordersInnerV |
592 ImGuiTableFlags_Resizable |
593 ImGuiTableFlags_Reorderable |
594 ImGuiTableFlags_Hideable |
595 ImGuiTableFlags_ContextMenuInBody;
596 im::Table("table", 4, flags, [&]{
597 ImGui::TableSetupColumn("Page");
598 ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_DefaultHide);
599 ImGui::TableSetupColumn("Slot");
600 ImGui::TableSetupColumn("Segment");
601 ImGui::TableHeadersRow();
602
603 for (auto page : xrange(uint8_t(4))) {
604 auto addr = 0x4000 * page;
605 if (ImGui::TableNextColumn()) { // page
606 ImGui::StrCat(page);
607 }
608 if (ImGui::TableNextColumn()) { // address
609 ImGui::StrCat("0x", hex_string<4>(addr));
610 }
611 if (ImGui::TableNextColumn()) { // slot
612 int ps = cpuInterface.getPrimarySlot(page);
613 if (cpuInterface.isExpanded(ps)) {
614 int ss = cpuInterface.getSecondarySlot(page);
615 ImGui::StrCat(ps, '-', ss);
616 } else {
617 ImGui::StrCat(' ', ps);
618 }
619 }
620 if (ImGui::TableNextColumn()) { // segment
621 auto* device = cpuInterface.getVisibleMSXDevice(page);
622 Debuggable* romBlocks = nullptr;
623 if (auto* mapper = dynamic_cast<MSXMemoryMapperBase*>(device)) {
624 ImGui::StrCat(mapper->getSelectedSegment(page));
625 } else if (!dynamic_cast<RomPlain*>(device) &&
626 (romBlocks = debugger.findDebuggable(device->getName() + " romblocks"))) {
627 std::array<uint8_t, 4> segments;
628 for (auto sub : xrange(4)) {
629 segments[sub] = romBlocks->read(addr + 0x1000 * sub);
630 }
631 if ((segments[0] == segments[1]) && (segments[2] == segments[3])) {
632 if (segments[0] == segments[2]) { // 16kB
633 ImGui::StrCat('R', segments[0]);
634 } else { // 8kB
635 ImGui::StrCat('R', segments[0], '/', segments[2]);
636 }
637 } else { // 4kB
638 ImGui::StrCat('R', segments[0], '/', segments[1], '/', segments[2], '/', segments[3]);
639 }
640 } else {
642 }
643 }
644 }
645 });
646 });
647}
648
649void ImGuiDebugger::drawStack(CPURegs& regs, MSXCPUInterface& cpuInterface, EmuTime::param time)
650{
651 if (!showStack) return;
652
653 auto line = ImGui::GetTextLineHeightWithSpacing();
654 ImGui::SetNextWindowSize(ImVec2(0.0f, 12 * line), ImGuiCond_FirstUseEver);
655 im::Window("stack", &showStack, [&]{
656 auto sp = regs.getSP();
657
658 int flags = ImGuiTableFlags_ScrollY |
659 ImGuiTableFlags_BordersInnerV |
660 ImGuiTableFlags_Resizable |
661 ImGuiTableFlags_Reorderable |
662 ImGuiTableFlags_Hideable |
663 ImGuiTableFlags_ContextMenuInBody;
664 im::Table("table", 3, flags, [&]{
665 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
666 ImGui::TableSetupColumn("Address");
667 ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_DefaultHide);
668 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide);
669 ImGui::TableHeadersRow();
670
672 im::ListClipper(std::min(128, (0x10000 - sp) / 2), [&](int row) {
673 auto offset = 2 * row;
674 auto addr = sp + offset;
675 if (ImGui::TableNextColumn()) { // address
676 ImGui::StrCat(hex_string<4>(addr));
677 }
678 if (ImGui::TableNextColumn()) { // offset
679 ImGui::Text("SP+%X", offset);
680 }
681 if (ImGui::TableNextColumn()) { // value
682 auto l = cpuInterface.peekMem(narrow_cast<uint16_t>(addr + 0), time);
683 auto h = cpuInterface.peekMem(narrow_cast<uint16_t>(addr + 1), time);
684 auto value = narrow<uint16_t>(256 * h + l);
685 ImGui::StrCat(hex_string<4>(value));
686 }
687 });
688 });
689 });
690}
691
692void ImGuiDebugger::drawRegisters(CPURegs& regs)
693{
694 if (!showRegisters) return;
695 im::Window("CPU registers", &showRegisters, [&]{
697
698 const auto& style = ImGui::GetStyle();
699 auto padding = 2 * style.FramePadding.x;
700 auto width16 = ImGui::CalcTextSize("FFFF"sv).x + padding;
701 auto edit16 = [&](std::string_view label, std::string_view high, std::string_view low, auto getter, auto setter) {
702 uint16_t value = getter();
703 im::Group([&]{
704 ImGui::AlignTextToFramePadding();
706 ImGui::SameLine();
707 ImGui::SetNextItemWidth(width16);
708 if (ImGui::InputScalar(tmpStrCat("##", label).c_str(), ImGuiDataType_U16, &value, nullptr, nullptr, "%04X")) {
709 setter(value);
710 }
711 });
712 simpleToolTip([&]{
713 return strCat(
714 "Bin: ", bin_string<4>(value >> 12), ' ', bin_string<4>(value >> 8), ' ',
715 bin_string<4>(value >> 4), ' ', bin_string<4>(value >> 0), "\n"
716 "Dec: ", dec_string<5>(value), '\n',
717 high, ": ", dec_string<3>(value >> 8), " ", low, ": ", dec_string<3>(value & 0xff));
718 });
719 };
720 auto edit8 = [&](std::string_view label, auto getter, auto setter) {
721 uint8_t value = getter();
722 im::Group([&]{
723 ImGui::AlignTextToFramePadding();
725 ImGui::SameLine();
726 ImGui::SetNextItemWidth(width16);
727 if (ImGui::InputScalar(tmpStrCat("##", label).c_str(), ImGuiDataType_U8, &value, nullptr, nullptr, "%02X")) {
728 setter(value);
729 }
730 });
731 simpleToolTip([&]{
732 return strCat(
733 "Bin: ", bin_string<4>(value >> 4), ' ', bin_string<4>(value >> 0), "\n"
734 "Dec: ", dec_string<3>(value), '\n');
735 });
736 };
737
738 edit16("AF", "A", "F", [&]{ return regs.getAF(); }, [&](uint16_t value) { regs.setAF(value); });
739 ImGui::SameLine(0.0f, 20.0f);
740 edit16("AF'", "A'", "F'", [&]{ return regs.getAF2(); }, [&](uint16_t value) { regs.setAF2(value); });
741
742 edit16("BC", "B", "C", [&]{ return regs.getBC(); }, [&](uint16_t value) { regs.setBC(value); });
743 ImGui::SameLine(0.0f, 20.0f);
744 edit16("BC'", "B'", "C'", [&]{ return regs.getBC2(); }, [&](uint16_t value) { regs.setBC2(value); });
745
746 edit16("DE", "D", "E", [&]{ return regs.getDE(); }, [&](uint16_t value) { regs.setDE(value); });
747 ImGui::SameLine(0.0f, 20.0f);
748 edit16("DE'", "D'", "E'", [&]{ return regs.getDE2(); }, [&](uint16_t value) { regs.setDE2(value); });
749
750 edit16("HL", "H", "L", [&]{ return regs.getHL(); }, [&](uint16_t value) { regs.setHL(value); });
751 ImGui::SameLine(0.0f, 20.0f);
752 edit16("HL'", "H'", "L'", [&]{ return regs.getHL2(); }, [&](uint16_t value) { regs.setHL2(value); });
753
754 edit16("IX", "IXh", "IXl", [&]{ return regs.getIX(); }, [&](uint16_t value) { regs.setIX(value); });
755 ImGui::SameLine(0.0f, 20.0f);
756 edit16("IY ", "IYh", "IYl", [&]{ return regs.getIY(); }, [&](uint16_t value) { regs.setIY(value); });
757
758 edit16("PC", "PCh", "PCl", [&]{ return regs.getPC(); }, [&](uint16_t value) { regs.setPC(value); });
759 ImGui::SameLine(0.0f, 20.0f);
760 edit16("SP ", "SPh", "SPl", [&]{ return regs.getSP(); }, [&](uint16_t value) { regs.setSP(value); });
761
762 edit8("I ", [&]{ return regs.getI(); }, [&](uint8_t value) { regs.setI(value); });
763 ImGui::SameLine(0.0f, 20.0f);
764 edit8("R ", [&]{ return regs.getR(); }, [&](uint8_t value) { regs.setR(value); });
765
766 ImGui::AlignTextToFramePadding();
768 ImGui::SameLine();
769 ImGui::SetNextItemWidth(width16);
770 uint8_t im = regs.getIM();
771 if (ImGui::InputScalar("##IM", ImGuiDataType_U8, &im, nullptr, nullptr, "%d")) {
772 if (im <= 2) regs.setIM(im);
773 }
774
775 ImGui::SameLine(0.0f, 20.0f);
776 ImGui::AlignTextToFramePadding();
777 bool ei = regs.getIFF1();
778 if (ImGui::Selectable(ei ? "EI" : "DI", false, ImGuiSelectableFlags_AllowDoubleClick)) {
779 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
780 regs.setIFF1(!ei);
781 regs.setIFF2(!ei);
782 }
783 }
784 simpleToolTip("double-click to toggle");
785 });
786}
787
788void ImGuiDebugger::drawFlags(CPURegs& regs)
789{
790 if (!showFlags) return;
791 im::Window("CPU flags", &showFlags, [&]{
792 auto [sizeH1_, sizeH2_, sizeV_] = [&]{
794 return std::tuple{
795 ImGui::CalcTextSize("NC"sv),
796 ImGui::CalcTextSize("X:0"sv),
797 ImGui::CalcTextSize("C 0 (NC)"sv)
798 };
799 }();
800 // clang workaround
801 auto sizeH1 = sizeH1_; auto sizeH2 = sizeH2_; auto sizeV = sizeV_;
802
803 auto f = regs.getF();
804
805 auto draw = [&](const char* name, uint8_t bit, const char* val0 = nullptr, const char* val1 = nullptr) {
806 std::string s;
807 ImVec2 sz;
808 if (flagsLayout == 0) {
809 // horizontal
810 if (val0) {
811 s = (f & bit) ? val1 : val0;
812 sz = sizeH1;
813 } else {
814 s = strCat(name, ':', (f & bit) ? '1' : '0');
815 sz = sizeH2;
816 }
817 } else {
818 // vertical
819 s = strCat(name, ' ', (f & bit) ? '1' : '0');
820 if (val0) {
821 strAppend(s, " (", (f & bit) ? val1 : val0, ')');
822 }
823 sz = sizeV;
824 }
826 if (ImGui::Selectable(s.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, sz)) {
827 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
828 regs.setF(f ^ bit);
829 }
830 }
831 });
832 simpleToolTip("double-click to toggle, right-click to configure");
833 if (flagsLayout == 0) {
834 // horizontal
835 ImGui::SameLine(0.0f, sizeH1.x);
836 }
837 };
838
839 draw("S", 0x80, " P", " M");
840 draw("Z", 0x40, "NZ", " Z");
841 if (showXYFlags) draw("Y", 0x20);
842 draw("H", 0x10);
843 if (showXYFlags) draw("X", 0x08);
844 draw("P", 0x04, "PO", "PE");
845 draw("N", 0x02);
846 draw("C", 0x01, "NC", " C");
847
849 ImGui::TextUnformatted("Layout"sv);
850 ImGui::RadioButton("horizontal", &flagsLayout, 0);
851 ImGui::RadioButton("vertical", &flagsLayout, 1);
852 ImGui::Separator();
853 ImGui::Checkbox("show undocumented", &showXYFlags);
854 });
855 });
856}
857
858} // namespace openmsx
uintptr_t id
int g
word getAddress() const
Definition BreakPoint.hh:21
std::string_view getDebuggableName() const
Debuggable * findDebuggable(std::string_view name)
Definition Debugger.cc:61
void loadStart() override
void showMenu(MSXMotherBoard *motherBoard) override
ImGuiDebugger(ImGuiManager &manager)
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
void save(ImGuiTextBuffer &buf) override
std::unique_ptr< ImGuiBreakPoints > breakPoints
std::unique_ptr< ImGuiVdpRegs > vdpRegs
std::unique_ptr< ImGuiWatchExpr > watchExpr
std::unique_ptr< ImGuiPalette > palette
std::unique_ptr< ImGuiSpriteViewer > sprite
std::unique_ptr< ImGuiBitmapViewer > bitmap
std::unique_ptr< ImGuiCharacter > character
void executeDelayed(std::function< void()> action)
std::unique_ptr< ImGuiSymbols > symbols
ImGuiManager & manager
Definition ImGuiPart.hh:30
auto getPrimarySlot(int page) const
MSXDevice * getVisibleMSXDevice(int page)
bool isExpanded(int ps) const
auto getSecondarySlot(int page) const
CPURegs & getRegisters()
Definition MSXCPU.cc:339
MSXCPUInterface & getCPUInterface()
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
std::span< Symbol const *const > lookupValue(uint16_t value)
std::optional< uint16_t > parseSymbolOrValue(std::string_view s) const
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
Definition join.hh:60
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:43
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
vecN< 2, float > vec2
Definition gl_vec.hh:150
Definition ImGuiCpp.hh:60
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:473
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void ID(const char *str_id, std::invocable<> auto next)
Definition ImGuiCpp.hh:258
void PopupContextWindow(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:456
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
Definition ImGuiCpp.hh:377
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:524
void Group(std::invocable<> auto next)
Definition ImGuiCpp.hh:250
void Font(ImFont *font, std::invocable<> auto next)
Definition ImGuiCpp.hh:125
void Indent(float indent_w, std::invocable<> auto next)
Definition ImGuiCpp.hh:238
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:409
void ListClipper(size_t count, std::invocable< int > auto next)
Definition ImGuiCpp.hh:556
This file implemented 3 utility functions:
Definition Autofire.cc:9
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:66
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
unsigned dasm(const MSXCPUInterface &interface, uint16_t pc, std::span< uint8_t, 4 > buf, std::string &dest, EmuTime::param time, std::function< void(std::string &, uint16_t)> appendAddr)
Disassemble.
Definition Dasm.cc:24
ImU32 getColor(imColor col)
uint16_t instructionBoundary(const MSXCPUInterface &interface, uint16_t addr, EmuTime::param time)
This is only an heuristic to display instructions in a debugger disassembly view.
Definition Dasm.cc:173
uint16_t nInstructionsBefore(const MSXCPUInterface &interface, uint16_t addr, EmuTime::param time, int n)
Get the start address of the 'n'th instruction before the instruction containing the byte at the give...
Definition Dasm.cc:180
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:289
auto equal_range(ForwardRange &&range, const T &value, Compare comp={})
Definition ranges.hh:133
constexpr void sort(RandomAccessRange &&range)
Definition ranges.hh:49
auto lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
Definition ranges.hh:115
STL namespace.
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition view.hh:520
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
std::optional< int > seg
std::optional< int > ss
std::string name
constexpr auto xrange(T e)
Definition xrange.hh:132