OXYGENHUB
◆ main openmsx / src / debugger / Debugger.cc
64.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "Debugger.hh"
2
3
#include "BreakPoint.hh"
4
#include "CPURegs.hh"
5
#include "CommandException.hh"
6
#include "Dasm.hh"
7
#include "DebugCondition.hh"
8
#include "Debuggable.hh"
9
#include "MSXCPU.hh"
10
#include "MSXCPUInterface.hh"
11
#include "MSXCliComm.hh"
12
#include "MSXMotherBoard.hh"
13
#include "ProbeBreakPoint.hh"
14
#include "Reactor.hh"
15
#include "SymbolManager.hh"
16
#include "TclArgParser.hh"
17
#include "TclObject.hh"
18
#include "WatchPoint.hh"
19
20
#include "MemBuffer.hh"
21
#include "StringOp.hh"
22
#include "narrow.hh"
23
#include "one_of.hh"
24
#include "stl.hh"
25
#include "unreachable.hh"
26
#include "xrange.hh"
27
28
#include <algorithm>
29
#include <array>
30
#include <cassert>
31
#include <memory>
32
#include <ranges>
33
34
namespace openmsx {
35
36
Debugger::Debugger(MSXMotherBoard& motherBoard_)
37
	: motherBoard(motherBoard_)
38
	, cmd(motherBoard.getCommandController(),
39
	      motherBoard.getStateChangeDistributor(),
40
	      motherBoard.getScheduler())
41
	, tracer(*this)
42
{
43
}
44
45
Debugger::~Debugger()
46
{
47
	assert(!cpu);
48
	assert(debuggables.empty());
49
}
50
51
void Debugger::registerDebuggable(std::string name, Debuggable& debuggable)
52
{
53
	assert(!debuggables.contains(name));
54
	debuggables.emplace_noDuplicateCheck(std::move(name), &debuggable);
55
}
56
57
void Debugger::unregisterDebuggable(std::string_view name, Debuggable& debuggable)
58
{
59
	assert(debuggables.contains(name));
60
	assert(debuggables[name] == &debuggable); (void)debuggable;
61
	debuggables.erase(name);
62
}
63
64
Debuggable* Debugger::findDebuggable(std::string_view name)
65
{
66
	auto* v = lookup(debuggables, name);
67
	return v ? *v : nullptr;
68
}
69
70
Debuggable& Debugger::getDebuggable(std::string_view name)
71
{
72
	Debuggable* result = findDebuggable(name);
73
	if (!result) {
74
		throw CommandException("No such debuggable: ", name);
75
	}
76
	return *result;
77
}
78
79
void Debugger::registerProbe(ProbeBase& probe)
80
{
81
	auto it = std::ranges::lower_bound(probes, probe.getName(), {}, &ProbeBase::getName);
82
	assert(it == probes.end() || (*it)->getName() != probe.getName()); // was not yet present
83
	probes.insert(it, &probe);
84
	tracer.probeCreated(*this, probe);
85
}
86
87
void Debugger::unregisterProbe(ProbeBase& probe)
88
{
89
	tracer.probeRemoved(probe);
90
	auto it = std::ranges::lower_bound(probes, probe.getName(), {}, &ProbeBase::getName);
91
	assert(it != probes.end() && (*it)->getName() == probe.getName());
92
	probes.erase(it);
93
}
94
95
ProbeBase* Debugger::findProbe(std::string_view name)
96
{
97
	auto it = std::ranges::lower_bound(probes, name, {}, &ProbeBase::getName);
98
	return (it != probes.end() && (*it)->getName() == name) ? *it : nullptr;
99
}
100
101
ProbeBase& Debugger::getProbe(std::string_view name)
102
{
103
	auto* result = findProbe(name);
104
	if (!result) {
105
		throw CommandException("No such probe: ", name);
106
	}
107
	return *result;
108
}
109
110
std::string Debugger::insertProbeBreakPoint(
111
	TclObject command, TclObject condition,
112
	ProbeBase& probe, bool once, unsigned newId /*= -1*/)
113
{
114
	auto bp = std::make_unique<ProbeBreakPoint>(
115
		std::move(command), std::move(condition), *this, probe, once, newId);
116
	auto result = bp->getIdStr();
117
	motherBoard.getMSXCliComm().update(CliComm::UpdateType::DEBUG_UPDT, result, "add");
118
	probeBreakPoints.push_back(std::move(bp));
119
	return result;
120
}
121
122
void Debugger::removeProbeBreakPoint(std::string_view name)
123
{
124
	if (name.starts_with(ProbeBreakPoint::prefix)) {
125
		// remove by id
126
		if (auto id = StringOp::stringToBase<10, unsigned>(name.substr(ProbeBreakPoint::prefix.size()))) {
127
			if (auto it = std::ranges::find(probeBreakPoints, id, &ProbeBreakPoint::getId);
128
			    it != std::end(probeBreakPoints)) {
129
				motherBoard.getMSXCliComm().update(
130
					CliComm::UpdateType::DEBUG_UPDT, name, "remove");
131
				move_pop_back(probeBreakPoints, it);
132
				return;
133
			}
134
		}
135
		throw CommandException("No such breakpoint: ", name);
136
	} else {
137
		// remove by probe, only works for unconditional bp
138
		auto it = std::ranges::find(probeBreakPoints, name, [](auto& bp) {
139
			return bp->getProbe().getName();
140
		});
141
		if (it == std::end(probeBreakPoints)) {
142
			throw CommandException(
143
				"No (unconditional) breakpoint for probe: ", name);
144
		}
145
		motherBoard.getMSXCliComm().update(
146
			CliComm::UpdateType::DEBUG_UPDT, (*it)->getIdStr(), "remove");
147
		move_pop_back(probeBreakPoints, it);
148
	}
149
}
150
151
void Debugger::removeProbeBreakPoint(ProbeBreakPoint& bp)
152
{
153
	motherBoard.getMSXCliComm().update(
154
		CliComm::UpdateType::DEBUG_UPDT, bp.getIdStr(), "remove");
155
	move_pop_back(probeBreakPoints, rfind_unguarded(probeBreakPoints, &bp,
156
		[](auto& v) { return v.get(); }));
157
}
158
159
void Debugger::transfer(Debugger& other)
160
{
161
	// Copy watchpoints to new machine.
162
	auto& cpuInterface = motherBoard.getCPUInterface();
163
	assert(cpuInterface.getWatchPoints().empty());
164
	for (const auto& wp : other.motherBoard.getCPUInterface().getWatchPoints()) {
165
		cpuInterface.setWatchPoint(std::make_shared<WatchPoint>(
166
			WatchPoint::clone_tag{}, *wp));
167
	}
168
169
	// Copy probes to new machine.
170
	assert(probeBreakPoints.empty());
171
	for (const auto& bp : other.probeBreakPoints) {
172
		if (ProbeBase* probe = findProbe(bp->getProbe().getName())) {
173
			insertProbeBreakPoint(bp->getCommand(),
174
			                      bp->getCondition(),
175
			                      *probe, bp->onlyOnce(),
176
			                      bp->getId());
177
		}
178
	}
179
180
	tracer.transfer(other, *this);
181
182
	// Breakpoints and conditions are (currently) global, so no need to
183
	// copy those.
184
}
185
186
Interpreter& Debugger::getInterpreter()
187
{
188
	return motherBoard.getReactor().getInterpreter();
189
}
190
191
std::vector<ImGuiWatchExpr::WatchExpr>& Debugger::getWatchExprs() const
192
{
193
	return motherBoard.getReactor().getImGuiManager().watchExpr->getWatchExprs();
194
}
195
196
// class Debugger::Cmd
197
198
static uint16_t getAddress(Interpreter& interp, const TclObject& token)
199
{
200
	unsigned addr = token.getInt(interp);
201
	if (addr >= 0x10000) {
202
		throw CommandException("Invalid address");
203
	}
204
	return narrow_cast<uint16_t>(addr);
205
}
206
207
Debugger::Cmd::Cmd(CommandController& commandController_,
208
                   StateChangeDistributor& stateChangeDistributor_,
209
                   Scheduler& scheduler_)
210
	: RecordedCommand(commandController_, stateChangeDistributor_,
211
	                  scheduler_, "debug")
212
{
213
}
214
215
bool Debugger::Cmd::needRecord(std::span<const TclObject> tokens) const
216
{
217
	// Note: it's crucial for security that only the write and write_block
218
	// subcommands are recorded and replayed. The 'set_bp' command for
219
	// example would allow to set a callback that can execute arbitrary Tcl
220
	// code. See comments in RecordedCommand for more details.
221
	if (tokens.size() < 2) return false;
222
	return tokens[1].getString() == one_of("write", "write_block");
223
}
224
225
void Debugger::Cmd::execute(
226
	std::span<const TclObject> tokens, TclObject& result, EmuTime time)
227
{
228
	checkNumArgs(tokens, AtLeast{2}, "subcommand ?arg ...?");
229
	executeSubCommand(tokens[1].getString(),
230
		"read",              [&]{ read(tokens, result); },
231
		"read_block",        [&]{ readBlock(tokens, result); },
232
		"write",             [&]{ write(tokens, result); },
233
		"write_block",       [&]{ writeBlock(tokens, result); },
234
		"size",              [&]{ size(tokens, result); },
235
		"desc",              [&]{ desc(tokens, result); },
236
		"list",              [&]{ list(result); },
237
		"step",              [&]{ debugger().motherBoard.getCPUInterface().doStep(); },
238
		"cont",              [&]{ debugger().motherBoard.getCPUInterface().doContinue(); },
239
		"disasm",            [&]{ disasm(tokens, result, time); },
240
		"disasm_blob",       [&]{ disasmBlob(tokens, result); },
241
		"break",             [&]{ debugger().motherBoard.getCPUInterface().doBreak(); },
242
		"breaked",           [&]{ result = MSXCPUInterface::isBreaked(); },
243
		"breakpoint",        [&]{ breakPoint(tokens, result); },
244
		"set_bp",            [&]{ setBreakPoint(tokens, result); },
245
		"remove_bp",         [&]{ removeBreakPoint(tokens, result); },
246
		"list_bp",           [&]{ listBreakPoints(tokens, result); },
247
		"watchpoint",        [&]{ watchPoint(tokens, result); },
248
		"watchexpr",         [&]{ watchExpr(tokens, result); },
249
		"set_watchpoint",    [&]{ setWatchPoint(tokens, result); },
250
		"remove_watchpoint", [&]{ removeWatchPoint(tokens, result); },
251
		"list_watchpoints",  [&]{ listWatchPoints(tokens, result); },
252
		"condition",         [&]{ condition(tokens, result); },
253
		"set_condition",     [&]{ setCondition(tokens, result); },
254
		"remove_condition",  [&]{ removeCondition(tokens, result); },
255
		"list_conditions",   [&]{ listConditions(tokens, result); },
256
		"probe",             [&]{ probe(tokens, result); },
257
		"symbols",           [&]{ symbols(tokens, result); },
258
		"trace",             [&]{ auto& d = debugger(); d.tracer.execute(d, tokens, result, time); });
259
}
260
261
void Debugger::Cmd::list(TclObject& result)
262
{
263
	result.addListElements(std::views::keys(debugger().debuggables));
264
}
265
266
void Debugger::Cmd::desc(std::span<const TclObject> tokens, TclObject& result)
267
{
268
	checkNumArgs(tokens, 3, "debuggable");
269
	const Debuggable& device = debugger().getDebuggable(tokens[2].getString());
270
	result = device.getDescription();
271
}
272
273
void Debugger::Cmd::size(std::span<const TclObject> tokens, TclObject& result)
274
{
275
	checkNumArgs(tokens, 3, "debuggable");
276
	const Debuggable& device = debugger().getDebuggable(tokens[2].getString());
277
	result = device.getSize();
278
}
279
280
void Debugger::Cmd::read(std::span<const TclObject> tokens, TclObject& result)
281
{
282
	checkNumArgs(tokens, 4, Prefix{2}, "debuggable address");
283
	Debuggable& device = debugger().getDebuggable(tokens[2].getString());
284
	unsigned addr = tokens[3].getInt(getInterpreter());
285
	if (addr >= device.getSize()) {
286
		throw CommandException("Invalid address");
287
	}
288
	result = device.read(addr);
289
}
290
291
void Debugger::Cmd::readBlock(std::span<const TclObject> tokens, TclObject& result)
292
{
293
	checkNumArgs(tokens, 5, Prefix{2}, "debuggable address size");
294
	auto& interp = getInterpreter();
295
	Debuggable& device = debugger().getDebuggable(tokens[2].getString());
296
	unsigned devSize = device.getSize();
297
	unsigned addr = tokens[3].getInt(interp);
298
	if (addr >= devSize) {
299
		throw CommandException("Invalid address");
300
	}
301
	unsigned num = tokens[4].getInt(interp);
302
	if (num > (devSize - addr)) {
303
		throw CommandException("Invalid size");
304
	}
305
306
	MemBuffer<byte> buf(num);
307
	device.readBlock(addr, buf);
308
	result = std::span{buf}; // makes a copy
309
}
310
311
void Debugger::Cmd::write(std::span<const TclObject> tokens, TclObject& /*result*/)
312
{
313
	checkNumArgs(tokens, 5, Prefix{2}, "debuggable address value");
314
	auto& interp = getInterpreter();
315
	Debuggable& device = debugger().getDebuggable(tokens[2].getString());
316
	unsigned addr = tokens[3].getInt(interp);
317
	if (addr >= device.getSize()) {
318
		throw CommandException("Invalid address");
319
	}
320
	unsigned value = tokens[4].getInt(interp);
321
	if (value >= 256) {
322
		throw CommandException("Invalid value");
323
	}
324
325
	device.write(addr, narrow_cast<byte>(value));
326
}
327
328
void Debugger::Cmd::writeBlock(std::span<const TclObject> tokens, TclObject& /*result*/)
329
{
330
	checkNumArgs(tokens, 5, Prefix{2}, "debuggable address values");
331
	Debuggable& device = debugger().getDebuggable(tokens[2].getString());
332
	unsigned devSize = device.getSize();
333
	unsigned addr = tokens[3].getInt(getInterpreter());
334
	if (addr >= devSize) {
335
		throw CommandException("Invalid address");
336
	}
337
	auto buf = tokens[4].getBinary(getInterpreter());
338
	if ((buf.size() + addr) > devSize) {
339
		throw CommandException("Invalid size");
340
	}
341
342
	for (auto i : xrange(buf.size())) {
343
		device.write(unsigned(addr + i), buf[i]);
344
	}
345
}
346
347
static constexpr char toHex(byte x)
348
{
349
	return narrow<char>((x < 10) ? (x + '0') : (x - 10 + 'A'));
350
}
351
static constexpr void toHex(byte x, std::span<char, 3> buf)
352
{
353
	buf[0] = toHex(x / 16);
354
	buf[1] = toHex(x & 15);
355
}
356
357
void Debugger::Cmd::disasm(std::span<const TclObject> tokens, TclObject& result, EmuTime time) const
358
{
359
	uint16_t address = (tokens.size() < 3) ? debugger().cpu->getRegisters().getPC()
360
	                                       : uint16_t(tokens[2].getInt(getInterpreter()));
361
	std::array<byte, 4> outBuf;
362
	std::string dasmOutput;
363
	unsigned len = dasm(debugger().motherBoard.getCPUInterface(), address, outBuf, dasmOutput, time);
364
	dasmOutput.resize(19, ' ');
365
	result.addListElement(dasmOutput);
366
	std::array<char, 3> tmp; tmp[2] = 0;
367
	for (auto i : xrange(len)) {
368
		toHex(outBuf[i], tmp);
369
		result.addListElement(tmp.data());
370
	}
371
}
372
373
void Debugger::Cmd::disasmBlob(std::span<const TclObject> tokens, TclObject& result) const
374
{
375
	checkNumArgs(tokens, Between{4, 5}, Prefix{2}, "value addr ?function?");
376
	std::span<const uint8_t> bin = tokens[2].getBinary(getInterpreter());
377
	auto len = instructionLength(bin);
378
	if (!len || *len > bin.size()) {
379
		throw CommandException("Blob does not contain a complete Z80 instruction");
380
	}
381
	std::string dasmOutput;
382
	unsigned addr = tokens[3].getInt(getInterpreter());
383
	dasm(bin.subspan(0, *len), uint16_t(addr), dasmOutput,
384
		[&](std::string& out, uint16_t a) {
385
			zstring_view cmdRes;
386
			if (tokens.size() > 4) {
387
				auto command = makeTclList(tokens[4], a);
388
				cmdRes = command.executeCommand(getInterpreter()).getString();
389
			}
390
			if (!cmdRes.empty()) {
391
				strAppend(out, cmdRes);
392
			} else {
393
				appendAddrAsHex(out, a);
394
			}
395
		});
396
	dasmOutput.resize(19, ' ');
397
	result.addListElement(dasmOutput);
398
	result.addListElement(*len);
399
}
400
401
void Debugger::Cmd::breakPoint(std::span<const TclObject> tokens, TclObject& result)
402
{
403
	checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
404
	executeSubCommand(tokens[2].getString(),
405
		"list",      [&]{ breakPointList(tokens, result); },
406
		"create",    [&]{ breakPointCreate(tokens, result); },
407
		"configure", [&]{ breakPointConfigure(tokens, result); },
408
		"remove",    [&]{ breakPointRemove(tokens, result); });
409
}
410
411
void Debugger::Cmd::watchPoint(std::span<const TclObject> tokens, TclObject& result)
412
{
413
	checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
414
	executeSubCommand(tokens[2].getString(),
415
		"list",      [&]{ watchPointList(tokens, result); },
416
		"create",    [&]{ watchPointCreate(tokens, result); },
417
		"configure", [&]{ watchPointConfigure(tokens, result); },
418
		"remove",    [&]{ watchPointRemove(tokens, result); });
419
}
420
421
void Debugger::Cmd::watchExpr(std::span<const TclObject> tokens, TclObject& result)
422
{
423
	checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
424
	executeSubCommand(tokens[2].getString(),
425
		"list",      [&]{ watchExprList(tokens, result); },
426
		"create",    [&]{ watchExprCreate(tokens, result); },
427
		"configure", [&]{ watchExprConfigure(tokens, result); },
428
		"remove",    [&]{ watchExprRemove(tokens, result); });
429
}
430
431
void Debugger::Cmd::condition(std::span<const TclObject> tokens, TclObject& result)
432
{
433
	checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
434
	executeSubCommand(tokens[2].getString(),
435
		"list",      [&]{ conditionList(tokens, result); },
436
		"create",    [&]{ conditionCreate(tokens, result); },
437
		"configure", [&]{ conditionConfigure(tokens, result); },
438
		"remove",    [&]{ conditionRemove(tokens, result); });
439
}
440
441
BreakPoint* Debugger::Cmd::lookupBreakPoint(std::string_view str)
442
{
443
	if (!str.starts_with(BreakPoint::prefix)) return nullptr;
444
	if (auto id = StringOp::stringToBase<10, unsigned>(str.substr(BreakPoint::prefix.size()))) {
445
		auto& breakPoints = MSXCPUInterface::getBreakPoints();
446
		if (auto it = std::ranges::find(breakPoints, id, &BreakPoint::getId);
447
		    it != std::end(breakPoints)) {
448
			return std::to_address(it);
449
		}
450
	}
451
	return nullptr;
452
}
453
454
std::shared_ptr<WatchPoint> Debugger::Cmd::lookupWatchPoint(std::string_view str)
455
{
456
	if (!str.starts_with(WatchPoint::prefix)) return {};
457
	if (auto id = StringOp::stringToBase<10, unsigned>(str.substr(WatchPoint::prefix.size()))) {
458
		auto& interface = debugger().motherBoard.getCPUInterface();
459
		auto& watchPoints = interface.getWatchPoints();
460
		if (auto it = std::ranges::find(watchPoints, id, &WatchPoint::getId);
461
		    it != std::end(watchPoints)) {
462
			return *it;
463
		}
464
	}
465
	return {};
466
}
467
468
static auto findWatchExpr(std::vector<ImGuiWatchExpr::WatchExpr>& watchExprs, std::string_view str)
469
{
470
	if (!str.starts_with(ImGuiWatchExpr::WatchExpr::prefix)) return std::end(watchExprs);
471
	if (auto id = StringOp::stringToBase<10, unsigned>(str.substr(ImGuiWatchExpr::WatchExpr::prefix.size()))) {
472
		return std::ranges::find(watchExprs, id, &ImGuiWatchExpr::WatchExpr::getId);
473
	}
474
	return std::end(watchExprs);
475
}
476
477
ImGuiWatchExpr::WatchExpr* Debugger::Cmd::lookupWatchExpr(std::string_view str)
478
{
479
	auto& watchExprs = debugger().getWatchExprs();
480
	auto it = findWatchExpr(watchExprs, str);
481
	return (it != std::end(watchExprs)) ? std::to_address(it) : nullptr;
482
}
483
484
DebugCondition* Debugger::Cmd::lookupCondition(std::string_view str)
485
{
486
	if (!str.starts_with(DebugCondition::prefix)) return {};
487
	if (auto id = StringOp::stringToBase<10, unsigned>(str.substr(DebugCondition::prefix.size()))) {
488
		auto& conditions = MSXCPUInterface::getConditions();
489
		if (auto it = std::ranges::find(conditions, id, &DebugCondition::getId);
490
		    it != std::end(conditions)) {
491
			return std::to_address(it);
492
		}
493
	}
494
	return {};
495
}
496
497
static auto formatWpAddr(const WatchPoint& wp)
498
{
499
	TclObject result;
500
	result.addListElement(wp.getBeginAddressString());
501
	if (auto end = wp.getEndAddressString(); !end.getString().empty()) {
502
		result.addListElement(end);
503
	}
504
	return result;
505
}
506
507
void Debugger::Cmd::breakPointList(std::span<const TclObject> /*tokens*/, TclObject& result)
508
{
509
	for (const auto& bp : MSXCPUInterface::getBreakPoints()) {
510
		TclObject dict = makeTclDict(
511
			TclObject("-address"), bp.getAddressString(),
512
			TclObject("-condition"), bp.getCondition(),
513
			TclObject("-command"), bp.getCommand(),
514
			TclObject("-enabled"), bp.isEnabled(),
515
			TclObject("-once"), bp.onlyOnce());
516
		result.addDictKeyValue(bp.getIdStr(), std::move(dict));
517
	}
518
}
519
520
void Debugger::Cmd::watchPointList(std::span<const TclObject> /*tokens*/, TclObject& result)
521
{
522
	auto& interface = debugger().motherBoard.getCPUInterface();
523
	for (const auto& wp : interface.getWatchPoints()) {
524
		TclObject dict = makeTclDict(
525
			TclObject("-type"), WatchPoint::format(wp->getType()),
526
			TclObject("-address"), formatWpAddr(*wp),
527
			TclObject("-condition"), wp->getCondition(),
528
			TclObject("-command"), wp->getCommand(),
529
			TclObject("-enabled"), wp->isEnabled(),
530
			TclObject("-once"), wp->onlyOnce());
531
		result.addDictKeyValue(wp->getIdStr(), std::move(dict));
532
	}
533
}
534
535
void Debugger::Cmd::watchExprList(std::span<const TclObject> /*tokens*/, TclObject& result)
536
{
537
	for (const auto& we : debugger().getWatchExprs()) {
538
		TclObject dict = makeTclDict(
539
			TclObject("-description"), we.getDescription(),
540
			TclObject("-expression"), we.getExpression(),
541
			TclObject("-format"), we.getFormat());
542
		result.addDictKeyValue(we.getIdStr(), std::move(dict));
543
	}
544
}
545
546
void Debugger::Cmd::conditionList(std::span<const TclObject> /*tokens*/, TclObject& result)
547
{
548
	for (const auto& cond : MSXCPUInterface::getConditions()) {
549
		TclObject dict = makeTclDict(
550
			TclObject("-condition"), cond.getCondition(),
551
			TclObject("-command"), cond.getCommand(),
552
			TclObject("-enabled"), cond.isEnabled(),
553
			TclObject("-once"), cond.onlyOnce());
554
		result.addDictKeyValue(cond.getIdStr(), std::move(dict));
555
	}
556
}
557
558
void Debugger::Cmd::parseCreateBreakPoint(BreakPoint& bp, std::span<const TclObject> tokens)
559
{
560
	std::array info = {
561
		funcArg("-address", [&](Interpreter& interp, const TclObject& arg) {
562
			bp.setAddress(interp, arg);
563
		}),
564
		funcArg("-condition", [&](Interpreter& /*interp*/, const TclObject& arg) {
565
			bp.setCondition(arg);
566
		}),
567
		funcArg("-command", [&](Interpreter& /*interp*/, const TclObject& arg) {
568
			bp.setCommand(arg);
569
		}),
570
		funcArg("-enabled", [&](Interpreter& interp, const TclObject& arg) {
571
			bp.setEnabled(interp, arg);
572
		}),
573
		funcArg("-once", [&](Interpreter& interp, const TclObject& arg) {
574
			bp.setOnce(interp, arg);
575
		}),
576
	};
577
	auto arguments = parseTclArgs(getInterpreter(), tokens, info);
578
	if (!arguments.empty()) throw SyntaxError();
579
}
580
581
void Debugger::Cmd::parseCreateWatchPoint(WatchPoint& wp, std::span<const TclObject> tokens)
582
{
583
	std::array info = {
584
		funcArg("-type", [&](Interpreter& interp, const TclObject& arg) {
585
			wp.setType(interp, arg);
586
		}),
587
		funcArg("-address", [&](Interpreter& interp, const TclObject& arg) {
588
			wp.setAddress(interp, arg);
589
		}),
590
		funcArg("-condition", [&](Interpreter& /*interp*/, const TclObject& arg) {
591
			wp.setCondition(arg);
592
		}),
593
		funcArg("-command", [&](Interpreter& /*interp*/, const TclObject& arg) {
594
			wp.setCommand(arg);
595
		}),
596
		funcArg("-enabled", [&](Interpreter& interp, const TclObject& arg) {
597
			wp.setEnabled(interp, arg);
598
		}),
599
		funcArg("-once", [&](Interpreter& interp, const TclObject& arg) {
600
			wp.setOnce(interp, arg);
601
		}),
602
	};
603
	auto arguments = parseTclArgs(getInterpreter(), tokens, info);
604
	if (!arguments.empty()) throw SyntaxError();
605
}
606
607
void Debugger::Cmd::parseCreateWatchExpr(ImGuiWatchExpr::WatchExpr& we, std::span<const TclObject> tokens)
608
{
609
	std::array info = {
610
		funcArg("-description", [&](Interpreter& /*interp*/, const TclObject& arg) {
611
			we.setDescription(arg);
612
		}),
613
		funcArg("-expression", [&](Interpreter& /*interp*/, const TclObject& arg) {
614
			we.setExpression(arg);
615
		}),
616
		funcArg("-format", [&](Interpreter& /*interp*/, const TclObject& arg) {
617
			we.setFormat(arg);
618
		}),
619
	};
620
	auto arguments = parseTclArgs(getInterpreter(), tokens, info);
621
	if (!arguments.empty()) throw SyntaxError();
622
}
623
624
void Debugger::Cmd::parseCreateCondition(DebugCondition& cond, std::span<const TclObject> tokens)
625
{
626
	std::array info = {
627
		funcArg("-condition", [&](Interpreter& /*interp*/, const TclObject& arg) {
628
			cond.setCondition(arg);
629
		}),
630
		funcArg("-command", [&](Interpreter& /*interp*/, const TclObject& arg) {
631
			cond.setCommand(arg);
632
		}),
633
		funcArg("-enabled", [&](Interpreter& interp, const TclObject& arg) {
634
			cond.setEnabled(interp, arg);
635
		}),
636
		funcArg("-once", [&](Interpreter& interp, const TclObject& arg) {
637
			cond.setOnce(interp, arg);
638
		}),
639
	};
640
	auto arguments = parseTclArgs(getInterpreter(), tokens, info);
641
	if (!arguments.empty()) throw SyntaxError();
642
}
643
644
void Debugger::Cmd::breakPointCreate(std::span<const TclObject> tokens, TclObject& result)
645
{
646
	BreakPoint bp;
647
	parseCreateBreakPoint(bp, tokens.subspan(3));
648
	result = bp.getIdStr();
649
	debugger().motherBoard.getCPUInterface().insertBreakPoint(std::move(bp));
650
}
651
652
void Debugger::Cmd::watchPointCreate(std::span<const TclObject> tokens, TclObject& result)
653
{
654
	auto wp = std::make_shared<WatchPoint>();
655
	parseCreateWatchPoint(*wp, tokens.subspan(3));
656
	result = wp->getIdStr();
657
	debugger().motherBoard.getCPUInterface().setWatchPoint(std::move(wp));
658
}
659
660
void Debugger::Cmd::watchExprCreate(std::span<const TclObject> tokens, TclObject& result)
661
{
662
	ImGuiWatchExpr::WatchExpr we;
663
	parseCreateWatchExpr(we, tokens.subspan(3));
664
	result = we.getIdStr();
665
	debugger().getWatchExprs().push_back(std::move(we));
666
}
667
668
void Debugger::Cmd::conditionCreate(std::span<const TclObject> tokens, TclObject& result)
669
{
670
	DebugCondition cond;
671
	parseCreateCondition(cond, tokens.subspan(3));
672
	result = cond.getIdStr();
673
	debugger().motherBoard.getCPUInterface().setCondition(std::move(cond));
674
}
675
676
void Debugger::Cmd::breakPointConfigure(std::span<const TclObject> tokens, TclObject& /*result*/)
677
{
678
	checkNumArgs(tokens, AtLeast{4}, "id ?arg ...?");
679
	auto id = tokens[3].getString();
680
	auto* bp = lookupBreakPoint(id);
681
	if (!bp) {
682
		throw CommandException("No such breakpoint: ", id);
683
	}
684
	// No need to get a scoped change breakpoint.
685
	parseCreateBreakPoint(*bp, tokens.subspan(4));
686
}
687
688
void Debugger::Cmd::watchPointConfigure(std::span<const TclObject> tokens, TclObject& /*result*/)
689
{
690
	checkNumArgs(tokens, AtLeast{4}, "id ?arg ...?");
691
	auto id = tokens[3].getString();
692
	auto wp = lookupWatchPoint(id);
693
	if (!wp) {
694
		throw CommandException("No such watchpoint: ", id);
695
	}
696
	auto ch = debugger().motherBoard.getCPUInterface().getScopedChangeWatchpoint(wp);
697
	parseCreateWatchPoint(*wp, tokens.subspan(4));
698
}
699
700
void Debugger::Cmd::watchExprConfigure(std::span<const TclObject> tokens, TclObject& /*result*/)
701
{
702
	checkNumArgs(tokens, AtLeast{4}, "id ?arg ...?");
703
	auto id = tokens[3].getString();
704
	auto* we = lookupWatchExpr(id);
705
	if (!we) {
706
		throw CommandException("No such watchexpr: ", id);
707
	}
708
	parseCreateWatchExpr(*we, tokens.subspan(4));
709
}
710
711
void Debugger::Cmd::conditionConfigure(std::span<const TclObject> tokens, TclObject& /*result*/)
712
{
713
	checkNumArgs(tokens, AtLeast{4}, "id ?arg ...?");
714
	auto id = tokens[3].getString();
715
	auto* cond = lookupCondition(id);
716
	if (!cond) {
717
		throw CommandException("No such condition: ", id);
718
	}
719
	// No need to get a scoped change condition.
720
	parseCreateCondition(*cond, tokens.subspan(4));
721
}
722
723
void Debugger::Cmd::breakPointRemove(std::span<const TclObject> tokens, TclObject& /*result*/)
724
{
725
	checkNumArgs(tokens, 4, "id");
726
	auto id = tokens[3].getString();
727
	auto* bp = lookupBreakPoint(id);
728
	if (!bp) {
729
		throw CommandException("No such breakpoint: ", id);
730
	}
731
	debugger().motherBoard.getCPUInterface().removeBreakPoint(*bp);
732
}
733
734
void Debugger::Cmd::watchPointRemove(std::span<const TclObject> tokens, TclObject& /*result*/)
735
{
736
	checkNumArgs(tokens, 4, "id");
737
	auto id = tokens[3].getString();
738
	auto wp = lookupWatchPoint(id);
739
	if (!wp) {
740
		throw CommandException("No such watchpoint: ", id);
741
	}
742
	debugger().motherBoard.getCPUInterface().removeWatchPoint(wp);
743
}
744
745
void Debugger::Cmd::watchExprRemove(std::span<const TclObject> tokens, TclObject& /*result*/)
746
{
747
	checkNumArgs(tokens, 4, "id");
748
	auto id = tokens[3].getString();
749
750
	auto& watchExprs = debugger().getWatchExprs();
751
	auto it = findWatchExpr(watchExprs, id);
752
	if (it == std::end(watchExprs)) {
753
		throw CommandException("No such watchexpr: ", id);
754
	}
755
756
	watchExprs.erase(it);
757
}
758
759
void Debugger::Cmd::conditionRemove(std::span<const TclObject> tokens, TclObject& /*result*/)
760
{
761
	checkNumArgs(tokens, 4, "id");
762
	auto id = tokens[3].getString();
763
	auto* cond = lookupCondition(id);
764
	if (!cond) {
765
		throw CommandException("No such condition: ", id);
766
	}
767
	debugger().motherBoard.getCPUInterface().removeCondition(*cond);
768
}
769
770
void Debugger::Cmd::setBreakPoint(std::span<const TclObject> tokens, TclObject& result)
771
{
772
	checkNumArgs(tokens, AtLeast{3}, "address ?-once? ?condition? ?command?");
773
774
	bool once = false;
775
	std::array info = {flagArg("-once", once)};
776
777
	auto& interp = getInterpreter();
778
	auto arguments = parseTclArgs(interp, tokens.subspan(2), info);
779
	if ((arguments.size() < 1) || (arguments.size() > 3)) {
780
		throw SyntaxError();
781
	}
782
783
	BreakPoint bp;
784
	bp.setOnce(once);
785
786
	switch (arguments.size()) {
787
	case 3: // command
788
		bp.setCommand(arguments[2]);
789
		[[fallthrough]];
790
	case 2: // condition
791
		bp.setCondition(arguments[1]);
792
		[[fallthrough]];
793
	case 1: // address
794
		bp.setAddress(interp, arguments[0]);
795
		break;
796
	}
797
798
	result = bp.getIdStr();
799
	debugger().motherBoard.getCPUInterface().insertBreakPoint(std::move(bp));
800
}
801
802
void Debugger::Cmd::removeBreakPoint(
803
	std::span<const TclObject> tokens, TclObject& /*result*/)
804
{
805
	checkNumArgs(tokens, 3, "id|address");
806
	auto& interface = debugger().motherBoard.getCPUInterface();
807
	auto& breakPoints = MSXCPUInterface::getBreakPoints();
808
809
	std::string_view tmp = tokens[2].getString();
810
	if (tmp.starts_with(BreakPoint::prefix)) {
811
		// remove by id
812
		if (auto id = StringOp::stringToBase<10, unsigned>(tmp.substr(BreakPoint::prefix.size()))) {
813
			if (auto it = std::ranges::find(breakPoints, id, &BreakPoint::getId);
814
			    it != std::end(breakPoints)) {
815
				interface.removeBreakPoint(*it);
816
				return;
817
			}
818
		}
819
		throw CommandException("No such breakpoint: ", tmp);
820
	} else {
821
		// remove by addr, only works for unconditional bp
822
		uint16_t addr = getAddress(getInterpreter(), tokens[2]);
823
		auto it = std::ranges::find_if(breakPoints, [&](auto& bp) {
824
			return (bp.getAddress() == addr) &&
825
			       bp.getCondition().getString().empty();
826
		});
827
		if (it == breakPoints.end()) {
828
			throw CommandException(
829
				"No (unconditional) breakpoint at address: ", tmp);
830
		}
831
		interface.removeBreakPoint(*it);
832
	}
833
}
834
835
void Debugger::Cmd::listBreakPoints(std::span<const TclObject> /*tokens*/, TclObject& result) const
836
{
837
	std::string res;
838
	for (const auto& bp : MSXCPUInterface::getBreakPoints()) {
839
		TclObject line = makeTclList(
840
			bp.getIdStr(), bp.getAddressString(),
841
			bp.getCondition(), bp.getCommand());
842
		strAppend(res, line.getString(), '\n');
843
	}
844
	result = res;
845
}
846
847
848
void Debugger::Cmd::setWatchPoint(std::span<const TclObject> tokens, TclObject& result)
849
{
850
	checkNumArgs(tokens, AtLeast{4}, Prefix{2}, "type address ?-once? ?condition? ?command?");
851
852
	bool once = false;
853
	std::array info = {flagArg("-once", once)};
854
855
	auto& interp = getInterpreter();
856
	auto arguments = parseTclArgs(interp, tokens.subspan(2), info);
857
	if ((arguments.size() < 2) || (arguments.size() > 4)) {
858
		throw SyntaxError();
859
	}
860
861
	auto wp = std::make_shared<WatchPoint>();
862
	wp->setOnce(once);
863
864
	switch (arguments.size()) {
865
	case 4: // command
866
		wp->setCommand(arguments[3]);
867
		[[fallthrough]];
868
	case 3: // condition
869
		wp->setCondition(arguments[2]);
870
		[[fallthrough]];
871
	case 2: // address + type
872
		wp->setType(interp, arguments[0]);
873
		wp->setAddress(interp, arguments[1]);
874
		break;
875
	default:
876
		UNREACHABLE;
877
	}
878
879
	result = wp->getIdStr();
880
	debugger().motherBoard.getCPUInterface().setWatchPoint(std::move(wp));
881
}
882
883
void Debugger::Cmd::removeWatchPoint(
884
	std::span<const TclObject> tokens, TclObject& /*result*/)
885
{
886
	checkNumArgs(tokens, 3, "id");
887
	std::string_view tmp = tokens[2].getString();
888
	if (tmp.starts_with(WatchPoint::prefix)) {
889
		// remove by id
890
		if (auto id = StringOp::stringToBase<10, unsigned>(tmp.substr(WatchPoint::prefix.size()))) {
891
			auto& interface = debugger().motherBoard.getCPUInterface();
892
			for (auto& wp : interface.getWatchPoints()) {
893
				if (wp->getId() == *id) {
894
					interface.removeWatchPoint(wp);
895
					return;
896
				}
897
			}
898
		}
899
	}
900
	throw CommandException("No such watchpoint: ", tmp);
901
}
902
903
void Debugger::Cmd::listWatchPoints(
904
	std::span<const TclObject> /*tokens*/, TclObject& result)
905
{
906
	std::string res;
907
	const auto& interface = debugger().motherBoard.getCPUInterface();
908
	for (const auto& wp : interface.getWatchPoints()) {
909
		auto address = makeTclList(wp->getBeginAddressString());
910
		if (auto end = wp->getEndAddressString(); !end.getString().empty()) {
911
			address.addListElement(end);
912
		}
913
		TclObject line = makeTclList(
914
			wp->getIdStr(),
915
			WatchPoint::format(wp->getType()),
916
			address,
917
			wp->getCondition(),
918
			wp->getCommand());
919
		strAppend(res, line.getString(), '\n');
920
	}
921
	result = res;
922
}
923
924
925
void Debugger::Cmd::setCondition(std::span<const TclObject> tokens, TclObject& result)
926
{
927
	checkNumArgs(tokens, AtLeast{3}, "condition ?-once? ?command?");
928
929
	bool once = false;
930
	std::array info = {flagArg("-once", once)};
931
932
	auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(2), info);
933
	if ((arguments.size() < 1) || (arguments.size() > 2)) {
934
		throw SyntaxError();
935
	}
936
937
	DebugCondition dc;
938
	dc.setOnce(once);
939
940
	switch (arguments.size()) {
941
	case 2: // command
942
		dc.setCommand(arguments[1]);
943
		[[fallthrough]];
944
	case 1:
945
		dc.setCondition(arguments[0]);
946
		break;
947
	}
948
949
	result = dc.getIdStr();
950
	debugger().motherBoard.getCPUInterface().setCondition(std::move(dc));
951
}
952
953
void Debugger::Cmd::removeCondition(
954
	std::span<const TclObject> tokens, TclObject& /*result*/)
