OXYGENHUB
◆ main openmsx / src / imgui / ImGuiDebugger.cc
27.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "ImGuiDebugger.hh"
2
3
#include "DebuggableEditor.hh"
4
#include "ImGuiBitmapViewer.hh"
5
#include "ImGuiBreakPoints.hh"
6
#include "ImGuiCharacter.hh"
7
#include "ImGuiCpp.hh"
8
#include "ImGuiDisassembly.hh"
9
#include "ImGuiManager.hh"
10
#include "ImGuiPalette.hh"
11
#include "ImGuiRasterViewer.hh"
12
#include "ImGuiSpriteViewer.hh"
13
#include "ImGuiTraceViewer.hh"
14
#include "ImGuiSymbols.hh"
15
#include "ImGuiUtils.hh"
16
#include "ImGuiVdpRegs.hh"
17
#include "ImGuiWatchExpr.hh"
18
19
#include "Debuggable.hh"
20
#include "Debugger.hh"
21
#include "MSXCPU.hh"
22
#include "MSXCPUInterface.hh"
23
#include "MSXMemoryMapperBase.hh"
24
#include "MSXMotherBoard.hh"
25
#include "MSXRom.hh"
26
#include "RomBlockDebuggable.hh"
27
#include "RomInfo.hh"
28
29
#include "StringOp.hh"
30
#include "narrow.hh"
31
#include "stl.hh"
32
#include "strCat.hh"
33
34
#include <imgui.h>
35
#include <imgui_stdlib.h>
36
37
#include <algorithm>
38
39
using namespace std::literals;
40
41
namespace openmsx {
42
43
static constexpr std::string_view BREAKPOINT_DIR = "breakpoints";
44
45
ImGuiDebugger::ImGuiDebugger(ImGuiManager& manager_)
46
	: ImGuiPart(manager_)
47
	, saveBreakpoints("breakpoint", ".breakpoints", BREAKPOINT_DIR)
48
	, loadBreakpoints("breakpoint", ".breakpoints", BREAKPOINT_DIR)
49
{
50
	saveBreakpoints.drawAction = [&]{
51
		saveBreakpoints.drawTable();
52
		ImGui::TextUnformatted("Enter name:"sv);
53
		ImGui::InputText("##save-layout-name", &breakpointFile);
54
		ImGui::SameLine();
55
		im::Disabled(breakpointFile.empty(), [&]{
56
			if (ImGui::Button("Save as")) {
57
				loadSaveBreakpoints(SAVE);
58
				ImGui::CloseCurrentPopup();
59
			}
60
		});
61
	};
62
	saveBreakpoints.singleClickAction = [&](const FileListWidget::Entry& entry) {
63
		breakpointFile = entry.getDefaultDisplayName();
64
	};
65
66
	loadBreakpoints.singleClickAction = [&](const FileListWidget::Entry& entry) {
67
		breakpointFile = entry.getDefaultDisplayName();
68
		loadSaveBreakpoints(LOAD);
69
		ImGui::CloseCurrentPopup();
70
	};
71
}
72
73
ImGuiDebugger::~ImGuiDebugger() = default;
74
75
// TODO quick and dirty, doesn't look right yet
76
// Created from a .ttf file via:
77
//    binary_to_compressed_c openmsx-debugger-icons.ttf myIcons > myfont.cpp
78
//    https://github.com/ocornut/imgui/blob/master/misc/fonts/binary_to_compressed_c.cpp
79
static const char icons_compressed_base85[1815+1] =
80
    "7])#######;9u/('/###W),##.f1$#Q6>##%[n42)[KU%G5)=-<NE/1aNV=BZrPSb]->>#ICi9.o`(*HGsO2(b6O3L+lQS%,5LsCC,H3AnAMeNA*&#Gb';9Cs3BMNvSN`sr1dD4Eo3R/"
81
    "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%"
82
    "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"
83
    "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*<#"
84
    ")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$"
85
    "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"
86
    "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-"
87
    "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$_]"
88
    "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*$"
89
    "/)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;-##"
90
    "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"
91
    "]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/#"
92
    "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#";
93
94
static constexpr ImWchar DEBUGGER_ICON_MIN       = 0xead1;
95
static constexpr ImWchar DEBUGGER_ICON_MAX       = 0xeb8f;
96
static constexpr ImWchar DEBUGGER_ICON_RUN       = 0xead3;
97
static constexpr ImWchar DEBUGGER_ICON_BREAK     = 0xead1;
98
static constexpr ImWchar DEBUGGER_ICON_STEP_IN   = 0xead4;
99
static constexpr ImWchar DEBUGGER_ICON_STEP_OUT  = 0xead5;
100
static constexpr ImWchar DEBUGGER_ICON_STEP_OVER = 0xead6;
101
static constexpr ImWchar DEBUGGER_ICON_STEP_BACK = 0xeb8f;
102
103
void ImGuiDebugger::loadIcons()
104
{
105
	auto& io = ImGui::GetIO();
106
	ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true;
107
	static constexpr std::array<ImWchar, 3> ranges = {DEBUGGER_ICON_MIN, DEBUGGER_ICON_MAX, 0};
108
	io.Fonts->AddFontFromMemoryCompressedBase85TTF(icons_compressed_base85, 20.0f, &icons_config, ranges.data());
109
}
110
111
void ImGuiDebugger::signalBreak()
112
{
113
	for (auto& d : disassemblyViewers) {
114
		d->signalBreak();
115
	}
116
}
117
118
void ImGuiDebugger::signalContinue()
119
{
120
	auto* motherBoard = manager.getReactor().getMotherBoard();
121
	if (!motherBoard) return;
122
123
	auto& cpuInterface = motherBoard->getCPUInterface();
124
	auto& debugger = motherBoard->getDebugger();
125
	slotSnapshot = getSlotInfo(cpuInterface, debugger);
126
127
	cpuRegsSnapshot = motherBoard->getCPU().getRegisters();
128
129
	for (auto& h : hexEditors) {
130
		h->makeSnapshot(*motherBoard);
131
	}
132
133
	showChangesFrameCounter = 10;
134
}
135
136
void ImGuiDebugger::signalQuit()
137
{
138
	if (reloadBreakpoints) loadSaveBreakpoints(SAVE);
139
}
140
141
template<typename T>
142
static void openOrCreate(ImGuiManager& manager, std::vector<std::unique_ptr<T>>& viewers)
143
{
144
	// prefer to reuse a previously closed viewer
145
	if (auto it = std::ranges::find(viewers, false, &T::show); it != viewers.end()) {
146
		(*it)->show = true;
147
		return;
148
	}
149
	// or create a new one
150
	viewers.push_back(std::make_unique<T>(manager, viewers.size()));
151
}
152
153
void ImGuiDebugger::save(ImGuiTextBuffer& buf)
154
{
155
	savePersistent(buf, *this, persistentElements);
156
157
	buf.appendf("disassemblyViewers=%d\n", narrow<int>(disassemblyViewers.size()));
158
	buf.appendf("bitmapViewers=%d\n", narrow<int>(bitmapViewers.size()));
159
	buf.appendf("tileViewers=%d\n", narrow<int>(tileViewers.size()));
160
	buf.appendf("spriteViewers=%d\n", narrow<int>(spriteViewers.size()));
161
162
	// TODO in the future use c++23 std::chunk_by
163
	auto it = hexEditors.begin();
164
	auto et = hexEditors.end();
165
	while (it != et) {
166
		int count = 0;
167
		auto name = std::string((*it)->getDebuggableName());
168
		do {
169
			++it;
170
			++count;
171
		} while (it != et && (*it)->getDebuggableName() == name);
172
		buf.appendf("hexEditor.%s=%d\n", name.c_str(), count);
173
	}
174
}
175
176
void ImGuiDebugger::loadStart()
177
{
178
	disassemblyViewers.clear();
179
	bitmapViewers.clear();
180
	tileViewers.clear();
181
	spriteViewers.clear();
182
	hexEditors.clear();
183
}
184
185
void ImGuiDebugger::loadEnd()
186
{
187
	if (reloadBreakpoints) loadSaveBreakpoints(LOAD);
188
}
189
190
void ImGuiDebugger::loadLine(std::string_view name, zstring_view value)
191
{
192
	static constexpr std::string_view hexEditorPrefix = "hexEditor.";
193
194
	if (loadOnePersistent(name, value, *this, persistentElements)) {
195
		// already handled
196
	} else if (name == "disassemblyViewers") {
197
		if (auto r = StringOp::stringTo<unsigned>(value)) {
198
			repeat(*r, [&] { openOrCreate(manager, disassemblyViewers); });
199
		}
200
	} else if (name == "bitmapViewers") {
201
		if (auto r = StringOp::stringTo<unsigned>(value)) {
202
			repeat(*r, [&] { openOrCreate(manager, bitmapViewers); });
203
		}
204
	} else if (name == "tileViewers") {
205
		if (auto r = StringOp::stringTo<unsigned>(value)) {
206
			repeat(*r, [&] { openOrCreate(manager, tileViewers); });
207
		}
208
	} else if (name == "spriteViewers") {
209
		if (auto r = StringOp::stringTo<unsigned>(value)) {
210
			repeat(*r, [&] { openOrCreate(manager, spriteViewers); });
211
		}
212
	} else if (name.starts_with(hexEditorPrefix)) {
213
		if (auto r = StringOp::stringTo<unsigned>(value)) {
214
			auto debuggableName = std::string(name.substr(hexEditorPrefix.size()));
215
			auto [b, e] = std::ranges::equal_range(hexEditors, debuggableName, {}, &DebuggableEditor::getDebuggableName);
216
			auto index = std::distance(b, e); // expected to be 0, but be robust against imgui.ini changes
217
			for (auto i : xrange(*r)) {
218
				e = hexEditors.insert(e, std::make_unique<DebuggableEditor>(manager, debuggableName, index + i));
219
				++e;
220
			}
221
		}
222
	}
223
}
224
225
void ImGuiDebugger::showMenu(MSXMotherBoard* motherBoard)
226
{
227
	auto createHexEditor = [&](const std::string& name) {
228
		// prefer to reuse a previously closed editor
229
		auto [b, e] = std::ranges::equal_range(hexEditors, name, {}, &DebuggableEditor::getDebuggableName);
230
		for (auto it = b; it != e; ++it) {
231
			if (!(*it)->open) {
232
				(*it)->open = true;
233
				return;
234
			}
235
		}
236
		// or create a new one
237
		auto index = std::distance(b, e);
238
		auto it = hexEditors.insert(e, std::make_unique<DebuggableEditor>(manager, name, index));
239
		(*it)->open = true;
240
	};
241
242
	im::Menu("Debugger", motherBoard != nullptr, [&]{
243
		ImGui::MenuItem("Tool bar", nullptr, &showControl);
244
		if (ImGui::MenuItem("Disassembly")) {
245
			openOrCreate(manager, disassemblyViewers);
246
		}
247
		ImGui::MenuItem("CPU registers", nullptr, &showRegisters);
248
		ImGui::MenuItem("CPU flags", nullptr, &showFlags);
249
		ImGui::MenuItem("Slots", nullptr, &showSlots);
250
		ImGui::MenuItem("Stack", nullptr, &showStack);
251
		auto it = std::ranges::lower_bound(hexEditors, "memory", {}, &DebuggableEditor::getDebuggableName);
252
		bool memoryOpen = (it != hexEditors.end()) && (*it)->open;
253
		if (ImGui::MenuItem("Memory", nullptr, &memoryOpen)) {
254
			if (memoryOpen) {
255
				createHexEditor("memory");
256
			} else {
257
				assert(it != hexEditors.end());
258
				(*it)->open = false;
259
			}
260
		}
261
		ImGui::Separator();
262
		im::Menu("Breakpoints", [&]{
263
			ImGui::MenuItem("Editor", nullptr, &manager.breakPoints->show);
264
			saveBreakpoints.menu("Save to file");
265
			loadBreakpoints.menu("Load from file");
266
			ImGui::Separator();
267
			ImGui::MenuItem("Reload on startup", nullptr, &reloadBreakpoints);
268
		});
269
		ImGui::MenuItem("Symbol manager", nullptr, &manager.symbols->show);
270
		ImGui::MenuItem("Watch expression", nullptr, &manager.watchExpr->show);
271
		ImGui::MenuItem("Probe/Trace viewer", nullptr, &manager.traceViewer->show);
272
		ImGui::Separator();
273
		if (ImGui::MenuItem("VDP bitmap viewer")) {
274
			openOrCreate(manager, bitmapViewers);
275
		}
276
		if (ImGui::MenuItem("VDP tile viewer")) {
277
			openOrCreate(manager, tileViewers);
278
		}
279
		if (ImGui::MenuItem("VDP sprite viewer")) {
280
			openOrCreate(manager, spriteViewers);
281
		}
282
		ImGui::MenuItem("Raster beam viewer", nullptr, &manager.rasterViewer->show);
283
		ImGui::MenuItem("VDP register viewer", nullptr, &manager.vdpRegs->show);
284
		ImGui::MenuItem("Palette editor", nullptr, &manager.palette->window.open);
285
		ImGui::Separator();
286
		im::Menu("Add hex editor", [&]{
287
			const auto& debugger = motherBoard->getDebugger();
288
			auto debuggables = to_vector<std::pair<std::string, Debuggable*>>(debugger.getDebuggables());
289
			std::ranges::sort(debuggables, StringOp::caseless{}, [](const auto& p) { return p.first; }); // sort on name
290
			for (const auto& [name, debuggable] : debuggables) {
291
				if (ImGui::Selectable(strCat(name).c_str())) {
292
					createHexEditor(name);
293
				}
294
			}
295
		});
296
	});
297
}
298
299
void ImGuiDebugger::loadSaveBreakpoints(LoadSave loadSave)
300
{
301
	if (breakpointFile.empty()) return;
302
	try {
303
		manager.execute(makeTclList(loadSave == LOAD ? "load_breakpoints"sv : "save_breakpoints"sv,
304
		                            breakpointFile));
305
	} catch (MSXException& e) {
306
		manager.getCliComm().printWarning(e.getMessage());
307
	}
308
}
309
310
311
void ImGuiDebugger::setGotoTarget(uint16_t target)
312
{
313
	if (disassemblyViewers.empty()) {
314
		openOrCreate(manager, disassemblyViewers);
315
	}
316
	disassemblyViewers.front()->setGotoTarget(target);
317
}
318
319
void ImGuiDebugger::paint(MSXMotherBoard* motherBoard)
320
{
321
	if (!motherBoard) return;
322
323
	auto& regs = motherBoard->getCPU().getRegisters();
324
	auto& cpuInterface = motherBoard->getCPUInterface();
325
	auto& debugger = motherBoard->getDebugger();
326
	auto time = motherBoard->getCurrentTime();
327
	drawControl(cpuInterface, *motherBoard);
328
	drawSlots(cpuInterface, debugger);
329
	drawStack(regs, cpuInterface, time);
330
	drawRegisters(regs);
331
	drawFlags(regs);
332
333
	showChangesFrameCounter = std::max(0, showChangesFrameCounter - 1);
334
}
335
336
void ImGuiDebugger::actionBreakContinue(MSXCPUInterface& cpuInterface)
337
{
338
	if (MSXCPUInterface::isBreaked()) {
339
		cpuInterface.doContinue();
340
	} else {
341
		cpuInterface.doBreak();
342
	}
343
}
344
void ImGuiDebugger::actionStepIn(MSXCPUInterface& cpuInterface)
345
{
346
	cpuInterface.doStep();
347
}
348
void ImGuiDebugger::actionStepOver()
349
{
350
	manager.executeDelayed(TclObject("step_over"));
351
}
352
void ImGuiDebugger::actionStepOut()
353
{
354
	manager.executeDelayed(TclObject("step_out"));
355
}
356
void ImGuiDebugger::actionStepBack()
357
{
358
	signalContinue(); // make a snapshot
359
	manager.executeDelayed(TclObject("step_back"), [&](const TclObject&) {
360
		for (auto& d : disassemblyViewers) d->syncWithPC();
361
	});
362
}
363
364
void ImGuiDebugger::checkShortcuts(MSXCPUInterface& cpuInterface, MSXMotherBoard& motherBoard,
365
                                   ImGuiDisassembly* disassembly)
366
{
367
	using enum Shortcuts::ID;
368
	const auto& shortcuts = manager.getShortcuts();
369
370
	if (shortcuts.checkShortcut(DEBUGGER_BREAK_CONTINUE)) {
371
		actionBreakContinue(cpuInterface);
372
	} else if (shortcuts.checkShortcut(DEBUGGER_STEP_IN)) {
373
		actionStepIn(cpuInterface);
374
	} else if (shortcuts.checkShortcut(DEBUGGER_STEP_OVER)) {
375
		actionStepOver();
376
	} else if (shortcuts.checkShortcut(DEBUGGER_STEP_OUT)) {
377
		actionStepOut();
378
	} else if (shortcuts.checkShortcut(DEBUGGER_STEP_BACK)) {
379
		actionStepBack();
380
	} else if (shortcuts.checkShortcut(DISASM_TOGGLE_BREAKPOINT)) {
381
		if (!disassembly && !disassemblyViewers.empty()) {
382
			// Just pick one, doesn't matter which one. Because disassembly view
383
			//  doesn't have focus, they all behave the same (=toggle PC).
384
			disassembly = disassemblyViewers.front().get();
385
		}
386
		if (disassembly) disassembly->actionToggleBp(motherBoard);
387
	}
388
}
389
390
void ImGuiDebugger::drawControl(MSXCPUInterface& cpuInterface, MSXMotherBoard& motherBoard)
391
{
392
	if (!showControl) return;
393
	im::Window("Debugger tool bar", &showControl, [&]{
394
		checkShortcuts(cpuInterface, motherBoard, nullptr);
395
396
		gl::vec2 maxIconSize;
397
		auto* font = ImGui::GetFont()->GetFontBaked(ImGui::GetFontSize());
398
		for (auto c : {
399
			DEBUGGER_ICON_RUN, DEBUGGER_ICON_BREAK,
400
			DEBUGGER_ICON_STEP_IN, DEBUGGER_ICON_STEP_OVER,
401
			DEBUGGER_ICON_STEP_OUT, DEBUGGER_ICON_STEP_BACK,
402
		}) {
403
			const auto* g = font->FindGlyph(c);
404
			maxIconSize = max(maxIconSize, gl::vec2{g->X1 - g->X0, g->Y1 - g->Y0});
405
		}
406
407
		auto ButtonGlyph = [&](std::string_view id, ImWchar glyph, Shortcuts::ID sid) {
408
			bool result = ButtonWithCenteredGlyph(glyph, maxIconSize);
409
			simpleToolTip([&] -> std::string {
410
				const auto& shortcuts = manager.getShortcuts();
411
				const auto& shortcut = shortcuts.getShortcut(sid);
412
				if (shortcut.keyChord == ImGuiKey_None) return std::string(id);
413
				return strCat(id, " (", getKeyChordName(shortcut.keyChord), ')');
414
			});
415
			return result;
416
		};
417
418
		bool breaked = MSXCPUInterface::isBreaked();
419
		using enum Shortcuts::ID;
420
		if (auto breakContinueIcon = breaked ? DEBUGGER_ICON_RUN : DEBUGGER_ICON_BREAK;
421
		    ButtonGlyph("run", breakContinueIcon, DEBUGGER_BREAK_CONTINUE)) {
422
			actionBreakContinue(cpuInterface);
423
		}
424
		const auto& style = ImGui::GetStyle();
425
		ImGui::SameLine(0.0f, 2.0f * style.ItemSpacing.x);
426
427
		im::Disabled(!breaked, [&]{
428
			if (ButtonGlyph("step-in", DEBUGGER_ICON_STEP_IN, DEBUGGER_STEP_IN)) {
429
				actionStepIn(cpuInterface);
430
			}
431
			ImGui::SameLine();
432
433
			if (ButtonGlyph("step-over", DEBUGGER_ICON_STEP_OVER, DEBUGGER_STEP_OVER)) {
434
				actionStepOver();
435
			}
436
			ImGui::SameLine();
437
438
			if (ButtonGlyph("step-out",  DEBUGGER_ICON_STEP_OUT, DEBUGGER_STEP_OUT)) {
439
				actionStepOut();
440
			}
441
			ImGui::SameLine();
442
443
			if (ButtonGlyph("step-back", DEBUGGER_ICON_STEP_BACK, DEBUGGER_STEP_BACK)) {
444
				actionStepBack();
445
			}
446
		});
447
	});
448
}
449
450
ImGuiDebugger::SlotInfo ImGuiDebugger::getSlotInfo(MSXCPUInterface& cpuInterface, Debugger& debugger) const
451
{
452
	SlotInfo result;
453
	for (auto page : xrange(uint8_t(4))) {
454
		auto& r = result[page];
455
456
		r.ps = cpuInterface.getPrimarySlot(page);
457
		if (cpuInterface.isExpanded(r.ps)) {
458
			r.ss = cpuInterface.getSecondarySlot(page);
459
		}
460
461
		const auto* device = cpuInterface.getVisibleMSXDevice(page);
462
		if (const auto* mapper = dynamic_cast<const MSXMemoryMapperBase*>(device)) {
463
			r.segment = strCat(mapper->getSelectedSegment(page));
464
		} else if (auto [rom, romBlocks] = ImGuiDisassembly::getRomBlocks(debugger, device); romBlocks) {
465
			if (unsigned blockSize = RomInfo::getBlockSize(rom->getRomType())) {
466
				auto addr = 0x4000 * page;
467
				char separator = 'R';
468
				for (int offset = 0; offset < 0x4000; offset += blockSize) {
469
					auto seg = romBlocks->readExt(addr + offset);
470
					strAppend(r.segment, separator, strCat_if(seg != unsigned(-1), seg).else_('-'));
471
					separator = '/';
472
				}
473
			} else {
474
				r.segment = "-";
475
			}
476
		} else {
477
			r.segment = "-";
478
		}
479
	}
480
	return result;
481
}
482
483
void ImGuiDebugger::drawSlots(MSXCPUInterface& cpuInterface, Debugger& debugger)
484
{
485
	if (!showSlots) return;
486
	im::Window("Slots", &showSlots, [&]{
487
		int flags = ImGuiTableFlags_BordersInnerV |
488
			ImGuiTableFlags_Resizable |
489
			ImGuiTableFlags_Reorderable |
490
			ImGuiTableFlags_Hideable |
491
			ImGuiTableFlags_ContextMenuInBody;
492
		im::Table("table", 4, flags, [&]{
493
			ImGui::TableSetupColumn("Page");
494
			ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_DefaultHide);
495
			ImGui::TableSetupColumn("Slot");
496
			ImGui::TableSetupColumn("Segment");
497
			ImGui::TableHeadersRow();
498
499
			auto slots = getSlotInfo(cpuInterface, debugger);
500
501
			if (!slotSnapshot && needSnapshot()) {
502
				slotSnapshot = slots;
503
			}
504
			bool drawChanges = slotSnapshot && needDrawChanges();
505
			auto color = getChangesColor();
506
507
			for (auto page : xrange(uint8_t(4))) {
508
				const auto& p = slots[page];
509
				if (ImGui::TableNextColumn()) { // page
510
					ImGui::StrCat(page);
511
				}
512
				if (ImGui::TableNextColumn()) { // address
513
					ImGui::StrCat("0x", hex_string<4>(0x4000 * page));
514
				}
515
				if (ImGui::TableNextColumn()) { // slot
516
					auto s = p.ss ? strCat(p.ps, '-', *p.ss) : strCat(' ', p.ps);
517
					bool changed = drawChanges && ((p.ps != (*slotSnapshot)[page].ps) ||
518
					                               (p.ss != (*slotSnapshot)[page].ss));
519
					im::StyleColor(changed, ImGuiCol_Text, color, [&]{
520
						ImGui::TextUnformatted(s);
521
					});
522
				}
523
				if (ImGui::TableNextColumn()) { // segment
524
					bool changed = drawChanges && (p.segment != (*slotSnapshot)[page].segment);
525
					im::StyleColor(changed, ImGuiCol_Text, color, [&]{
526
						ImGui::TextUnformatted(p.segment);
527
					});
528
				}
529
			}
530
		});
531
	});
532
}
533
534
void ImGuiDebugger::drawStack(const CPURegs& regs, const MSXCPUInterface& cpuInterface, EmuTime time)
535
{
536
	if (!showStack) return;
537
538
	auto line = ImGui::GetTextLineHeightWithSpacing();
539
	ImGui::SetNextWindowSize(ImVec2(0.0f, 12 * line), ImGuiCond_FirstUseEver);
540
	im::Window("stack", &showStack, [&]{
541
		auto sp = regs.getSP();
542
543
		int flags = ImGuiTableFlags_ScrollY |
544
			ImGuiTableFlags_BordersInnerV |
545
			ImGuiTableFlags_Resizable |
546
			ImGuiTableFlags_Reorderable |
547
			ImGuiTableFlags_Hideable |
548
			ImGuiTableFlags_ContextMenuInBody;
549
		im::Table("table", 3, flags, [&]{
550
			ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
551
			ImGui::TableSetupColumn("Address");
552
			ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_DefaultHide);
553
			ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide);
554
			ImGui::TableHeadersRow();
555
556
			im::ScopedFont sf(manager.fontMono);
557
			im::ListClipper(std::min(128, (0x10000 - sp) / 2), [&](int row) {
558
				auto offset = 2 * row;
559
				auto addr = sp + offset;
560
				if (ImGui::TableNextColumn()) { // address
561
					ImGui::StrCat(hex_string<4>(addr));
562
				}
563
				if (ImGui::TableNextColumn()) { // offset
564
					ImGui::Text("SP+%X", offset);
565
				}
566
				if (ImGui::TableNextColumn()) { // value
567
					auto l = cpuInterface.peekMem(narrow_cast<uint16_t>(addr + 0), time);
568
					auto h = cpuInterface.peekMem(narrow_cast<uint16_t>(addr + 1), time);
569
					auto value = narrow<uint16_t>(256 * h + l);
570
					ImGui::StrCat(hex_string<4>(value));
571
				}
572
			});
573
		});
574
	});
