OXYGENHUB
◆ main openmsx / src / imgui / ImGuiDisassembly.cc
20.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "ImGuiDisassembly.hh"
2
3
#include "ImGuiBreakPoints.hh"
4
#include "ImGuiCpp.hh"
5
#include "ImGuiDebugger.hh"
6
#include "ImGuiManager.hh"
7
#include "ImGuiUtils.hh"
8
9
#include "CPURegs.hh"
10
#include "Dasm.hh"
11
#include "Debugger.hh"
12
#include "Display.hh"
13
#include "GlobalSettings.hh"
14
#include "MSXCPU.hh"
15
#include "MSXCPUInterface.hh"
16
#include "MSXMemoryMapperBase.hh"
17
#include "MSXMotherBoard.hh"
18
#include "Reactor.hh"
19
#include "RomBlockDebuggable.hh"
20
#include "RomPlain.hh"
21
#include "SymbolManager.hh"
22
#include "VideoSystem.hh"
23
24
#include "join.hh"
25
#include "narrow.hh"
26
#include "strCat.hh"
27
28
#include <imgui.h>
29
#include <imgui_stdlib.h>
30
31
#include <cstdint>
32
#include <ranges>
33
#include <vector>
34
35
using namespace std::literals;
36
37
namespace openmsx {
38
39
ImGuiDisassembly::ImGuiDisassembly(ImGuiManager& manager_, size_t index)
40
	: ImGuiPart(manager_)
41
	, symbolManager(manager.getReactor().getSymbolManager())
42
	, title(strCat("Disassembly", strCat_if(index, " (", index + 1, ')')))
43
{
44
	scrollToPcOnBreak = index == 0;
45
}
46
47
void ImGuiDisassembly::save(ImGuiTextBuffer& buf)
48
{
49
	savePersistent(buf, *this, persistentElements);
50
}
51
52
void ImGuiDisassembly::loadLine(std::string_view name, zstring_view value)
53
{
54
	loadOnePersistent(name, value, *this, persistentElements);
55
}
56
57
void ImGuiDisassembly::loadEnd()
58
{
59
	setDisassemblyScrollY = disassemblyScrollY;
60
}
61
62
void ImGuiDisassembly::setGotoTarget(uint16_t target)
63
{
64
	gotoTarget = target;
65
	show = true;
66
	setDisassemblyScrollY.reset(); // don't restore initial scroll position
67
}
68
69
std::pair<const MSXRom*, RomBlockDebuggableBase*>
70
	ImGuiDisassembly::getRomBlocks(Debugger& debugger, const MSXDevice* device)
71
{
72
	RomBlockDebuggableBase* romBlocks = nullptr;
73
	const auto* rom = dynamic_cast<const MSXRom*>(device);
74
	if (rom && !dynamic_cast<const RomPlain*>(rom)) {
75
		if (auto* debug8 = dynamic_cast<RomBlockDebuggableBase::Debuggable8*>(
76
			debugger.findDebuggable(tmpStrCat(rom->getName(), " romblocks")))) {
77
			romBlocks = &debug8->getRomBlocks();
78
		}
79
	}
80
	return {rom, romBlocks};
81
}
82
83
struct CurrentSlot {
84
	int ps = 0;
85
	std::optional<int> ss;
86
	std::optional<int> seg;
87
};
88
[[nodiscard]] static CurrentSlot getCurrentSlot(
89
	MSXCPUInterface& cpuInterface, Debugger& debugger,
90
	uint16_t addr, bool wantSs = true, bool wantSeg = true)
91
{
92
	CurrentSlot result;
93
	int page = addr / 0x4000;
94
	result.ps = cpuInterface.getPrimarySlot(page);
95
96
	if (wantSs && cpuInterface.isExpanded(result.ps)) {
97
		result.ss = cpuInterface.getSecondarySlot(page);
98
	}
99
	if (wantSeg) {
100
		const auto* device = cpuInterface.getVisibleMSXDevice(page);
101
		if (const auto* mapper = dynamic_cast<const MSXMemoryMapperBase*>(device)) {
102
			result.seg = mapper->getSelectedSegment(narrow<uint8_t>(page));
103
		} else if (auto [_, romBlocks] = ImGuiDisassembly::getRomBlocks(debugger, device); romBlocks) {
104
			result.seg = romBlocks->readExt(addr);
105
		}
106
	}
107
	return result;
108
}
109
110
[[nodiscard]] static TclObject toTclExpression(const CurrentSlot& slot)
111
{
112
	return TclObject(tmpStrCat(
113
		"[pc_in_slot ", slot.ps,
114
		strCat_if(slot.ss, ' ', STRCAT_LAZY(*slot.ss)).else_if(slot.seg, " X"),
115
		strCat_if(slot.seg, ' ', STRCAT_LAZY(*slot.seg)),
116
		']'));
117
}
118
119
[[nodiscard]] static bool addrInSlot(
120
	const ParsedSlotCond& slot, MSXCPUInterface& cpuInterface, Debugger& debugger, uint16_t addr)
121
{
122
	if (!slot.hasPs) return true; // no ps specified -> always ok
123
124
	auto current = getCurrentSlot(cpuInterface, debugger, addr, slot.hasSs, slot.hasSeg);
125
	if (slot.ps != current.ps) return false;
126
	if (slot.hasSs && current.ss && (slot.ss != current.ss)) return false;
127
	if (slot.hasSeg && current.seg && (slot.seg != current.seg)) return false;
128
	return true;
129
}
130
131
struct BpLine {
132
	int count = 0;
133
	int idx = -1; // only valid when count=1
134
	bool anyEnabled = false;
135
	bool anyDisabled = false;
136
	bool anyInSlot = false;
137
	bool anyComplex = false;
138
};
139
static BpLine examineBpLine(uint16_t addr, std::span<BreakPoint> bps, MSXCPUInterface& cpuInterface, Debugger& debugger)
140
{
141
	BpLine result;
142
	for (auto [i, bp] : enumerate(bps)) {
143
		if (bp.getAddress() != addr) continue;
144
		++result.count;
145
		result.idx = int(i);
146
147
		bool enabled = bp.isEnabled();
148
		result.anyEnabled |= enabled;
149
		result.anyDisabled |= !enabled;
150
151
		ParsedSlotCond bpSlot("pc_in_slot", bp.getCondition().getString());
152
		result.anyInSlot |= addrInSlot(bpSlot, cpuInterface, debugger, addr);
153
154
		result.anyComplex |= !bpSlot.rest.empty() || (bp.getCommand().getString() != "debug break");
155
	}
156
	return result;
157
}
158
159
static void toggleBp(uint16_t addr, const BpLine& bpLine, std::span<BreakPoint> bps,
160
                     MSXCPUInterface& cpuInterface, Debugger& debugger,
161
                     std::optional<BreakPoint>& addBp, std::optional<unsigned>& removeBpId)
162
{
163
	if (bpLine.count != 0) {
164
		// only allow to remove single breakpoints,
165
		// others can be edited via the breakpoint viewer
166
		if (bpLine.count == 1) {
167
			auto& bp = bps[bpLine.idx];
168
			removeBpId = bp.getId(); // schedule removal
169
		}
170
	} else {
171
		// schedule creation of new bp
172
		addBp.emplace();
173
		addBp->setAddress(debugger.getInterpreter(), TclObject(tmpStrCat("0x", hex_string<4>(addr))));
174
		addBp->setCondition(toTclExpression(getCurrentSlot(cpuInterface, debugger, addr)));
175
	}
176
}
177
void ImGuiDisassembly::actionToggleBp(MSXMotherBoard& motherBoard)
178
{
179
	auto& cpuInterface = motherBoard.getCPUInterface();
180
	auto& debugger = motherBoard.getDebugger();
181
	auto& bps = cpuInterface.getBreakPoints();
182
	std::optional<BreakPoint> addBp;
183
	std::optional<unsigned> removeBpId;
184
	auto addr = selectedAddr ? *selectedAddr : motherBoard.getCPU().getRegisters().getPC();
185
186
	auto bpLine = examineBpLine(addr, bps, cpuInterface, debugger);
187
188
	toggleBp(addr, bpLine, bps, cpuInterface, debugger, addBp, removeBpId);
189
	if (addBp) {
190
		cpuInterface.insertBreakPoint(std::move(*addBp));
191
	}
192
	if (removeBpId) {
193
		cpuInterface.removeBreakPoint(*removeBpId);
194
	}
195
}
196
197
void ImGuiDisassembly::paint(MSXMotherBoard* motherBoard)
198
{
199
	if (!motherBoard || !show) return;
200
201
	ImGui::SetNextWindowSize({340, 540}, ImGuiCond_FirstUseEver);
202
	im::Window(title.c_str(), &show, [&]{
203
		auto& regs = motherBoard->getCPU().getRegisters();
204
		auto& cpuInterface = motherBoard->getCPUInterface();
205
		auto& debugger = motherBoard->getDebugger();
206
		auto time = motherBoard->getCurrentTime();
207
208
		manager.debugger->checkShortcuts(cpuInterface, *motherBoard, this);
209
210
		std::optional<BreakPoint> addBp;
211
		std::optional<unsigned> removeBpId;
212
213
		auto pc = regs.getPC();
214
		auto& reactor = manager.getReactor();
215
		auto running = !MSXCPUInterface::isBreaked() && !reactor.getGlobalSettings().getPauseSetting().getBoolean();
216
		if (followPC && running) {
217
			gotoTarget = pc;
218
		}
219
220
		if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
221
			selectedAddr = {};
222
		}
223
224
		auto widthOpcode = ImGui::CalcTextSize("12 34 56 78"sv).x;
225
		int flags = ImGuiTableFlags_RowBg |
226
			ImGuiTableFlags_BordersV |
227
			ImGuiTableFlags_BordersOuterV |
228
			ImGuiTableFlags_Resizable |
229
			ImGuiTableFlags_Hideable |
230
			ImGuiTableFlags_Reorderable |
231
			ImGuiTableFlags_ScrollY |
232
			ImGuiTableFlags_ScrollX;
233
		im::Table("table", 4, flags, [&]{
234
			ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
235
			ImGui::TableSetupColumn("bp", ImGuiTableColumnFlags_WidthFixed);
236
			ImGui::TableSetupColumn("address", ImGuiTableColumnFlags_NoHide);
237
			ImGui::TableSetupColumn("opcode", ImGuiTableColumnFlags_WidthFixed, widthOpcode);
238
			ImGui::TableSetupColumn("mnemonic", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHide);
239
			ImGui::TableHeadersRow();
240
241
			auto& bps = cpuInterface.getBreakPoints();
242
			auto textSize = ImGui::GetTextLineHeight();
243
244
			std::string mnemonic;
245
			std::string opcodesStr;
246
			std::vector<std::string_view> candidates;
247
			std::array<uint8_t, 4> opcodes;
248
			ImGuiListClipper clipper; // only draw the actually visible rows
249
			clipper.Begin(0x10000, ImGui::GetTextLineHeightWithSpacing());
250
			if (gotoTarget) {
251
				clipper.IncludeItemsByIndex(narrow<int>(*gotoTarget), narrow<int>(*gotoTarget + 4));
252
			}
253
			std::optional<unsigned> nextGotoTarget;
254
			std::optional<unsigned> minAddr;
255
			std::optional<unsigned> maxAddr;
256
			bool toClipboard = false;
257
			while (clipper.Step()) {
258
				// Note this while loop can iterate multiple times, though we mitigate this by passing the
259
				// row height to the ImGuiListClipper constructor. Another reason is because we called
260
				// clipper.IncludeItemsByIndex(), but that only happens when 'gotoTarget' is set.
261
				// Because of this it's acceptable to just record the min and max address in the for loop below.
262
				auto addr16 = instructionBoundary(cpuInterface, narrow<uint16_t>(clipper.DisplayStart), time);
263
				for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
264
					unsigned addr = addr16;
265
					minAddr = std::min(addr, minAddr.value_or(std::numeric_limits<unsigned>::max()));
266
					maxAddr = std::max(addr, maxAddr.value_or(std::numeric_limits<unsigned>::min()));
267
					ImGui::TableNextRow();
268
					if (addr >= 0x10000) continue;
269
					im::ID(narrow<int>(addr), [&]{
270
						if (gotoTarget && addr >= *gotoTarget) {
271
							gotoTarget = {};
272
							ImGui::SetScrollHereY(0.25f);
273
						}
274
275
						if (bool rowAtPc = !syncDisassemblyWithPC && (addr == pc);
276
						    rowAtPc) {
277
							ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, getColor(imColor::YELLOW_BG));
278
						}
279
						bool bpRightClick = false;
280
						if (ImGui::TableNextColumn()) { // bp
281
							auto bpLine = examineBpLine(addr16, bps, cpuInterface, debugger);
282
							bool hasBp = bpLine.count != 0;
283
							bool multi = bpLine.count > 1;
284
							if (hasBp) {
285
								auto calcColor = [](bool enabled, bool inSlot) {
286
									auto [r,g,b] = enabled ? std::tuple{0xE0, 0x00, 0x00}
287
									                       : std::tuple{0x70, 0x70, 0x70};
288
									auto a = inSlot ? 0xFF : 0x60;
289
									return IM_COL32(r, g, b, a);
290
								};
291
								auto colIn = calcColor(bpLine.anyEnabled, bpLine.anyInSlot);
292
								auto colOut = ImGui::GetColorU32(ImGuiCol_WindowBg);
293
294
								auto* drawList = ImGui::GetWindowDrawList();
295
								gl::vec2 scrn = ImGui::GetCursorScreenPos();
296
								auto center = scrn + textSize * gl::vec2(multi ? 0.3f : 0.5f, 0.5f);
297
								auto radiusIn = 0.4f * textSize;
298
								auto radiusOut = 0.5f * textSize;
299
300
								if (multi) {
301
									auto center2 = center + textSize * gl::vec2(0.4f, 0.0f);
302
									drawList->AddCircleFilled(center2, radiusOut, colOut);
303
									auto colIn2 = calcColor(!bpLine.anyDisabled, bpLine.anyInSlot);
304
									drawList->AddCircleFilled(center2, radiusIn, colIn2);
305
								}
306
								drawList->AddCircleFilled(center, radiusOut, colOut);
307
								drawList->AddCircleFilled(center, radiusIn, colIn);
308
								if (bpLine.anyComplex) {
309
									auto d = 0.3f * textSize;
310
									auto c = IM_COL32(0, 0, 0, 192);
311
									auto t = 0.2f * textSize;
312
									drawList->AddLine(center - gl::vec2(d, 0.0f), center + gl::vec2(d, 0.0f), c, t);
313
									drawList->AddLine(center - gl::vec2(0.0f, d), center + gl::vec2(0.0f, d), c, t);
314
								}
315
							}
316
							if (ImGui::InvisibleButton("##bp-button", {-FLT_MIN, textSize})) {
317
								toggleBp(addr16, bpLine, bps, cpuInterface, debugger, addBp, removeBpId);
318
							} else {
319
								bpRightClick = hasBp && ImGui::IsItemClicked(ImGuiMouseButton_Right);
320
								if (bpRightClick) ImGui::OpenPopup("bp-context");
321
								im::Popup("bp-context", [&]{
322
									manager.breakPoints->paintBpTab(cpuInterface, addr16);
323
								});
324
							}
325
						}
326
327
						std::optional<uint16_t> mnemonicAddr;
328
						std::span<const Symbol* const> mnemonicLabels;
329
						auto len = disassemble(cpuInterface, addr, pc, time,
330
							opcodes, mnemonic, mnemonicAddr, mnemonicLabels);
331
332
						if (ImGui::TableNextColumn()) { // addr
333
							bool focusScrollToAddress = false;
334
							bool focusRunToAddress = false;
335
336
							// do the full-row-selectable stuff in a column that cannot be hidden
337
							auto pos = ImGui::GetCursorPos();
338
							ImGui::Selectable("##row", selectedAddr == addr,
339
									ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
340
							if (ImGui::IsItemFocused()) {
341
								selectedAddr = addr;
342
							}
343
							using enum Shortcuts::ID;
344
							auto& shortcuts = manager.getShortcuts();
345
							if (shortcuts.checkShortcut(DISASM_GOTO_ADDR)) {
346
								ImGui::OpenPopup("disassembly-context");
347
								focusScrollToAddress = true;
348
							}
349
							if (shortcuts.checkShortcut(DISASM_RUN_TO_ADDR)) {
350
								ImGui::OpenPopup("disassembly-context");
351
								focusRunToAddress = true;
352
							}
353
							if (!bpRightClick && ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
354
								ImGui::OpenPopup("disassembly-context");
355
							}
356
357
							auto addrStr = tmpStrCat(hex_string<4>(addr));
358
							im::Popup("disassembly-context", [&]{
359
								auto addrToolTip = [&](const std::string& str) {
360
									simpleToolTip([&]{
361
										if (auto a = symbolManager.parseSymbolOrValue(str)) {
362
											return strCat("0x", hex_string<4>(*a));
363
										}
364
										return std::string{};
365
									});
366
								};
367
368
								if (ImGui::MenuItem("Scroll to PC")) {
369
									nextGotoTarget = pc;
370
								}
371
372
								im::Indent([&]{
373
									ImGui::Checkbox("Follow PC while running", &followPC);
374
									ImGui::Checkbox("Scroll to PC on break", &scrollToPcOnBreak);
375
								});
376
377
								ImGui::AlignTextToFramePadding();
378
								ImGui::TextUnformatted("Scroll to address:"sv);
379
								ImGui::SameLine();
380
								if (focusScrollToAddress) ImGui::SetKeyboardFocusHere();
381
								if (ImGui::InputText("##goto", &gotoAddr, ImGuiInputTextFlags_EnterReturnsTrue)) {
382
									if (auto a = symbolManager.parseSymbolOrValue(gotoAddr)) {
383
										nextGotoTarget = *a;
384
									}
385
								}
386
								addrToolTip(gotoAddr);
387
388
								ImGui::Separator();
389
390
								auto runTo = strCat("Run to here (address 0x", addrStr, ')');
391
								if (ImGui::MenuItem(runTo.c_str())) {
392
									manager.executeDelayed(makeTclList("run_to", addr));
393
								}
394
395
								ImGui::AlignTextToFramePadding();
396
								ImGui::TextUnformatted("Run to address:"sv);
397
								ImGui::SameLine();
398
								if (focusRunToAddress) ImGui::SetKeyboardFocusHere();
399
								if (ImGui::InputText("##run", &runToAddr, ImGuiInputTextFlags_EnterReturnsTrue)) {
400
									if (auto a = symbolManager.parseSymbolOrValue(runToAddr)) {
401
										manager.executeDelayed(makeTclList("run_to", *a));
402
									}
403
								}
404
								addrToolTip(runToAddr);
405
406
								ImGui::Separator();
407
408
								auto setPc = strCat("Set PC to 0x", addrStr);
409
								if (ImGui::MenuItem(setPc.c_str())) {
410
									regs.setPC(addr16);
411
								}
412
413
								ImGui::Separator();
414
415
								if (ImGui::MenuItem("Copy instructions to clipboard")) {
416
									toClipboard = true;
417
								}
418
							});
419
420
							enum class Priority : uint8_t {
421
								MISSING_BOTH = 0, // from lowest to highest
422
								MISSING_ONE,
423
								SLOT_AND_SEGMENT
424
							};
425
							using enum Priority;
426
							Priority currentPriority = MISSING_BOTH;
427
							candidates.clear();
428
							auto add = [&](const Symbol* sym, Priority newPriority) {
429
								if (newPriority < currentPriority) return; // we already have a better candidate
430
								if (newPriority > currentPriority) candidates.clear(); // drop previous candidates, we found a better one
431
								currentPriority = newPriority;
432
								candidates.push_back(sym->name); // cycle symbols in the same priority level
433
							};
434
435
							auto slot = getCurrentSlot(cpuInterface, debugger, addr16);
436
							auto psSs = uint8_t((slot.ss.value_or(0) << 2) + slot.ps);
437
							auto addrLabels = symbolManager.lookupValue(addr16);
438
							for (const Symbol* symbol: addrLabels) {
439
								// skip symbols with any mismatch
440
								if (symbol->slot && *symbol->slot != psSs) continue;
441
								if (symbol->segment && *symbol->segment != slot.seg) continue;
442
								// the info that's present does match
443
								if (symbol->slot && symbol->segment == slot.seg) {
444
									add(symbol, SLOT_AND_SEGMENT);
445
								} else if (!symbol->slot && !symbol->segment) {
446
									add(symbol, MISSING_BOTH);
447
								} else {
448
									add(symbol, MISSING_ONE);
449
								}
450
							}
451
							ImGui::SetCursorPos(pos);
452
							im::Font(manager.fontMono, [&]{
453
								ImGui::TextUnformatted(candidates.empty() ? addrStr : candidates[cycleLabelsCounter % candidates.size()]);
454
							});
455
							if (!addrLabels.empty()) {
456
								simpleToolTip([&]{
457
									return tmpStrCat(
458
										addrStr,
459
										strCat_if(addrLabels.size() > 1,
460
											"\nmultiple possibilities (click to cycle):\n",
461
											STRCAT_LAZY(join(std::views::transform(addrLabels, &Symbol::name), ' '))));
462
								});
463
								ImGui::SetCursorPos(pos);
464
								if (ImGui::InvisibleButton("##addrButton", {-FLT_MIN, textSize})) {
465
									++cycleLabelsCounter;
466
								}
467
							}
468
						}
469
470
						if (ImGui::TableNextColumn()) { // opcode
471
							opcodesStr.clear();
472
							for (auto i : xrange(len)) {
473
								strAppend(opcodesStr, hex_string<2>(opcodes[i]), ' ');
474
							}
475
							im::Font(manager.fontMono, [&]{
476
								ImGui::TextUnformatted(opcodesStr.data(), opcodesStr.data() + 3 * size_t(len) - 1);
477
							});
478
						}
479
480
						if (ImGui::TableNextColumn()) { // mnemonic
481
							auto pos = ImGui::GetCursorPos();
482
							im::Font(manager.fontMono, [&]{
483
								ImGui::TextUnformatted(mnemonic);
484
							});
485
							if (mnemonicAddr) {
486
								ImGui::SetCursorPos(pos);
487
								bool click = ImGui::InvisibleButton("##mnemonicButton", {-FLT_MIN, textSize});
488
								if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
489
									nextGotoTarget = *mnemonicAddr;
490
								}
491
								if (!mnemonicLabels.empty()) {
492
									if (click) ++cycleLabelsCounter;
493
									simpleToolTip([&]{
494
										return tmpStrCat(
495
											'#', hex_string<4>(*mnemonicAddr),
496
											strCat_if(mnemonicLabels.size() > 1,
497
												"\nmultiple possibilities (click to cycle):\n",
498
												STRCAT_LAZY(join(std::views::transform(mnemonicLabels, &Symbol::name), ' '))));
499
									});
500
								}
501
							}
502
						}
503
						addr16 += len;
504
					});
505
				}
506
			}
507
			gotoTarget = nextGotoTarget;
508
			if (syncDisassemblyWithPC) {
509
				syncDisassemblyWithPC = false; // only once
510
511
				auto itemHeight = ImGui::GetTextLineHeightWithSpacing();
512
				auto winHeight = ImGui::GetWindowHeight();
513
				auto lines = std::max(1, int(winHeight / itemHeight) - 1); // approx
514
515
				auto topAddr = nInstructionsBefore(cpuInterface, pc, time, narrow<int>(lines / 4) + 1);
516
517
				ImGui::SetScrollY(float(topAddr) * itemHeight);
518
			} else if (setDisassemblyScrollY) {
519
				ImGui::SetScrollY(*setDisassemblyScrollY);
520
				setDisassemblyScrollY.reset();
521
			}
522
			disassemblyScrollY = ImGui::GetScrollY();
523
524
			if (toClipboard && minAddr && maxAddr) {
525
				disassembleToClipboard(cpuInterface, pc, time, *minAddr, *maxAddr);
526
			}
527
		});
528
		// only add/remove bp's after drawing (can't change list of bp's while iterating over it)
529
		if (addBp) {
530
			cpuInterface.insertBreakPoint(std::move(*addBp));
531
		}
532
		if (removeBpId) {
533
			cpuInterface.removeBreakPoint(*removeBpId);
534
		}
535
	});