955
{
956
	checkNumArgs(tokens, 3, "id");
957
958
	std::string_view tmp = tokens[2].getString();
959
	if (tmp.starts_with(DebugCondition::prefix)) {
960
		// remove by id
961
		if (auto id = StringOp::stringToBase<10, unsigned>(tmp.substr(DebugCondition::prefix.size()))) {
962
			for (auto& c : MSXCPUInterface::getConditions()) {
963
				if (c.getId() == *id) {
964
					auto& interface = debugger().motherBoard.getCPUInterface();
965
					interface.removeCondition(c);
966
					return;
967
				}
968
			}
969
		}
970
	}
971
	throw CommandException("No such condition: ", tmp);
972
}
973
974
void Debugger::Cmd::listConditions(std::span<const TclObject> /*tokens*/, TclObject& result) const
975
{
976
	std::string res;
977
	for (const auto& c : MSXCPUInterface::getConditions()) {
978
		TclObject line = makeTclList(c.getIdStr(),
979
		                             c.getCondition(),
980
		                             c.getCommand());
981
		strAppend(res, line.getString(), '\n');
982
	}
983
	result = res;
984
}
985
986
987
void Debugger::Cmd::probe(std::span<const TclObject> tokens, TclObject& result)
988
{
989
	checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
990
	executeSubCommand(tokens[2].getString(),
991
		"list",      [&]{ probeList(tokens, result); },
992
		"desc",      [&]{ probeDesc(tokens, result); },
993
		"read",      [&]{ probeRead(tokens, result); },
994
		"set_bp",    [&]{ probeSetBreakPoint(tokens, result); },
995
		"remove_bp", [&]{ probeRemoveBreakPoint(tokens, result); },
996
		"list_bp",   [&]{ probeListBreakPoints(tokens, result); });
997
}
998
void Debugger::Cmd::probeList(std::span<const TclObject> /*tokens*/, TclObject& result)
999
{
1000
	result.addListElements(std::views::transform(debugger().probes, &ProbeBase::getName));
1001
}
1002
void Debugger::Cmd::probeDesc(std::span<const TclObject> tokens, TclObject& result)
1003
{
1004
	checkNumArgs(tokens, 4, "probe");
1005
	result = debugger().getProbe(tokens[3].getString()).getDescription();
1006
}
1007
void Debugger::Cmd::probeRead(std::span<const TclObject> tokens, TclObject& result)
1008
{
1009
	checkNumArgs(tokens, 4, "probe");
1010
	result = debugger().getProbe(tokens[3].getString()).getValue();
1011
}
1012
void Debugger::Cmd::probeSetBreakPoint(
1013
	std::span<const TclObject> tokens, TclObject& result)