575
}
576
577
bool ImGuiDebugger::needDrawChanges() const
578
{
579
	if (showChanges == SHOW_NEVER) return false;
580
	if (showChanges == SHOW_ALWAYS) return true;
581
	return MSXCPUInterface::isBreaked() || showChangesFrameCounter;
582
}
583
584
void ImGuiDebugger::drawRegisters(CPURegs& regs)
585
{
586
	if (!showRegisters) return;
587
	im::Window("CPU registers", &showRegisters, [&]{
588
		std::optional<im::ScopedFont> sf(manager.fontMono);
589
590
		if (!cpuRegsSnapshot && needSnapshot()) {
591
			cpuRegsSnapshot = regs;
592
		}
593
		bool drawChanges = cpuRegsSnapshot && needDrawChanges();
594
		auto color = getChangesColor();
595
596
		const auto& style = ImGui::GetStyle();
597
		auto padding = 2 * style.FramePadding.x;
598
		auto width16 = ImGui::CalcTextSize("FFFF"sv).x + padding;
599
		auto edit16 = [&](std::string_view label, std::string_view high, std::string_view low, auto getter, auto setter) {
600
			auto value = getter(regs);
601
			im::Group([&]{
602
				ImGui::AlignTextToFramePadding();
603
				ImGui::TextUnformatted(label);
604
				ImGui::SameLine();
605
				ImGui::SetNextItemWidth(width16);
606
				bool changed = drawChanges && (getter(*cpuRegsSnapshot) != value);
607
				im::StyleColor(changed, ImGuiCol_Text, color, [&]{
608
					if (ImGui::InputScalar(tmpStrCat("##", label).c_str(), ImGuiDataType_U16, &value, nullptr, nullptr, "%04X")) {
609
						setter(value);
610
					}
611
				});
612
			});
613
			simpleToolTip([&]{
614
				return strCat(
615
					"Bin: ", bin_string<4>(value >> 12), ' ', bin_string<4>(value >>  8), ' ',
616
					         bin_string<4>(value >>  4), ' ', bin_string<4>(value >>  0), "\n"
617
					"Dec: ", dec_string<5>(value), '\n',
618
					high, ": ", dec_string<3>(value >> 8), "  ", low, ": ", dec_string<3>(value & 0xff));
619
			});
620
		};
621
		auto edit8 = [&](std::string_view label, auto getter, auto setter) {
622
			auto value = getter(regs);
623
			im::Group([&]{
624
				ImGui::AlignTextToFramePadding();
625
				ImGui::TextUnformatted(label);
626
				ImGui::SameLine();
627
				ImGui::SetNextItemWidth(width16);
628
				bool changed = drawChanges && (getter(*cpuRegsSnapshot) != value);
629
				im::StyleColor(changed, ImGuiCol_Text, color, [&]{
630
					if (ImGui::InputScalar(tmpStrCat("##", label).c_str(), ImGuiDataType_U8, &value, nullptr, nullptr, "%02X")) {
631
						setter(value);
632
					}
633
				});
634
			});
635
			simpleToolTip([&]{
636
				return strCat(
637
					"Bin: ", bin_string<4>(value >> 4), ' ', bin_string<4>(value >> 0), "\n"
638
					"Dec: ", dec_string<3>(value), '\n');
639
			});
640
		};
641
642
		edit16("AF", "A", "F", [&](const CPURegs& r) { return r.getAF(); }, [&](uint16_t value) { regs.setAF(value); });
643
		ImGui::SameLine(0.0f, 20.0f);
644
		edit16("AF'", "A'", "F'", [&](const CPURegs& r) { return r.getAF2(); }, [&](uint16_t value) { regs.setAF2(value); });
645
646
		edit16("BC", "B", "C", [&](const CPURegs& r) { return r.getBC(); }, [&](uint16_t value) { regs.setBC(value); });
647
		ImGui::SameLine(0.0f, 20.0f);
648
		edit16("BC'", "B'", "C'", [&](const CPURegs& r) { return r.getBC2(); }, [&](uint16_t value) { regs.setBC2(value); });
649
650
		edit16("DE", "D", "E", [&](const CPURegs& r) { return r.getDE(); }, [&](uint16_t value) { regs.setDE(value); });
651
		ImGui::SameLine(0.0f, 20.0f);
652
		edit16("DE'", "D'", "E'", [&](const CPURegs& r) { return r.getDE2(); }, [&](uint16_t value) { regs.setDE2(value); });
653
654
		edit16("HL", "H", "L", [&](const CPURegs& r) { return r.getHL(); }, [&](uint16_t value) { regs.setHL(value); });
655
		ImGui::SameLine(0.0f, 20.0f);
656
		edit16("HL'", "H'", "L'", [&](const CPURegs& r) { return r.getHL2(); }, [&](uint16_t value) { regs.setHL2(value); });
657
658
		edit16("IX", "IXh", "IXl", [&](const CPURegs& r) { return r.getIX(); }, [&](uint16_t value) { regs.setIX(value); });
659
		ImGui::SameLine(0.0f, 20.0f);
660
		edit16("IY ", "IYh", "IYl", [&](const CPURegs& r) { return r.getIY(); }, [&](uint16_t value) { regs.setIY(value); });
661
662
		edit16("PC", "PCh", "PCl", [&](const CPURegs& r) { return r.getPC(); }, [&](uint16_t value) { regs.setPC(value); });
663
		ImGui::SameLine(0.0f, 20.0f);
664
		edit16("SP ", "SPh", "SPl", [&](const CPURegs& r) { return r.getSP(); }, [&](uint16_t value) { regs.setSP(value); });
665
666
		edit8("I ", [&](const CPURegs& r) { return r.getI(); }, [&](uint8_t value) { regs.setI(value); });
667
		ImGui::SameLine(0.0f, 20.0f);
668
		edit8("R  ", [&](const CPURegs& r) { return r.getR(); }, [&](uint8_t value) { regs.setR(value); });
669
670
		ImGui::AlignTextToFramePadding();
671
		ImGui::TextUnformatted("IM"sv);
672
		ImGui::SameLine();
673
		ImGui::SetNextItemWidth(width16);
674
		uint8_t im = regs.getIM();
675
		bool imChanged = drawChanges && (im != cpuRegsSnapshot->getIM());
676
		im::StyleColor(imChanged, ImGuiCol_Text, color, [&]{
677
			if (ImGui::InputScalar("##IM", ImGuiDataType_U8, &im, nullptr, nullptr, "%d")) {
678
				if (im <= 2) regs.setIM(im);
679
			}
680
		});
681
682
		ImGui::SameLine(0.0f, 20.0f);
683
		ImGui::AlignTextToFramePadding();
684
		bool ei = regs.getIFF1();
685
		bool eiChanged = drawChanges && (ei != cpuRegsSnapshot->getIFF1());
686
		im::StyleColor(eiChanged, ImGuiCol_Text, color, [&]{
687
			auto size = ImGui::CalcTextSize("EI");
688
			if (ImGui::Selectable(ei ? "EI" : "DI", false, ImGuiSelectableFlags_AllowDoubleClick, size)) {
689
				if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
690
					regs.setIFF1(!ei);
691
					regs.setIFF2(!ei);
692
				}
693
			}
694
		});
695
696
		sf.reset();
697
		simpleToolTip("double-click to toggle");
698
699
		HelpMarker("Click values to edit, double-click EI/DI to toggle.\n"
700
		           "Right-click to configure",
701
		           2.0f * ImGui::GetFontSize());
702
703
		im::PopupContextWindow([&]{
704
			configureChangesMenu();
705
		});
706
	});