536
}
537
538
unsigned ImGuiDisassembly::disassemble(
539
	const MSXCPUInterface& cpuInterface, unsigned addr, unsigned pc, EmuTime time,
540
	std::span<uint8_t, 4> opcodes, std::string& mnemonic,
541
	std::optional<uint16_t>& mnemonicAddr, std::span<const Symbol* const>& mnemonicLabels)
542
{
543
	mnemonic.clear();
544
	auto len = dasm(cpuInterface, narrow<uint16_t>(addr), opcodes, mnemonic, time,
545
		[&](std::string& output, uint16_t a) {
546
			mnemonicAddr = a;
547
			mnemonicLabels = symbolManager.lookupValue(a);
548
			if (!mnemonicLabels.empty()) {
549
				strAppend(output, mnemonicLabels[cycleLabelsCounter % mnemonicLabels.size()]->name);
550
			} else {
551
				appendAddrAsHex(output, a);
552
			}
553
		});
554
	assert(len >= 1);
555
	if ((addr < pc) && (pc < (addr + len))) {
556
		// pc is strictly inside current instruction,
557
		// replace the just disassembled instruction with "db #..."
558
		mnemonicAddr.reset();
559
		mnemonicLabels = {};
560
		len = pc - addr;
561
		assert((1 <= len) && (len <= 3));
562
		mnemonic = strCat("db     ", join(
563
			std::views::transform(xrange(len),
564
				[&](unsigned i) { return strCat('#', hex_string<2>(opcodes[i])); }),
565
			','));
566
	}
567
	return len;
568
}
569
570
void ImGuiDisassembly::disassembleToClipboard(
571
	const MSXCPUInterface& cpuInterface, unsigned pc, EmuTime time,
572
	unsigned minAddr, unsigned maxAddr)
573
{
574
	std::string mnemonic;
575
	std::array<uint8_t, 4> opcodes;
576
	std::optional<uint16_t> mnemonicAddr;
577
	std::span<const Symbol* const> mnemonicLabels;
578
579
	std::string result;
580
581
	unsigned addr = minAddr;
582
	while (addr <= maxAddr) {
583
		mnemonic.clear();
584
		auto len = disassemble(cpuInterface, addr, pc, time,
585
			opcodes, mnemonic, mnemonicAddr, mnemonicLabels);
586
		strAppend(result , '\t', mnemonic, '\n');
587
		addr += len;
588
	}
589
590
	manager.getReactor().getDisplay().getVideoSystem().setClipboardText(result);
591
}
592
593
} // namespace openmsx