1014
{
1015
	checkNumArgs(tokens, AtLeast{4}, "probe ?-once? ?condition? ?command?");
1016
	TclObject command("debug break");
1017
	TclObject condition;
1018
	ProbeBase* p;
1019
	bool once = false;
1020
1021
	std::array info = {flagArg("-once", once)};
1022
	auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(3), info);
1023
	if ((arguments.size() < 1) || (arguments.size() > 3)) {
1024
		throw SyntaxError();
1025
	}
1026
1027
	switch (arguments.size()) {
1028
	case 3: // command
1029
		command = arguments[2];
1030
		[[fallthrough]];
1031
	case 2: // condition
1032
		condition = arguments[1];
1033
		[[fallthrough]];
1034
	case 1: { // probe
1035
		p = &debugger().getProbe(arguments[0].getString());
1036
		break;
1037
	}
1038
	default:
1039
		UNREACHABLE;
1040
	}
1041
1042
	result = debugger().insertProbeBreakPoint(command, condition, *p, once);
1043
}
1044
void Debugger::Cmd::probeRemoveBreakPoint(
1045
	std::span<const TclObject> tokens, TclObject& /*result*/)
1046
{
1047
	checkNumArgs(tokens, 4, "id");
1048
	debugger().removeProbeBreakPoint(tokens[3].getString());
1049
}
1050
void Debugger::Cmd::probeListBreakPoints(
1051
	std::span<const TclObject> /*tokens*/, TclObject& result)