707
}
708
709
void ImGuiDebugger::configureChangesMenu()
710
{
711
	ImGui::TextUnformatted("Highlight changes between breaks"sv);
712
	ImGui::RadioButton("don't show", &showChanges, SHOW_NEVER);
713
	ImGui::RadioButton("show during break", &showChanges, SHOW_DURING_BREAK);
714
	ImGui::RadioButton("always show", &showChanges, SHOW_ALWAYS);
715
	ImGui::ColorEdit4("highlight color", changesColor.data(),
716
		ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha);
717
}
718
719
void ImGuiDebugger::drawFlags(CPURegs& regs)
720
{
721
	if (!showFlags) return;
722
	im::Window("CPU flags", &showFlags, [&]{
723
		auto [sizeH1, sizeH2, sizeV] = [&]{
724
			im::ScopedFont sf(manager.fontMono);
725
			return std::tuple{
726
				ImGui::CalcTextSize("NC"sv),
727
				ImGui::CalcTextSize("X:0"sv),
728
				ImGui::CalcTextSize("C 0 (NC)"sv)
729
			};
730
		}();
731
732
		if (!cpuRegsSnapshot && needSnapshot()) {
733
			cpuRegsSnapshot = regs;
734
		}
735
		bool drawChanges = cpuRegsSnapshot && needDrawChanges();
736
		auto color = getChangesColor();
737
738
		auto f = regs.getF();
739
		auto oldF = drawChanges ? cpuRegsSnapshot->getF() : f;
740
741
		auto draw = [&](std::string_view name, uint8_t bit, std::string_view val0 = {}, std::string_view val1 = {}) {
742
			std::string s;
743
			ImVec2 sz;
744
			bool val = f & bit;
745
			bool oldVal = oldF & bit;
746
747
			if (flagsLayout == 0) {
748
				// horizontal
749
				if (!val0.empty()) {
750
					s = val ? val1 : val0;
751
					sz = sizeH1;
752
				} else {
753
					s = strCat(name, ':', val ? '1' : '0');
754
					sz = sizeH2;
755
				}
756
			} else {
757
				// vertical
758
				s = strCat(name, ' ', val ? '1' : '0', strCat_if(!val0.empty(), " (", val ? val1 : val0, ')'));
759
				sz = sizeV;
760
			}
761
762
			im::StyleColor(val != oldVal, ImGuiCol_Text, color, [&]{
763
				if (ImGui::Selectable(s.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, sz)) {
764
					if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
765
						regs.setF(f ^ bit);
766
					}
767
				}
768
			});
769
			simpleToolTip("double-click to toggle, right-click to configure");
770
			if (flagsLayout == 0) {
771
				// horizontal
772
				ImGui::SameLine(0.0f, sizeH1.x);
773
			}
774
		};
775
776
		im::Font(manager.fontMono, [&]{
777
			draw("S", 0x80, " P", " M");
778
			draw("Z", 0x40, "NZ", " Z");
779
			if (showXYFlags) draw("Y", 0x20);
780
			draw("H", 0x10);
781
			if (showXYFlags) draw("X", 0x08);
782
			draw("P", 0x04, "PO", "PE");
783
			draw("N", 0x02);
784
			draw("C", 0x01, "NC", " C");
785
		});
786
787
		im::PopupContextWindow([&]{
788
			ImGui::TextUnformatted("Layout"sv);
789
			ImGui::RadioButton("horizontal", &flagsLayout, 0);
790
			ImGui::RadioButton("vertical", &flagsLayout, 1);
791
			ImGui::Separator();
792
			ImGui::Checkbox("show undocumented", &showXYFlags);
793
		});
794
	});
795
}
796
797
} // namespace openmsx