1052
{
1053
	std::string res;
1054
	for (const auto& p : debugger().probeBreakPoints) {
1055
		TclObject line = makeTclList(p->getIdStr(),
1056
		                             p->getProbe().getName(),
1057
		                             p->getCondition(),
1058
		                             p->getCommand());
1059
		strAppend(res, line.getString(), '\n');
1060
	}
1061
	result = res;
1062
}
1063
1064
SymbolManager& Debugger::Cmd::getSymbolManager()
1065
{
1066
	return debugger().getMotherBoard().getReactor().getSymbolManager();
1067
}
1068
void Debugger::Cmd::symbols(std::span<const TclObject> tokens, TclObject& result)
1069
{
1070
	checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
1071
	executeSubCommand(tokens[2].getString(),
1072
		"types",  [&]{ symbolsTypes(tokens, result); },
1073
		"load",   [&]{ symbolsLoad(tokens, result); },
1074
		"remove", [&]{ symbolsRemove(tokens, result); },
1075
		"files",  [&]{ symbolsFiles(tokens, result); },
1076
		"lookup", [&]{ symbolsLookup(tokens, result); });
1077
}
1078
void Debugger::Cmd::symbolsTypes(std::span<const TclObject> tokens, TclObject& result) const
1079
{
1080
	checkNumArgs(tokens, 3, "");
1081
	for (auto type = SymbolFile::Type::FIRST;
1082
	     type < SymbolFile::Type::LAST;
1083
	     type = static_cast<SymbolFile::Type>(static_cast<int>(type) + 1)) {
1084
		result.addListElement(SymbolFile::toString(type));
1085
	}
1086
}
1087
void Debugger::Cmd::symbolsLoad(std::span<const TclObject> tokens, TclObject& /*result*/)
1088
{
1089
	checkNumArgs(tokens, Between{4, 5}, "filename ?type?");
1090
	auto filename = tokens[3].getString();
1091
	auto type = [&]{
1092
		if (tokens.size() < 5) return SymbolFile::Type::AUTO_DETECT;
1093
		auto str = tokens[4].getString();
1094
		auto t = SymbolFile::parseType(str);
1095
		if (!t) throw CommandException("Invalid symbol file type: ", str);
1096
		return *t;
1097
	}();
1098
	try {
1099
		getSymbolManager().reloadFile(filename, SymbolManager::LoadEmpty::ALLOWED, type, {}, {});
1100
	} catch (MSXException& e) {
1101
		throw CommandException("Couldn't load symbol file '", filename, "': ", e.getMessage());
1102
	}
1103
}
1104
void Debugger::Cmd::symbolsRemove(std::span<const TclObject> tokens, TclObject& /*result*/)
1105
{
1106
	checkNumArgs(tokens, 4, "filename");
1107
	auto filename = tokens[3].getString();
1108
	getSymbolManager().removeFile(filename);
1109
}
1110
void Debugger::Cmd::symbolsFiles(std::span<const TclObject> tokens, TclObject& result)
1111
{
1112
	checkNumArgs(tokens, 3, "");
1113
	for (const auto& file : getSymbolManager().getFiles()) {
1114
		result.addListElement(TclObject(TclObject::MakeDictTag{},
1115
			"filename", file.filename,
1116
			"type", SymbolFile::toString(file.type)));
1117
	}
1118
}
1119
void Debugger::Cmd::symbolsLookup(std::span<const TclObject> tokens, TclObject& result)
1120
{
1121
	std::string_view filename;
1122
	std::string_view name;
1123
	std::optional<int> value;
1124
	std::array info = {valueArg("-filename", filename),
1125
	                   valueArg("-name", name),
1126
	                   valueArg("-value", value)};
1127
	auto args = parseTclArgs(getInterpreter(), tokens.subspan(3), info);
1128
	if (!args.empty()) throw SyntaxError();
1129
	if (!filename.empty() && !name.empty() && value) throw SyntaxError();
1130
1131
	for (const auto& file : getSymbolManager().getFiles()) {
1132
		if (!filename.empty() && (file.filename != filename)) continue;
1133
		for (const auto& sym : file.symbols) {
1134
			if (!name.empty() && (name != sym.name)) continue;
1135
			if (value && (sym.value != *value)) continue;
1136
1137
			TclObject elem;
1138
			if (filename.empty()) elem.addDictKeyValue("filename", file.filename);
1139
			if (name.empty()) elem.addDictKeyValue("name", sym.name);
1140
			if (!value) elem.addDictKeyValue("value", sym.value);
1141
			result.addListElement(std::move(elem));
1142
		}
1143
	}
1144
}
1145
1146
// A tiny structural string type, because we're not using C++26 yet that let's you constexpr + std::string
1147
template<size_t N>
1148
struct FixedStr {
1149
	char buf[N]{};
1150
	constexpr FixedStr() = default;
1151
	constexpr FixedStr(const char(&str)[N]) { std::copy_n(str, N, buf); }
1152
	constexpr operator std::string_view() const { return {buf, N - 1}; }
1153
	// Allows you to use + natively on your constexpr variables!
1154
	template<size_t M>
1155
	constexpr auto operator+(FixedStr<M> rhs) const {
1156
		FixedStr<N + M - 1> out{};
1157
		std::copy_n(buf, N - 1, out.buf);
1158
		std::copy_n(rhs.buf, M, out.buf + N - 1);
1159
		return out;
1160
	}
1161
};
1162
1163
// 1. Handles: "literal" + FixedStr
1164
template<size_t N, size_t M>
1165
constexpr auto operator+(const char(&lhs)[N], FixedStr<M> rhs) { return FixedStr<N>(lhs) + rhs; }
1166
1167
// 2. Handles: FixedStr + "literal"
1168
template<size_t N, size_t M>
1169
constexpr auto operator+(FixedStr<N> lhs, const char(&rhs)[M]) { return lhs + FixedStr<M>(rhs); }
1170
1171
std::string Debugger::Cmd::help(std::span<const TclObject> tokens) const
1172
{
1173
	constexpr auto generalHelp =
1174
		"debug <subcommand> [<arguments>]\n"
1175
		"  Possible subcommands are:\n"
1176
		"    list         returns a list of all debuggables\n"
1177
		"    desc         returns a description of this debuggable\n"
1178
		"    size         returns the size of this debuggable\n"
1179
		"    read         read a byte from a debuggable\n"
1180
		"    write        write a byte to a debuggable\n"
1181
		"    read_block   read a whole block at once\n"
1182
		"    write_block  write a whole block at once\n"
1183
		"    breakpoint   breakpoint related subcommands\n"
1184
		"    watchpoint   watchpoint related subcommands\n"
1185
		"    watchexpr    watch expression related subcommands\n"
1186
		"    condition    debug condition related subcommands\n"
1187
		"    probe        probe related subcommands\n"
1188
		"    cont         continue execution after break\n"
1189
		"    step         execute one instruction\n"
1190
		"    break        break CPU at current position\n"
1191
		"    breaked      query CPU breaked status\n"
1192
		"    disasm       disassemble instructions\n"
1193
		"    disasm_blob  disassemble a instruction in Tcl binary string\n"
1194
		"    symbols      manage debug symbols\n"
1195
		"    trace        trace related subcommands\n"
1196
		"  The arguments are specific for each subcommand.\n"
1197
		"  Type 'help debug <subcommand>' for help about a specific subcommand.\n";
1198
1199
	constexpr auto listHelp =
1200
		"debug list\n"
1201
		"  Returns a list with the names of all 'debuggables'.\n"
1202
		"  These names are used in other debug subcommands.\n";
1203
	constexpr auto descHelp =
1204
		"debug desc <name>\n"
1205
		"  Returns a description for the debuggable with given name.\n";
1206
	constexpr auto sizeHelp =
1207
		"debug size <name>\n"
1208
		"  Returns the size (in bytes) of the debuggable with given name.\n";
1209
	constexpr auto readHelp =
1210
		"debug read <name> <addr>\n"
1211
		"  Read a byte at offset <addr> from the given debuggable.\n"
1212
		"  The offset must be smaller than the value returned from the "
1213
		"'size' subcommand\n"
1214
		"  Note that openMSX comes with a bunch of Tcl scripts that make "
1215
		"some of the debug reads much more convenient (e.g. reading from "
1216
		"Z80 or VDP registers). See the Console Command Reference for more "
1217
		"details about these.\n";
1218
	constexpr auto writeHelp =
1219
		"debug write <name> <addr> <val>\n"
1220
		"  Write a byte to the given debuggable at a certain offset.\n"
1221
		"  The offset must be smaller than the value returned from the "
1222
		"'size' subcommand\n";
1223
	constexpr auto readBlockHelp =
1224
		"debug read_block <name> <addr> <size>\n"
1225
		"  Read a whole block at once. This is equivalent with repeated "
1226
		"invocations of the 'read' subcommand, but using this subcommand "
1227
		"may be faster. The result is a Tcl binary string (see Tcl manual).\n"
1228
		"  The block is specified as size/offset in the debuggable. The "
1229
		"complete block must fit in the debuggable (see the 'size' "
1230
		"subcommand).\n";
1231
	constexpr auto writeBlockHelp =
1232
		"debug write_block <name> <addr> <values>\n"
1233
		"  Write a whole block at once. This is equivalent with repeated "
1234
		"invocations of the 'write' subcommand, but using this subcommand "
1235
		"may be faster. The <values> argument must be a Tcl binary string "
1236
		"(see Tcl manual).\n"
1237
		"  The block has a size and an offset in the debuggable. The "
1238
		"complete block must fit in the debuggable (see the 'size' "
1239
		"subcommand).\n";
1240
	constexpr auto breakPointHelp =
1241
		"debug breakpoint <subcommand> [<arguments>]\n"
1242
		"  Possible subcommands are:\n"
1243
		"    list      list all active breakpoints\n"
1244
		"    create    create a new breakpoint\n"
1245
		"    configure configure an existing breakpoint\n"
1246
		"    remove    remove an existing breakpoint\n"
1247
		"  Type 'help debug breakpoint <subcommand>' for help about a specific subcommand.\n";
1248
	constexpr auto watchPointHelp =
1249
		"debug watchpoint <subcommand> [<arguments>]\n"
1250
		"  Possible subcommands are:\n"
1251
		"    list      list all active watchpoints\n"
1252
		"    create    create a new watchpoint\n"
1253
		"    configure configure an existing watchpoint\n"
1254
		"    remove    remove an existing watchpoint\n"
1255
		"  Type 'help debug watchpoint <subcommand>' for help about a specific subcommand.\n";
1256
	constexpr auto watchExprHelp =
1257
		"debug watchexpr <subcommand> [<arguments>]\n"
1258
		"  Possible subcommands are:\n"
1259
		"    list      list all active watch expressions\n"
1260
		"    create    create a new watch expression\n"
1261
		"    configure configure an existing watch expression\n"
1262
		"    remove    remove an existing watch expression\n"
1263
		"  Type 'help debug watchexpr <subcommand>' for help about a specific subcommand.\n";
1264
	constexpr auto conditionHelp =
1265
		"debug condition <subcommand> [<arguments>]\n"
1266
		"  Possible subcommands are:\n"
1267
		"    list      list all active debug conditions\n"
1268
		"    create    create a new debug conditions\n"
1269
		"    configure configure an existing debug condition\n"
1270
		"    remove    remove an existing debug condition\n"
1271
		"  Type 'help debug condition <subcommand>' for help about a specific subcommand.\n";
1272
	constexpr auto breakPointListHelp =
1273
		"debug breakpoint list\n"
1274
		"  Lists all breakpoints. The result is a Tcl dict (<key>/<value>-pairs), where\n"
1275
		"  * <key> is the breakpoint ID\n"
1276
		"  * <value> is another Tcl dict containing the properties of the breakpoint.\n"
1277
		"            See 'help debug breakpoint create' for a description of these properties.\n";
1278
	constexpr auto watchPointListHelp =
1279
		"debug watchpoint list\n"
1280
		"  Lists all watchpoints. The result is a Tcl dict (<key>/<value>-pairs), where\n"
1281
		"  * <key> is the watchpoint ID\n"
1282
		"  * <value> is another Tcl dict containing the properties of the watchpoint.\n"
1283
		"            See 'help debug watchpoint create' for a description of these properties.\n";
1284
	constexpr auto watchExprListHelp =
1285
		"debug watchexpr list\n"
1286
		"  Lists all watch expressions. The result is a Tcl dict (<key>/<value>-pairs), where\n"
1287
		"  * <key> is the watchexpr ID\n"
1288
		"  * <value> is another Tcl dict containing the properties of the watch expression.\n"
1289
		"            See 'help debug watchexpr create' for a description of these properties.\n";
1290
	constexpr auto conditionListHelp =
1291
		"debug condition list\n"
1292
		"  Lists all debug conditions. The result is a Tcl dict (<key>/<value>-pairs), where\n"
1293
		"  * <key> is the debug condition ID\n"
1294
		"  * <value> is another Tcl dict containing the properties of the debug condition.\n"
1295
		"            See 'help debug condition create' for a description of these properties.\n";
1296
	constexpr FixedStr addressNote = "An address is just a number [0:0xFFFF] but can also be dynamically evaluated from a known symbol by specifying {$sym(MySymbol)}\n";
1297
	constexpr auto breakPointCreateHelp =
1298
		"debug breakpoint create [<property-name> <property-value>]...\n"
1299
		"  Create a new breakpoint with given properties. The following properties are supported:\n"
1300
		"  -address    the address where the breakpoint should trigger\n"
1301
		"              " +  addressNote +
1302
		"  -condition  a Tcl expression that must evaluate to true for the breakpoint to trigger (default = no condition)\n"
1303
		"  -command    a Tcl command that should be executed when the breakpoint triggers (default = 'debug break')\n"
1304
		"  -enabled    set to false to (temporarily) disable this breakpoint\n"
1305
		"  -once       if 'true' the breakpoint is automatically removed after it triggered (default = 'false', meaning recurring)\n";
1306
	constexpr auto watchPointCreateHelp =
1307
		"debug watchpoint create [<property-name> <property-value>]...\n"
1308
		"  Create a new watchpoint with given properties. The following properties are supported:\n"
1309
		"  -type       one of 'read_io', 'write_io', 'read_mem', 'write_mem'\n"
1310
		"  -address    the address(es) where the watchpoint should trigger, can be a single address or a begin/end-pair\n"
1311
		"              " + addressNote +
1312
		"  -condition  a Tcl expression that must evaluate to true for the watchpoint to trigger (default = no condition)\n"
1313
		"  -command    a Tcl command that should be executed when the watchpoint triggers (default = 'debug break')\n"
1314
		"  -enabled    set to false to (temporarily) disable this watchpoint\n"
1315
		"  -once       if 'true' the watchpoint is automatically removed after it triggered (default = 'false', meaning recurring)\n";
1316
	constexpr auto watchExprCreateHelp =
1317
		"debug watchexpr create [<property-name> <property-value>]...\n"
1318
		"  Create a new watch expression with given properties. The following properties are supported:\n"
1319
		"  -description  (optional) description for the expression\n"
1320
		"  -expression   the actual Tcl expression\n"
1321
		"  -format       (optional) format specifier\n"
1322
		"Note: watch expressions are mostly a GUI feature, hover the help-marker (?) "
1323
		"in the GUI (menu bar > Debugger > Watch expression) to get a more detailed explanation.\n";
1324
	constexpr auto conditionCreateHelp =
1325
		"debug condition create [<property-name> <property-value>]...\n"
1326
		"  Create a new debug condition with given properties. The following properties are supported:\n"
1327
		"  -condition  a Tcl expression that must evaluate to true for the debug condition to trigger (default = no condition)\n"
1328
		"  -command    a Tcl command that should be executed when the debug condition triggers (default = 'debug break')\n"
1329
		"  -enabled    set to false to (temporarily) disable this condition\n"
1330
		"  -once       if 'true' the debug condition is automatically removed after it triggered (default = 'false', meaning recurring)\n";
1331
	constexpr auto breakPointConfigureHelp =
1332
		"debug breakpoint configure <id> [<property-name> <property-value>]...\n"
1333
		"  Change one or more properties of an existing breakpoint.\n"
1334
		"  See 'help debug breakpoint create' for a description of the properties.\n";
1335
	constexpr auto watchPointConfigureHelp =
1336
		"debug watchpoint configure <id> [<property-name> <property-value>]...\n"
1337
		"  Change one or more properties of an existing watchpoint.\n"
1338
		"  See 'help debug watchpoint create' for a description of the properties.\n";
1339
	constexpr auto watchExprConfigureHelp =
1340
		"debug watchexpr configure <id> [<property-name> <property-value>]...\n"
1341
		"  Change one or more properties of an existing watch expression.\n"
1342
		"  See 'help debug watchexpr create' for a description of the properties.\n";
1343
	constexpr auto conditionConfigureHelp =
1344
		"debug condition configure <id> [<property-name> <property-value>]...\n"
1345
		"  Change one or more properties of an existing debug condition.\n"
1346
		"  See 'help debug condition create' for a description of the properties.\n";
1347
	constexpr auto breakPointRemoveHelp =
1348
		"debug breakpoint remove <id>\n"
1349
		"  Remove the breakpoint with given ID.\n";
1350
	constexpr auto watchPointRemoveHelp =
1351
		"debug watchpoint remove <id>\n"
1352
		"  Remove the watchpoint with given ID.\n";
1353
	constexpr auto watchExprRemoveHelp =
1354
		"debug watchexpr remove <id>\n"
1355
		"  Remove the watch expression with given ID.\n";
1356
	constexpr auto conditionRemoveHelp =
1357
		"debug condition remove <id>\n"
1358
		"  Remove the debug condition with given ID.\n";
1359
	constexpr auto setBpHelp =
1360
		"[deprecated] replaced by: 'debug breakpoint create <args>...'\n"
1361
		"\n"
1362
		"debug set_bp [-once] <addr> [<cond>] [<cmd>]\n"
1363
		"  Insert a new breakpoint at given address. When the CPU is about "
1364
		"to execute the instruction at this address, execution will be "
1365
		"breaked. At least this is the default behaviour, see next "
1366
		"paragraphs.\n"
1367
		"  When the -once flag is given, the breakpoint is automatically "
1368
		"removed after it triggered. In other words: it only triggers once.\n"
1369
		"  Optionally you can specify a condition. When the CPU reaches "
1370
		"the breakpoint this condition is evaluated, only when the condition "
1371
		"evaluated to true execution will be breaked.\n"
1372
		"  A condition must be specified as a Tcl expression. For example\n"
1373
		"     debug set_bp 0xf37d {[reg C] == 0x2F}\n"
1374
		"  This breaks on address 0xf37d but only when Z80 register C has the "
1375
		"value 0x2F.\n"
1376
		"  Also optionally you can specify a command that should be "
1377
		"executed when the breakpoint is reached (and condition is true). "
1378
		"By default this command is 'debug break'.\n"
1379
		"  The result of this command is a breakpoint ID. This ID can "
1380
		"later be used to remove this breakpoint again.\n";
1381
	constexpr auto removeBpHelp =
1382
		"[deprecated] replaced by: 'debug breakpoint remove <id>'\n"
1383
		"\n"
1384
		"debug remove_bp <id>\n"
1385
		"  Remove the breakpoint with given ID again. You can use the "
1386
		"'list_bp' subcommand to see all valid IDs.\n";
1387
	constexpr auto listBpHelp =
1388
		"[deprecated] replaced by: 'debug breakpoint list'\n"
1389
		"\n"
1390
		"debug list_bp\n"
1391
		"  Lists all active breakpoints. The result is printed in 4 "
1392
		"columns. The first column contains the breakpoint ID. The "
1393
		"second one has the address. The third has the condition "
1394
		"(default condition is empty). And the last column contains "
1395
		"the command that will be executed (default is 'debug break').\n";
1396
	constexpr auto setWatchPointHelp =
1397
		"[deprecated] replaced by: 'debug watchpoint create <args>...'\n"
1398
		"\n"
1399
		"debug set_watchpoint [-once] <type> <region> [<cond>] [<cmd>]\n"
1400
		"  Insert a new watchpoint of given type on the given region, "
1401
		"there can be an optional -once flag, a condition and alternative "
1402
		"command. See the 'set_bp' subcommand for details about these.\n"
1403
		"  Type must be one of the following:\n"
1404
		"    read_io    break when CPU reads from given IO port(s)\n"
1405
		"    write_io   break when CPU writes to given IO port(s)\n"
1406
		"    read_mem   break when CPU reads from given memory location(s)\n"
1407
		"    write_mem  break when CPU writes to given memory location(s)\n"
1408
		"  Region is either a single value, this corresponds to a single "
1409
		"memory location or IO port. Otherwise region must be a list of "
1410
		"two values (enclosed in braces) that specify a begin and end "
1411
		"point of a whole memory region or a range of IO ports.\n"
1412
		"During the execution of <cmd>, the following global Tcl "
1413
		"variables are set:\n"
1414
		"  ::wp_last_address   this is the actual address of the mem/io "
1415
		"read/write that triggered the watchpoint\n"
1416
		"  ::wp_last_value     this is the actual value that was written "
1417
		"by the mem/io write that triggered the watchpoint\n"
1418
		"Examples:\n"
1419
		"  debug set_watchpoint write_io 0x99 {[reg A] == 0x81}\n"
1420
		"  debug set_watchpoint read_mem {0xfbe5 0xfbef}\n";
1421
	constexpr auto removeWatchPointHelp =
1422
		"[deprecated] replaced by: 'debug watchpoint remove <id>'\n"
1423
		"\n"
1424
		"debug remove_watchpoint <id>\n"
1425
		"  Remove the watchpoint with given ID again. You can use the "
1426
		"'list_watchpoints' subcommand to see all valid IDs.\n";
1427
	constexpr auto listWatchPointsHelp =
1428
		"[deprecated] replaced by: 'debug watchpoint list'\n"
1429
		"\n"
1430
		"debug list_watchpoints\n"
1431
		"  Lists all active watchpoints. The result is similar to the "
1432
		"'list_bp' subcommand, but there is an extra column (2nd column) "
1433
		"that contains the type of the watchpoint.\n";
1434
	constexpr auto setCondHelp =
1435
		"[deprecated] replaced by: 'debug condition create <args>...'\n"
1436
		"\n"
1437
		"debug set_condition [-once] <cond> [<cmd>]\n"
1438
		"  Insert a new condition. These are much like breakpoints, "
1439
		"except that they are checked before every instruction "
1440
		"(breakpoints are tied to a specific address).\n"
1441
		"  Conditions will slow down simulation MUCH more than "
1442
		"breakpoints. So only use them when you don't care about "
1443
		"simulation speed (when you're debugging this is usually not "
1444
		"a problem).\n"
1445
		"  See 'help debug set_bp' for more details.\n";
1446
	constexpr auto removeCondHelp =
1447
		"[deprecated] replaced by: 'debug condition remove <id>'\n"
1448
		"\n"
1449
		"debug remove_condition <id>\n"
1450
		"  Remove the condition with given ID again. You can use the "
1451
		"'list_conditions' subcommand to see all valid IDs.\n";
1452
	constexpr auto listCondHelp =
1453
		"[deprecated] replaced by: 'debug condition list'\n"
1454
		"\n"
1455
		"debug list_conditions\n"
1456
		"  Lists all active conditions. The result is similar to the "
1457
		"'list_bp' subcommand, but without the 2nd column that would "
1458
		"show the address.\n";
1459
	constexpr auto probeHelp =
1460
		"debug probe <subcommand> [<arguments>]\n"
1461
		"  Possible subcommands are:\n"
1462
		"    list                                     returns a list of all probes\n"
1463
		"    desc   <probe>                           returns a description of this probe\n"
1464
		"    read   <probe>                           returns the current value of this probe\n"
1465
		"    set_bp <probe> [-once] [<cond>] [<cmd>]  set a breakpoint on the given probe\n"
1466
		"    remove_bp <id>                           remove the given breakpoint\n"
1467
		"    list_bp                                  returns a list of breakpoints that are set on probes\n";
1468
	constexpr auto contHelp =
1469
		"debug cont\n"
1470
		"  Continue execution after CPU was breaked.\n";
1471
	constexpr auto stepHelp =
1472
		"debug step\n"
1473
		"  Execute one instruction. This command is only meaningful in "
1474
		"break mode.\n";
1475
	constexpr auto breakHelp =
1476
		"debug break\n"
1477
		"  Immediately break CPU execution. When CPU was already breaked "
1478
		"this command has no effect.\n";
1479
	constexpr auto breakedHelp =
1480
		"debug breaked\n"
1481
		"  Query the CPU breaked status. Returns '1' when CPU was "
1482
		"breaked, '0' otherwise.\n";
1483
	constexpr auto disasmHelp =
1484
		"debug disasm <addr>\n"
1485
		"  Disassemble the instruction at the given address. The result "
1486
		"is a Tcl list. The first element in the list contains a textual "
1487
		"representation of the instruction, the next elements contain the "
1488
		"bytes that make up this instruction (thus the length of the "
1489
		"resulting list can be used to derive the number of bytes in the "
1490
		"instruction).\n"
1491
		"  Note that openMSX comes with a 'disasm' Tcl script that is much "
1492
		"more convenient to use than this subcommand.\n  "
1493
		+ addressNote + "\n";
1494
	constexpr auto disasmBlobHelp =
1495
		"debug disasm_blob <value> <addr> [<function>]\n"
1496
		"  This is a more generic version of the disasm subcommand, but it "
1497
		"works on a Tcl binary string (see Tcl manual) to disassemble a "
1498
		"single instruction. The given address is used when relative "
1499
		"address to jump to is necessary. The optional fuction will be "
1500
		"called with an address as parameter and may return a symbol name "
1501
		"that replaces that address if a symbol that matches the address "
1502
		"is found.\n  "
1503
		+ addressNote + "\n";
1504
	constexpr auto symbolsHelp =
1505
		"debug symbols <subcommand> [<arguments>]\n"
1506
		"  Possible subcommands are:\n"
1507
		"    types                     returns a list of symbol file types\n"
1508
		"    load <filename> [<type>]  load a symbol file, auto-detect type if none is given\n"
1509
		"    remove <filename>         remove a previously loaded symbol file\n"
1510
		"    files                     returns a list of all loaded symbol files\n"
1511
		"    lookup [-filename <filename>] [-name <name>] [-value <value>]\n"
1512
		"           returns a list of symbols in an optionally given file\n"
1513
		"           and/or with an optionally given name\n"
1514
		"           and/or with an optionally given value\n"
1515
		"  Note: an easier syntax to lookup a symbol value based on the name is:\n"
1516
		"        $sym(<name>)\n";
1517
	constexpr auto unknownHelp =
1518
		"Unknown subcommand, use 'help debug' to see a list of valid "
1519
		"subcommands.\n";
1520
1521
	auto size = tokens.size();
1522
	assert(size >= 1);
1523
	if (size == 1) {
1524
		return generalHelp;
1525
	} else if (tokens[1] == "list") {
1526
		return listHelp;
1527
	} else if (tokens[1] == "desc") {
1528
		return descHelp;
1529
	} else if (tokens[1] == "size") {
1530
		return sizeHelp;
1531
	} else if (tokens[1] == "read") {
1532
		return readHelp;
1533
	} else if (tokens[1] == "write") {
1534
		return writeHelp;
1535
	} else if (tokens[1] == "read_block") {
1536
		return readBlockHelp;
1537
	} else if (tokens[1] == "write_block") {
1538
		return writeBlockHelp;
1539
	} else if (tokens[1] == "breakpoint") {
1540
		if (size == 2) {
1541
			return breakPointHelp;
1542
		} else if (tokens[2] == "list") {
1543
			return breakPointListHelp;
1544
		} else if (tokens[2] == "create") {
1545
			return std::string(breakPointCreateHelp);
1546
		} else if (tokens[2] == "configure") {
1547
			return breakPointConfigureHelp;
1548
		} else if (tokens[2] == "remove") {
1549
			return breakPointRemoveHelp;
1550
		} else {
1551
			return breakPointHelp;
1552
		}
1553
	} else if (tokens[1] == "watchpoint") {
1554
		if (size == 2) {
1555
			return watchPointHelp;
1556
		} else if (tokens[2] == "list") {
1557
			return watchPointListHelp;
1558
		} else if (tokens[2] == "create") {
1559
			return std::string(watchPointCreateHelp);
1560
		} else if (tokens[2] == "configure") {
1561
			return watchPointConfigureHelp;
1562
		} else if (tokens[2] == "remove") {
1563
			return watchPointRemoveHelp;
1564
		} else {
1565
			return watchPointHelp;
1566
		}
1567
	} else if (tokens[1] == "watchexpr") {
1568
		if (size == 2) {
1569
			return watchExprHelp;
1570
		} else if (tokens[2] == "list") {
1571
			return watchExprListHelp;
1572
		} else if (tokens[2] == "create") {
1573
			return watchExprCreateHelp;
1574
		} else if (tokens[2] == "configure") {
1575
			return watchExprConfigureHelp;
1576
		} else if (tokens[2] == "remove") {
1577
			return watchExprRemoveHelp;
1578
		} else {
1579
			return watchExprHelp;
1580
		}
1581
	} else if (tokens[1] == "condition") {
1582
		if (size == 2) {
1583
			return conditionHelp;
1584
		} else if (tokens[2] == "list") {
1585
			return conditionListHelp;
1586
		} else if (tokens[2] == "create") {
1587
			return conditionCreateHelp;
1588
		} else if (tokens[2] == "configure") {
1589
			return conditionConfigureHelp;
1590
		} else if (tokens[2] == "remove") {
1591
			return conditionRemoveHelp;
1592
		} else {
1593
			return conditionHelp;
1594
		}
1595
	} else if (tokens[1] == "set_bp") {
1596
		return setBpHelp;
1597
	} else if (tokens[1] == "remove_bp") {
1598
		return removeBpHelp;
1599
	} else if (tokens[1] == "list_bp") {
1600
		return listBpHelp;
1601
	} else if (tokens[1] == "set_watchpoint") {
1602
		return setWatchPointHelp;
1603
	} else if (tokens[1] == "remove_watchpoint") {
1604
		return removeWatchPointHelp;
1605
	} else if (tokens[1] == "list_watchpoints") {
1606
		return listWatchPointsHelp;
1607
	} else if (tokens[1] == "set_condition") {
1608
		return setCondHelp;
1609
	} else if (tokens[1] == "remove_condition") {
1610
		return removeCondHelp;
1611
	} else if (tokens[1] == "list_conditions") {
1612
		return listCondHelp;
1613
	} else if (tokens[1] == "probe") {
1614
		return probeHelp;
1615
	} else if (tokens[1] == "cont") {
1616
		return contHelp;
1617
	} else if (tokens[1] == "step") {
1618
		return stepHelp;
1619
	} else if (tokens[1] == "break") {
1620
		return breakHelp;
1621
	} else if (tokens[1] == "breaked") {
1622
		return breakedHelp;
1623
	} else if (tokens[1] == "disasm") {
1624
		return std::string(disasmHelp);
1625
	} else if (tokens[1] == "disasm_blob") {
1626
		return std::string(disasmBlobHelp);
1627
	} else if (tokens[1] == "symbols") {
1628
		return symbolsHelp;
1629
	} else if (tokens[1] == "trace") {
1630
		return debugger().tracer.help(tokens);
1631
	} else {
1632
		return unknownHelp;
1633
	}
1634
}
1635
1636
std::vector<std::string> Debugger::Cmd::getBreakPointIds() const
1637
{
1638
	return to_vector(std::views::transform(
1639
		MSXCPUInterface::getBreakPoints(),
1640
		[](const auto& bp) { return bp.getIdStr(); }));
1641
}
1642
std::vector<std::string> Debugger::Cmd::getWatchPointIds() const
1643
{
1644
	return to_vector(std::views::transform(
1645
		debugger().motherBoard.getCPUInterface().getWatchPoints(),
1646
		[](const auto& w) { return w->getIdStr(); }));
1647
}
1648
std::vector<std::string> Debugger::Cmd::getWatchExprIds() const
1649
{
1650
	return to_vector(std::views::transform(
1651
		debugger().getWatchExprs(),
1652
		[](const auto& w) { return w.getIdStr(); }));
1653
}
1654
std::vector<std::string> Debugger::Cmd::getConditionIds() const
1655
{
1656
	return to_vector(std::views::transform(
1657
		MSXCPUInterface::getConditions(),
1658
		[](const auto& c) { return c.getIdStr(); }));
1659
}
1660
1661
void Debugger::Cmd::tabCompletion(std::vector<std::string>& tokens) const
1662
{
1663
	using namespace std::literals;
1664
	static constexpr std::array singleArgCmds = {
1665
		"list"sv, "step"sv, "cont"sv, "break"sv, "breaked"sv,
1666
		"list_bp"sv, "list_watchpoints"sv, "list_conditions"sv,
1667
	};
1668
	static constexpr std::array debuggableArgCmds = {
1669
		"desc"sv, "size"sv, "read"sv, "read_block"sv,
1670
		"write"sv, "write_block"sv,
1671
	};
1672
	static constexpr std::array otherCmds = {
1673
		"disasm"sv, "disasm_blob"sv, "set_bp"sv, "remove_bp"sv, "set_watchpoint"sv,
1674
		"remove_watchpoint"sv, "set_condition"sv, "remove_condition"sv, "trace"sv,
1675
		"probe"sv, "symbols"sv, "breakpoint"sv, "watchpoint"sv, "watchexpr"sv, "condition"sv,
1676
	};
1677
	static constexpr std::array types = {
1678
		"read_io"sv, "write_io"sv, "read_mem"sv, "write_mem"sv,
1679
	};
1680
	switch (auto size = tokens.size(); size) {
1681
	case 2: {
1682
		completeString(tokens, concatArray(singleArgCmds, debuggableArgCmds, otherCmds));
1683
		break;
1684
	}
1685
	case 3:
1686
		if (!contains(singleArgCmds, tokens[1])) {
1687
			// this command takes (an) argument(s)
1688
			if (contains(debuggableArgCmds, tokens[1])) {
1689
				// it takes a debuggable here
1690
				completeString(tokens, std::views::keys(debugger().debuggables));
1691
			} else if (tokens[1] == one_of("breakpoint"sv, "watchpoint"sv, "watchexpr"sv, "condition"sv)) {
1692
				static constexpr std::array subCmds = {
1693
					"list"sv, "create"sv,
1694
					"configure"sv, "remove"sv,
1695
				};
1696
				completeString(tokens, subCmds);
1697
			} else if (tokens[1] == "remove_bp") {
1698
				// this one takes a bp id
1699
				completeString(tokens, getBreakPointIds());
1700
			} else if (tokens[1] == "remove_watchpoint") {
1701
				// this one takes a wp id
1702
				completeString(tokens, getWatchPointIds());
1703
			} else if (tokens[1] == "remove_condition") {
1704
				// this one takes a cond id
1705
				completeString(tokens, getConditionIds());
1706
			} else if (tokens[1] == "set_watchpoint") {
1707
				completeString(tokens, types);
1708
			} else if (tokens[1] == "probe") {
1709
				static constexpr std::array subCmds = {
1710
					"list"sv, "desc"sv, "read"sv, "set_bp"sv,
1711
					"remove_bp"sv, "list_bp"sv,
1712
				};
1713
				completeString(tokens, subCmds);
1714
			} else if (tokens[1] == "symbols") {
1715
				static constexpr std::array subCmds = {
1716
					"types"sv, "load"sv, "remove"sv,
1717
					"files"sv, "lookup"sv,
1718
				};
1719
				completeString(tokens, subCmds);
1720
			} else if (tokens[1] == "trace") {
1721
				debugger().tracer.tabCompletion(debugger(), tokens);
1722
			}
1723
		}
1724
		break;
1725
	default:
1726
		if ((size == 4) && (tokens[1] == "probe") &&
1727
		    (tokens[2] == one_of("desc", "read", "set_bp"))) {
1728
			completeString(tokens, std::views::transform(
1729
				debugger().probes, &ProbeBase::getName));
1730
		} else if (tokens[1] == "breakpoint") {
1731
			if ((size == 4) && tokens[2] == one_of("remove"sv, "configure"sv)) {
1732
				completeString(tokens, getBreakPointIds());
1733
			} else if (((size >= 4) && (tokens[2] == "create")) ||
1734
			           ((size >= 5) && (tokens[2] == "configure"))) {
1735
				static constexpr std::array properties = {
1736
					"-address"sv, "-command"sv, "-condition"sv, "-enabled"sv, "-once"sv,
1737
				};
1738
				completeString(tokens, properties);
1739
			}
1740
		} else if (tokens[1] == "watchpoint") {
1741
			if ((size == 4) && tokens[2] == one_of("remove"sv, "configure"sv)) {
1742
				completeString(tokens, getWatchPointIds());
1743
			} else if (((size >= 4) && (tokens[2] == "create")) ||
1744
			           ((size >= 5) && (tokens[2] == "configure"))) {
1745
				if (tokens[size - 2] == "-type") {
1746
					completeString(tokens, types);
1747
				} else {
1748
					static constexpr std::array properties = {
1749
						"-type"sv, "-address"sv, "-command"sv, "-condition"sv, "-enabled"sv, "-once"sv,
1750
					};
1751
					completeString(tokens, properties);
1752
				}
1753
			}
1754
		} else if (tokens[1] == "watchexpr") {
1755
			if ((size == 4) && tokens[2] == one_of("remove"sv, "configure"sv)) {
1756
				completeString(tokens, getWatchExprIds());
1757
			} else if (((size >= 4) && (tokens[2] == "create")) ||
1758
			           ((size >= 5) && (tokens[2] == "configure"))) {
1759
				static constexpr std::array properties = {
1760
					"-description"sv, "-expression"sv, "-format"sv,
1761
				};
1762
				completeString(tokens, properties);
1763
			}
1764
		} else if (tokens[1] == "condition") {
1765
			if ((size == 4) && tokens[2] == one_of("remove"sv, "configure"sv)) {
1766
				completeString(tokens, getConditionIds());
1767
			} else if (((size >= 4) && (tokens[2] == "create")) ||
1768
			           ((size >= 5) && (tokens[2] == "configure"))) {
1769
				static constexpr std::array properties = {
1770
					"-command"sv, "-condition"sv, "-enabled"sv, "-once"sv,
1771
				};
1772
				completeString(tokens, properties);
1773
			}
1774
		} else if (tokens[1] == "trace") {
1775
			debugger().tracer.tabCompletion(debugger(), tokens);
1776
		}
1777
		break;
1778
	}
1779
}
1780
1781
} // namespace openmsx