openMSX
Debugger.cc
Go to the documentation of this file.
1#include "Debugger.hh"
2#include "Debuggable.hh"
3#include "CliComm.hh"
4#include "ProbeBreakPoint.hh"
5#include "MSXMotherBoard.hh"
6#include "MSXCPU.hh"
7#include "MSXCPUInterface.hh"
8#include "BreakPoint.hh"
9#include "DebugCondition.hh"
10#include "MSXWatchIODevice.hh"
11#include "TclArgParser.hh"
12#include "TclObject.hh"
13#include "CommandException.hh"
14#include "MemBuffer.hh"
15#include "one_of.hh"
16#include "ranges.hh"
17#include "stl.hh"
18#include "StringOp.hh"
19#include "unreachable.hh"
20#include "view.hh"
21#include "xrange.hh"
22#include <array>
23#include <cassert>
24#include <memory>
25
26using std::string;
27using std::string_view;
28
29namespace openmsx {
30
32 : motherBoard(motherBoard_)
33 , cmd(motherBoard.getCommandController(),
34 motherBoard.getStateChangeDistributor(),
35 motherBoard.getScheduler())
36{
37}
38
40{
41 assert(!cpu);
42 assert(debuggables.empty());
43}
44
45void Debugger::registerDebuggable(string name, Debuggable& debuggable)
46{
47 assert(!debuggables.contains(name));
48 debuggables.emplace_noDuplicateCheck(std::move(name), &debuggable);
49}
50
51void Debugger::unregisterDebuggable(string_view name, Debuggable& debuggable)
52{
53 assert(debuggables.contains(name));
54 assert(debuggables[name] == &debuggable); (void)debuggable;
55 debuggables.erase(name);
56}
57
59{
60 auto* v = lookup(debuggables, name);
61 return v ? *v : nullptr;
62}
63
64Debuggable& Debugger::getDebuggable(string_view name)
65{
66 Debuggable* result = findDebuggable(name);
67 if (!result) {
68 throw CommandException("No such debuggable: ", name);
69 }
70 return *result;
71}
72
74{
75 assert(!probes.contains(probe.getName()));
76 probes.insert_noDuplicateCheck(&probe);
77}
78
80{
81 assert(probes.contains(probe.getName()));
82 probes.erase(probe.getName());
83}
84
86{
87 auto it = probes.find(name);
88 return (it != std::end(probes)) ? *it : nullptr;
89}
90
91ProbeBase& Debugger::getProbe(string_view name)
92{
93 auto* result = findProbe(name);
94 if (!result) {
95 throw CommandException("No such probe: ", name);
96 }
97 return *result;
98}
99
100unsigned Debugger::insertProbeBreakPoint(
101 TclObject command, TclObject condition,
102 ProbeBase& probe, bool once, unsigned newId /*= -1*/)
103{
104 auto bp = std::make_unique<ProbeBreakPoint>(
105 std::move(command), std::move(condition), *this, probe, once, newId);
106 unsigned result = bp->getId();
107 motherBoard.getMSXCliComm().update(CliComm::DEBUG_UPDT, tmpStrCat("pp#", result), "add");
108 probeBreakPoints.push_back(std::move(bp));
109 return result;
110}
111
112void Debugger::removeProbeBreakPoint(string_view name)
113{
114 if (name.starts_with("pp#")) {
115 // remove by id
116 if (auto id = StringOp::stringToBase<10, unsigned>(name.substr(3))) {
117 if (auto it = ranges::find(probeBreakPoints, id, &ProbeBreakPoint::getId);
118 it != std::end(probeBreakPoints)) {
119 motherBoard.getMSXCliComm().update(
120 CliComm::DEBUG_UPDT, name, "remove");
121 move_pop_back(probeBreakPoints, it);
122 return;
123 }
124 }
125 throw CommandException("No such breakpoint: ", name);
126 } else {
127 // remove by probe, only works for unconditional bp
128 auto it = ranges::find(probeBreakPoints, name, [](auto& bp) {
129 return bp->getProbe().getName();
130 });
131 if (it == std::end(probeBreakPoints)) {
132 throw CommandException(
133 "No (unconditional) breakpoint for probe: ", name);
134 }
135 motherBoard.getMSXCliComm().update(
136 CliComm::DEBUG_UPDT, tmpStrCat("pp#", (*it)->getId()), "remove");
137 move_pop_back(probeBreakPoints, it);
138 }
139}
140
142{
143 motherBoard.getMSXCliComm().update(
144 CliComm::DEBUG_UPDT, tmpStrCat("pp#", bp.getId()), "remove");
145 move_pop_back(probeBreakPoints, rfind_unguarded(probeBreakPoints, &bp,
146 [](auto& v) { return v.get(); }));
147}
148
149unsigned Debugger::setWatchPoint(TclObject command, TclObject condition,
150 WatchPoint::Type type,
151 unsigned beginAddr, unsigned endAddr,
152 bool once, unsigned newId /*= -1*/)
153{
154 std::shared_ptr<WatchPoint> wp;
156 wp = std::make_shared<WatchIO>(
157 motherBoard, type, beginAddr, endAddr,
158 std::move(command), std::move(condition), once, newId);
159 } else {
160 wp = std::make_shared<WatchPoint>(
161 std::move(command), std::move(condition), type, beginAddr, endAddr, once, newId);
162 }
163 motherBoard.getCPUInterface().setWatchPoint(wp);
164 return wp->getId();
165}
166
168{
169 // Copy watchpoints to new machine.
170 assert(motherBoard.getCPUInterface().getWatchPoints().empty());
171 for (const auto& wp : other.motherBoard.getCPUInterface().getWatchPoints()) {
172 setWatchPoint(wp->getCommandObj(), wp->getConditionObj(),
173 wp->getType(), wp->getBeginAddress(),
174 wp->getEndAddress(), wp->onlyOnce(),
175 wp->getId());
176 }
177
178 // Copy probes to new machine.
179 assert(probeBreakPoints.empty());
180 for (auto& bp : other.probeBreakPoints) {
181 if (ProbeBase* probe = findProbe(bp->getProbe().getName())) {
182 insertProbeBreakPoint(bp->getCommandObj(),
183 bp->getConditionObj(),
184 *probe, bp->onlyOnce(),
185 bp->getId());
186 }
187 }
188
189 // Breakpoints and conditions are (currently) global, so no need to
190 // copy those.
191}
192
193
194// class Debugger::Cmd
195
196static word getAddress(Interpreter& interp, const TclObject& token)
197{
198 unsigned addr = token.getInt(interp);
199 if (addr >= 0x10000) {
200 throw CommandException("Invalid address");
201 }
202 return addr;
203}
204
205Debugger::Cmd::Cmd(CommandController& commandController_,
206 StateChangeDistributor& stateChangeDistributor_,
207 Scheduler& scheduler_)
208 : RecordedCommand(commandController_, stateChangeDistributor_,
209 scheduler_, "debug")
210{
211}
212
213bool Debugger::Cmd::needRecord(std::span<const TclObject> tokens) const
214{
215 // Note: it's crucial for security that only the write and write_block
216 // subcommands are recorded and replayed. The 'set_bp' command for
217 // example would allow to set a callback that can execute arbitrary Tcl
218 // code. See comments in RecordedCommand for more details.
219 if (tokens.size() < 2) return false;
220 return tokens[1].getString() == one_of("write", "write_block");
221}
222
223void Debugger::Cmd::execute(
224 std::span<const TclObject> tokens, TclObject& result, EmuTime::param /*time*/)
225{
226 checkNumArgs(tokens, AtLeast{2}, "subcommand ?arg ...?");
227 executeSubCommand(tokens[1].getString(),
228 "read", [&]{ read(tokens, result); },
229 "read_block", [&]{ readBlock(tokens, result); },
230 "write", [&]{ write(tokens, result); },
231 "write_block", [&]{ writeBlock(tokens, result); },
232 "size", [&]{ size(tokens, result); },
233 "desc", [&]{ desc(tokens, result); },
234 "list", [&]{ list(result); },
235 "step", [&]{ debugger().motherBoard.getCPUInterface().doStep(); },
236 "cont", [&]{ debugger().motherBoard.getCPUInterface().doContinue(); },
237 "disasm", [&]{ debugger().cpu->disasmCommand(getInterpreter(), tokens, result); },
238 "break", [&]{ debugger().motherBoard.getCPUInterface().doBreak(); },
239 "breaked", [&]{ result = debugger().motherBoard.getCPUInterface().isBreaked(); },
240 "set_bp", [&]{ setBreakPoint(tokens, result); },
241 "remove_bp", [&]{ removeBreakPoint(tokens, result); },
242 "list_bp", [&]{ listBreakPoints(tokens, result); },
243 "set_watchpoint", [&]{ setWatchPoint(tokens, result); },
244 "remove_watchpoint", [&]{ removeWatchPoint(tokens, result); },
245 "list_watchpoints", [&]{ listWatchPoints(tokens, result); },
246 "set_condition", [&]{ setCondition(tokens, result); },
247 "remove_condition", [&]{ removeCondition(tokens, result); },
248 "list_conditions", [&]{ listConditions(tokens, result); },
249 "probe", [&]{ probe(tokens, result); });
250}
251
252void Debugger::Cmd::list(TclObject& result)
253{
254 result.addListElements(view::keys(debugger().debuggables));
255}
256
257void Debugger::Cmd::desc(std::span<const TclObject> tokens, TclObject& result)
258{
259 checkNumArgs(tokens, 3, "debuggable");
260 Debuggable& device = debugger().getDebuggable(tokens[2].getString());
261 result = device.getDescription();
262}
263
264void Debugger::Cmd::size(std::span<const TclObject> tokens, TclObject& result)
265{
266 checkNumArgs(tokens, 3, "debuggable");
267 Debuggable& device = debugger().getDebuggable(tokens[2].getString());
268 result = device.getSize();
269}
270
271void Debugger::Cmd::read(std::span<const TclObject> tokens, TclObject& result)
272{
273 checkNumArgs(tokens, 4, Prefix{2}, "debuggable address");
274 Debuggable& device = debugger().getDebuggable(tokens[2].getString());
275 unsigned addr = tokens[3].getInt(getInterpreter());
276 if (addr >= device.getSize()) {
277 throw CommandException("Invalid address");
278 }
279 result = device.read(addr);
280}
281
282void Debugger::Cmd::readBlock(std::span<const TclObject> tokens, TclObject& result)
283{
284 checkNumArgs(tokens, 5, Prefix{2}, "debuggable address size");
285 auto& interp = getInterpreter();
286 Debuggable& device = debugger().getDebuggable(tokens[2].getString());
287 unsigned devSize = device.getSize();
288 unsigned addr = tokens[3].getInt(interp);
289 if (addr >= devSize) {
290 throw CommandException("Invalid address");
291 }
292 unsigned num = tokens[4].getInt(interp);
293 if (num > (devSize - addr)) {
294 throw CommandException("Invalid size");
295 }
296
297 MemBuffer<byte> buf(num);
298 for (auto i : xrange(num)) {
299 buf[i] = device.read(addr + i);
300 }
301 result = std::span<byte>{buf.data(), num};
302}
303
304void Debugger::Cmd::write(std::span<const TclObject> tokens, TclObject& /*result*/)
305{
306 checkNumArgs(tokens, 5, Prefix{2}, "debuggable address value");
307 auto& interp = getInterpreter();
308 Debuggable& device = debugger().getDebuggable(tokens[2].getString());
309 unsigned addr = tokens[3].getInt(interp);
310 if (addr >= device.getSize()) {
311 throw CommandException("Invalid address");
312 }
313 unsigned value = tokens[4].getInt(interp);
314 if (value >= 256) {
315 throw CommandException("Invalid value");
316 }
317
318 device.write(addr, value);
319}
320
321void Debugger::Cmd::writeBlock(std::span<const TclObject> tokens, TclObject& /*result*/)
322{
323 checkNumArgs(tokens, 5, Prefix{2}, "debuggable address values");
324 Debuggable& device = debugger().getDebuggable(tokens[2].getString());
325 unsigned devSize = device.getSize();
326 unsigned addr = tokens[3].getInt(getInterpreter());
327 if (addr >= devSize) {
328 throw CommandException("Invalid address");
329 }
330 auto buf = tokens[4].getBinary();
331 if ((buf.size() + addr) > devSize) {
332 throw CommandException("Invalid size");
333 }
334
335 for (auto i : xrange(buf.size())) {
336 device.write(unsigned(addr + i), buf[i]);
337 }
338}
339
340void Debugger::Cmd::setBreakPoint(std::span<const TclObject> tokens, TclObject& result)
341{
342 checkNumArgs(tokens, AtLeast{3}, "address ?-once? ?condition? ?command?");
343 TclObject command("debug break");
344 TclObject condition;
345 bool once = false;
346
347 std::array info = {flagArg("-once", once)};
348 auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(2), info);
349 if ((arguments.size() < 1) || (arguments.size() > 3)) {
350 throw SyntaxError();
351 }
352
353 switch (arguments.size()) {
354 case 3: // command
355 command = arguments[2];
356 [[fallthrough]];
357 case 2: // condition
358 condition = arguments[1];
359 [[fallthrough]];
360 case 1: { // address
361 word addr = getAddress(getInterpreter(), arguments[0]);
362 BreakPoint bp(addr, command, condition, once);
363 result = tmpStrCat("bp#", bp.getId());
364 debugger().motherBoard.getCPUInterface().insertBreakPoint(std::move(bp));
365 break;
366 }
367 }
368}
369
370void Debugger::Cmd::removeBreakPoint(
371 std::span<const TclObject> tokens, TclObject& /*result*/)
372{
373 checkNumArgs(tokens, 3, "id|address");
374 auto& interface = debugger().motherBoard.getCPUInterface();
375 auto& breakPoints = interface.getBreakPoints();
376
377 string_view tmp = tokens[2].getString();
378 if (tmp.starts_with("bp#")) {
379 // remove by id
380 if (auto id = StringOp::stringToBase<10, unsigned>(tmp.substr(3))) {
381 if (auto it = ranges::find(breakPoints, id, &BreakPoint::getId);
382 it != std::end(breakPoints)) {
383 interface.removeBreakPoint(*it);
384 return;
385 }
386 }
387 throw CommandException("No such breakpoint: ", tmp);
388 } else {
389 // remove by addr, only works for unconditional bp
390 word addr = getAddress(getInterpreter(), tokens[2]);
391 auto [first, last] = ranges::equal_range(breakPoints, addr, {}, &BreakPoint::getAddress);
392 auto it = std::find_if(first, last, [](auto& bp) {
393 return bp.getCondition().empty();
394 });
395 if (it == last) {
396 throw CommandException(
397 "No (unconditional) breakpoint at address: ", tmp);
398 }
399 interface.removeBreakPoint(*it);
400 }
401}
402
403void Debugger::Cmd::listBreakPoints(
404 std::span<const TclObject> /*tokens*/, TclObject& result)
405{
406 string res;
407 auto& interface = debugger().motherBoard.getCPUInterface();
408 for (const auto& bp : interface.getBreakPoints()) {
409 TclObject line = makeTclList(
410 tmpStrCat("bp#", bp.getId()),
411 tmpStrCat("0x", hex_string<4>(bp.getAddress())),
412 bp.getCondition(),
413 bp.getCommand());
414 strAppend(res, line.getString(), '\n');
415 }
416 result = res;
417}
418
419
420void Debugger::Cmd::setWatchPoint(std::span<const TclObject> tokens, TclObject& result)
421{
422 checkNumArgs(tokens, AtLeast{4}, Prefix{2}, "type address ?-once? ?condition? ?command?");
423 TclObject command("debug break");
424 TclObject condition;
425 unsigned beginAddr, endAddr;
426 WatchPoint::Type type;
427 bool once = false;
428
429 std::array info = {flagArg("-once", once)};
430 auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(2), info);
431 if ((arguments.size() < 2) || (arguments.size() > 4)) {
432 throw SyntaxError();
433 }
434
435 switch (arguments.size()) {
436 case 4: // command
437 command = arguments[3];
438 [[fallthrough]];
439 case 3: // condition
440 condition = arguments[2];
441 [[fallthrough]];
442 case 2: { // address + type
443 string_view typeStr = arguments[0].getString();
444 unsigned max = [&] {
445 if (typeStr == "read_io") {
446 type = WatchPoint::READ_IO;
447 return 0x100;
448 } else if (typeStr == "write_io") {
450 return 0x100;
451 } else if (typeStr == "read_mem") {
453 return 0x10000;
454 } else if (typeStr == "write_mem") {
456 return 0x10000;
457 } else {
458 throw CommandException("Invalid type: ", typeStr);
459 }
460 }();
461 auto& interp = getInterpreter();
462 if (arguments[1].getListLength(interp) == 2) {
463 beginAddr = arguments[1].getListIndex(interp, 0).getInt(interp);
464 endAddr = arguments[1].getListIndex(interp, 1).getInt(interp);
465 if (endAddr < beginAddr) {
466 throw CommandException(
467 "Not a valid range: end address may "
468 "not be smaller than begin address.");
469 }
470 } else {
471 beginAddr = endAddr = arguments[1].getInt(interp);
472 }
473 if (endAddr >= max) {
474 throw CommandException("Invalid address: out of range");
475 }
476 break;
477 }
478 default:
479 UNREACHABLE; break;
480 }
481 unsigned id = debugger().setWatchPoint(
482 command, condition, type, beginAddr, endAddr, once);
483 result = tmpStrCat("wp#", id);
484}
485
486void Debugger::Cmd::removeWatchPoint(
487 std::span<const TclObject> tokens, TclObject& /*result*/)
488{
489 checkNumArgs(tokens, 3, "id");
490 string_view tmp = tokens[2].getString();
491 if (tmp.starts_with("wp#")) {
492 // remove by id
493 if (auto id = StringOp::stringToBase<10, unsigned>(tmp.substr(3))) {
494 auto& interface = debugger().motherBoard.getCPUInterface();
495 for (auto& wp : interface.getWatchPoints()) {
496 if (wp->getId() == *id) {
497 interface.removeWatchPoint(wp);
498 return;
499 }
500 }
501 }
502 }
503 throw CommandException("No such watchpoint: ", tmp);
504}
505
506void Debugger::Cmd::listWatchPoints(
507 std::span<const TclObject> /*tokens*/, TclObject& result)
508{
509 string res;
510 auto& interface = debugger().motherBoard.getCPUInterface();
511 for (const auto& wp : interface.getWatchPoints()) {
512 TclObject line = makeTclList(tmpStrCat("wp#", wp->getId()));
513 string type;
514 switch (wp->getType()) {
516 type = "read_io";
517 break;
519 type = "write_io";
520 break;
522 type = "read_mem";
523 break;
525 type = "write_mem";
526 break;
527 default:
528 UNREACHABLE; break;
529 }
530 line.addListElement(type);
531 unsigned beginAddr = wp->getBeginAddress();
532 unsigned endAddr = wp->getEndAddress();
533 if (beginAddr == endAddr) {
534 line.addListElement(tmpStrCat("0x", hex_string<4>(beginAddr)));
535 } else {
536 line.addListElement(makeTclList(
537 tmpStrCat("0x", hex_string<4>(beginAddr)),
538 tmpStrCat("0x", hex_string<4>(endAddr))));
539 }
540 line.addListElement(wp->getCondition(),
541 wp->getCommand());
542 strAppend(res, line.getString(), '\n');
543 }
544 result = res;
545}
546
547
548void Debugger::Cmd::setCondition(std::span<const TclObject> tokens, TclObject& result)
549{
550 checkNumArgs(tokens, AtLeast{3}, "condition ?-once? ?command?");
551 TclObject command("debug break");
552 TclObject condition;
553 bool once = false;
554
555 std::array info = {flagArg("-once", once)};
556 auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(2), info);
557 if ((arguments.size() < 1) || (arguments.size() > 2)) {
558 throw SyntaxError();
559 }
560
561 switch (arguments.size()) {
562 case 2: // command
563 command = arguments[1];
564 [[fallthrough]];
565 case 1: { // condition
566 condition = arguments[0];
567 DebugCondition dc(command, condition, once);
568 result = tmpStrCat("cond#", dc.getId());
569 debugger().motherBoard.getCPUInterface().setCondition(std::move(dc));
570 break;
571 }
572 }
573}
574
575void Debugger::Cmd::removeCondition(
576 std::span<const TclObject> tokens, TclObject& /*result*/)
577{
578 checkNumArgs(tokens, 3, "id");
579
580 string_view tmp = tokens[2].getString();
581 if (tmp.starts_with("cond#")) {
582 // remove by id
583 if (auto id = StringOp::stringToBase<10, unsigned>(tmp.substr(5))) {
584 auto& interface = debugger().motherBoard.getCPUInterface();
585 for (auto& c : interface.getConditions()) {
586 if (c.getId() == *id) {
587 interface.removeCondition(c);
588 return;
589 }
590 }
591 }
592 }
593 throw CommandException("No such condition: ", tmp);
594}
595
596void Debugger::Cmd::listConditions(
597 std::span<const TclObject> /*tokens*/, TclObject& result)
598{
599 string res;
600 auto& interface = debugger().motherBoard.getCPUInterface();
601 for (const auto& c : interface.getConditions()) {
602 TclObject line = makeTclList(tmpStrCat("cond#", c.getId()),
603 c.getCondition(),
604 c.getCommand());
605 strAppend(res, line.getString(), '\n');
606 }
607 result = res;
608}
609
610
611void Debugger::Cmd::probe(std::span<const TclObject> tokens, TclObject& result)
612{
613 checkNumArgs(tokens, AtLeast{3}, "subcommand ?arg ...?");
614 executeSubCommand(tokens[2].getString(),
615 "list", [&]{ probeList(tokens, result); },
616 "desc", [&]{ probeDesc(tokens, result); },
617 "read", [&]{ probeRead(tokens, result); },
618 "set_bp", [&]{ probeSetBreakPoint(tokens, result); },
619 "remove_bp", [&]{ probeRemoveBreakPoint(tokens, result); },
620 "list_bp", [&]{ probeListBreakPoints(tokens, result); });
621}
622void Debugger::Cmd::probeList(std::span<const TclObject> /*tokens*/, TclObject& result)
623{
624 result.addListElements(view::transform(debugger().probes,
625 [](auto* p) { return p->getName(); }));
626}
627void Debugger::Cmd::probeDesc(std::span<const TclObject> tokens, TclObject& result)
628{
629 checkNumArgs(tokens, 4, "probe");
630 result = debugger().getProbe(tokens[3].getString()).getDescription();
631}
632void Debugger::Cmd::probeRead(std::span<const TclObject> tokens, TclObject& result)
633{
634 checkNumArgs(tokens, 4, "probe");
635 result = debugger().getProbe(tokens[3].getString()).getValue();
636}
637void Debugger::Cmd::probeSetBreakPoint(
638 std::span<const TclObject> tokens, TclObject& result)
639{
640 checkNumArgs(tokens, AtLeast{4}, "probe ?-once? ?condition? ?command?");
641 TclObject command("debug break");
642 TclObject condition;
643 ProbeBase* p;
644 bool once = false;
645
646 std::array info = {flagArg("-once", once)};
647 auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(3), info);
648 if ((arguments.size() < 1) || (arguments.size() > 3)) {
649 throw SyntaxError();
650 }
651
652 switch (arguments.size()) {
653 case 3: // command
654 command = arguments[2];
655 [[fallthrough]];
656 case 2: // condition
657 condition = arguments[1];
658 [[fallthrough]];
659 case 1: { // probe
660 p = &debugger().getProbe(arguments[0].getString());
661 break;
662 }
663 default:
664 UNREACHABLE; break;
665 }
666
667 unsigned id = debugger().insertProbeBreakPoint(command, condition, *p, once);
668 result = tmpStrCat("pp#", id);
669}
670void Debugger::Cmd::probeRemoveBreakPoint(
671 std::span<const TclObject> tokens, TclObject& /*result*/)
672{
673 checkNumArgs(tokens, 4, "id");
674 debugger().removeProbeBreakPoint(tokens[3].getString());
675}
676void Debugger::Cmd::probeListBreakPoints(
677 std::span<const TclObject> /*tokens*/, TclObject& result)
678{
679 string res;
680 for (auto& p : debugger().probeBreakPoints) {
681 TclObject line = makeTclList(tmpStrCat("pp#", p->getId()),
682 p->getProbe().getName(),
683 p->getCondition(),
684 p->getCommand());
685 strAppend(res, line.getString(), '\n');
686 }
687 result = res;
688}
689
690string Debugger::Cmd::help(std::span<const TclObject> tokens) const
691{
692 auto generalHelp =
693 "debug <subcommand> [<arguments>]\n"
694 " Possible subcommands are:\n"
695 " list returns a list of all debuggables\n"
696 " desc returns a description of this debuggable\n"
697 " size returns the size of this debuggable\n"
698 " read read a byte from a debuggable\n"
699 " write write a byte to a debuggable\n"
700 " read_block read a whole block at once\n"
701 " write_block write a whole block at once\n"
702 " set_bp insert a new breakpoint\n"
703 " remove_bp remove a certain breakpoint\n"
704 " list_bp list the active breakpoints\n"
705 " set_watchpoint insert a new watchpoint\n"
706 " remove_watchpoint remove a certain watchpoint\n"
707 " list_watchpoints list the active watchpoints\n"
708 " set_condition insert a new condition\n"
709 " remove_condition remove a certain condition\n"
710 " list_conditions list the active conditions\n"
711 " probe probe related subcommands\n"
712 " cont continue execution after break\n"
713 " step execute one instruction\n"
714 " break break CPU at current position\n"
715 " breaked query CPU breaked status\n"
716 " disasm disassemble instructions\n"
717 " The arguments are specific for each subcommand.\n"
718 " Type 'help debug <subcommand>' for help about a specific subcommand.\n";
719
720 auto listHelp =
721 "debug list\n"
722 " Returns a list with the names of all 'debuggables'.\n"
723 " These names are used in other debug subcommands.\n";
724 auto descHelp =
725 "debug desc <name>\n"
726 " Returns a description for the debuggable with given name.\n";
727 auto sizeHelp =
728 "debug size <name>\n"
729 " Returns the size (in bytes) of the debuggable with given name.\n";
730 auto readHelp =
731 "debug read <name> <addr>\n"
732 " Read a byte at offset <addr> from the given debuggable.\n"
733 " The offset must be smaller than the value returned from the "
734 "'size' subcommand\n"
735 " Note that openMSX comes with a bunch of Tcl scripts that make "
736 "some of the debug reads much more convenient (e.g. reading from "
737 "Z80 or VDP registers). See the Console Command Reference for more "
738 "details about these.\n";
739 auto writeHelp =
740 "debug write <name> <addr> <val>\n"
741 " Write a byte to the given debuggable at a certain offset.\n"
742 " The offset must be smaller than the value returned from the "
743 "'size' subcommand\n";
744 auto readBlockHelp =
745 "debug read_block <name> <addr> <size>\n"
746 " Read a whole block at once. This is equivalent with repeated "
747 "invocations of the 'read' subcommand, but using this subcommand "
748 "may be faster. The result is a Tcl binary string (see Tcl manual).\n"
749 " The block is specified as size/offset in the debuggable. The "
750 "complete block must fit in the debuggable (see the 'size' "
751 "subcommand).\n";
752 auto writeBlockHelp =
753 "debug write_block <name> <addr> <values>\n"
754 " Write a whole block at once. This is equivalent with repeated "
755 "invocations of the 'write' subcommand, but using this subcommand "
756 "may be faster. The <values> argument must be a Tcl binary string "
757 "(see Tcl manual).\n"
758 " The block has a size and an offset in the debuggable. The "
759 "complete block must fit in the debuggable (see the 'size' "
760 "subcommand).\n";
761 auto setBpHelp =
762 "debug set_bp [-once] <addr> [<cond>] [<cmd>]\n"
763 " Insert a new breakpoint at given address. When the CPU is about "
764 "to execute the instruction at this address, execution will be "
765 "breaked. At least this is the default behaviour, see next "
766 "paragraphs.\n"
767 " When the -once flag is given, the breakpoint is automatically "
768 "removed after it triggered. In other words: it only triggers once.\n"
769 " Optionally you can specify a condition. When the CPU reaches "
770 "the breakpoint this condition is evaluated, only when the condition "
771 "evaluated to true execution will be breaked.\n"
772 " A condition must be specified as a Tcl expression. For example\n"
773 " debug set_bp 0xf37d {[reg C] == 0x2F}\n"
774 " This breaks on address 0xf37d but only when Z80 register C has the "
775 "value 0x2F.\n"
776 " Also optionally you can specify a command that should be "
777 "executed when the breakpoint is reached (and condition is true). "
778 "By default this command is 'debug break'.\n"
779 " The result of this command is a breakpoint ID. This ID can "
780 "later be used to remove this breakpoint again.\n";
781 auto removeBpHelp =
782 "debug remove_bp <id>\n"
783 " Remove the breakpoint with given ID again. You can use the "
784 "'list_bp' subcommand to see all valid IDs.\n";
785 auto listBpHelp =
786 "debug list_bp\n"
787 " Lists all active breakpoints. The result is printed in 4 "
788 "columns. The first column contains the breakpoint ID. The "
789 "second one has the address. The third has the condition "
790 "(default condition is empty). And the last column contains "
791 "the command that will be executed (default is 'debug break').\n";
792 auto setWatchPointHelp =
793 "debug set_watchpoint [-once] <type> <region> [<cond>] [<cmd>]\n"
794 " Insert a new watchpoint of given type on the given region, "
795 "there can be an optional -once flag, a condition and alternative "
796 "command. See the 'set_bp' subcommand for details about these.\n"
797 " Type must be one of the following:\n"
798 " read_io break when CPU reads from given IO port(s)\n"
799 " write_io break when CPU writes to given IO port(s)\n"
800 " read_mem break when CPU reads from given memory location(s)\n"
801 " write_mem break when CPU writes to given memory location(s)\n"
802 " Region is either a single value, this corresponds to a single "
803 "memory location or IO port. Otherwise region must be a list of "
804 "two values (enclosed in braces) that specify a begin and end "
805 "point of a whole memory region or a range of IO ports.\n"
806 "During the execution of <cmd>, the following global Tcl "
807 "variables are set:\n"
808 " ::wp_last_address this is the actual address of the mem/io "
809 "read/write that triggered the watchpoint\n"
810 " ::wp_last_value this is the actual value that was written "
811 "by the mem/io write that triggered the watchpoint\n"
812 "Examples:\n"
813 " debug set_watchpoint write_io 0x99 {[reg A] == 0x81}\n"
814 " debug set_watchpoint read_mem {0xfbe5 0xfbef}\n";
815 auto removeWatchPointHelp =
816 "debug remove_watchpoint <id>\n"
817 " Remove the watchpoint with given ID again. You can use the "
818 "'list_watchpoints' subcommand to see all valid IDs.\n";
819 auto listWatchPointsHelp =
820 "debug list_watchpoints\n"
821 " Lists all active watchpoints. The result is similar to the "
822 "'list_bp' subcommand, but there is an extra column (2nd column) "
823 "that contains the type of the watchpoint.\n";
824 auto setCondHelp =
825 "debug set_condition [-once] <cond> [<cmd>]\n"
826 " Insert a new condition. These are much like breakpoints, "
827 "except that they are checked before every instruction "
828 "(breakpoints are tied to a specific address).\n"
829 " Conditions will slow down simulation MUCH more than "
830 "breakpoints. So only use them when you don't care about "
831 "simulation speed (when you're debugging this is usually not "
832 "a problem).\n"
833 " See 'help debug set_bp' for more details.\n";
834 auto removeCondHelp =
835 "debug remove_condition <id>\n"
836 " Remove the condition with given ID again. You can use the "
837 "'list_conditions' subcommand to see all valid IDs.\n";
838 auto listCondHelp =
839 "debug list_conditions\n"
840 " Lists all active conditions. The result is similar to the "
841 "'list_bp' subcommand, but without the 2nd column that would "
842 "show the address.\n";
843 auto probeHelp =
844 "debug probe <subcommand> [<arguments>]\n"
845 " Possible subcommands are:\n"
846 " list returns a list of all probes\n"
847 " desc <probe> returns a description of this probe\n"
848 " read <probe> returns the current value of this probe\n"
849 " set_bp <probe> [-once] [<cond>] [<cmd>] set a breakpoint on the given probe\n"
850 " remove_bp <id> remove the given breakpoint\n"
851 " list_bp returns a list of breakpoints that are set on probes\n";
852 auto contHelp =
853 "debug cont\n"
854 " Continue execution after CPU was breaked.\n";
855 auto stepHelp =
856 "debug step\n"
857 " Execute one instruction. This command is only meaningful in "
858 "break mode.\n";
859 auto breakHelp =
860 "debug break\n"
861 " Immediately break CPU execution. When CPU was already breaked "
862 "this command has no effect.\n";
863 auto breakedHelp =
864 "debug breaked\n"
865 " Query the CPU breaked status. Returns '1' when CPU was "
866 "breaked, '0' otherwise.\n";
867 auto disasmHelp =
868 "debug disasm <addr>\n"
869 " Disassemble the instruction at the given address. The result "
870 "is a Tcl list. The first element in the list contains a textual "
871 "representation of the instruction, the next elements contain the "
872 "bytes that make up this instruction (thus the length of the "
873 "resulting list can be used to derive the number of bytes in the "
874 "instruction).\n"
875 " Note that openMSX comes with a 'disasm' Tcl script that is much "
876 "more convenient to use than this subcommand.";
877 auto unknownHelp =
878 "Unknown subcommand, use 'help debug' to see a list of valid "
879 "subcommands.\n";
880
881 if (tokens.size() == 1) {
882 return generalHelp;
883 } else if (tokens[1] == "list") {
884 return listHelp;
885 } else if (tokens[1] == "desc") {
886 return descHelp;
887 } else if (tokens[1] == "size") {
888 return sizeHelp;
889 } else if (tokens[1] == "read") {
890 return readHelp;
891 } else if (tokens[1] == "write") {
892 return writeHelp;
893 } else if (tokens[1] == "read_block") {
894 return readBlockHelp;
895 } else if (tokens[1] == "write_block") {
896 return writeBlockHelp;
897 } else if (tokens[1] == "set_bp") {
898 return setBpHelp;
899 } else if (tokens[1] == "remove_bp") {
900 return removeBpHelp;
901 } else if (tokens[1] == "list_bp") {
902 return listBpHelp;
903 } else if (tokens[1] == "set_watchpoint") {
904 return setWatchPointHelp;
905 } else if (tokens[1] == "remove_watchpoint") {
906 return removeWatchPointHelp;
907 } else if (tokens[1] == "list_watchpoints") {
908 return listWatchPointsHelp;
909 } else if (tokens[1] == "set_condition") {
910 return setCondHelp;
911 } else if (tokens[1] == "remove_condition") {
912 return removeCondHelp;
913 } else if (tokens[1] == "list_conditions") {
914 return listCondHelp;
915 } else if (tokens[1] == "probe") {
916 return probeHelp;
917 } else if (tokens[1] == "cont") {
918 return contHelp;
919 } else if (tokens[1] == "step") {
920 return stepHelp;
921 } else if (tokens[1] == "break") {
922 return breakHelp;
923 } else if (tokens[1] == "breaked") {
924 return breakedHelp;
925 } else if (tokens[1] == "disasm") {
926 return disasmHelp;
927 } else {
928 return unknownHelp;
929 }
930}
931
932std::vector<string> Debugger::Cmd::getBreakPointIds() const
933{
935 debugger().motherBoard.getCPUInterface().getBreakPoints(),
936 [](auto& bp) { return strCat("bp#", bp.getId()); }));
937}
938std::vector<string> Debugger::Cmd::getWatchPointIds() const
939{
941 debugger().motherBoard.getCPUInterface().getWatchPoints(),
942 [](auto& w) { return strCat("wp#", w->getId()); }));
943}
944std::vector<string> Debugger::Cmd::getConditionIds() const
945{
947 debugger().motherBoard.getCPUInterface().getConditions(),
948 [](auto& c) { return strCat("cond#", c.getId()); }));
949}
950
951void Debugger::Cmd::tabCompletion(std::vector<string>& tokens) const
952{
953 using namespace std::literals;
954 static constexpr std::array singleArgCmds = {
955 "list"sv, "step"sv, "cont"sv, "break"sv, "breaked"sv,
956 "list_bp"sv, "list_watchpoints"sv, "list_conditions"sv,
957 };
958 static constexpr std::array debuggableArgCmds = {
959 "desc"sv, "size"sv, "read"sv, "read_block"sv,
960 "write"sv, "write_block"sv,
961 };
962 static constexpr std::array otherCmds = {
963 "disasm"sv, "set_bp"sv, "remove_bp"sv, "set_watchpoint"sv,
964 "remove_watchpoint"sv, "set_condition"sv, "remove_condition"sv,
965 "probe"sv,
966 };
967 switch (tokens.size()) {
968 case 2: {
969 completeString(tokens, concatArray(singleArgCmds, debuggableArgCmds, otherCmds));
970 break;
971 }
972 case 3:
973 if (!contains(singleArgCmds, tokens[1])) {
974 // this command takes (an) argument(s)
975 if (contains(debuggableArgCmds, tokens[1])) {
976 // it takes a debuggable here
977 completeString(tokens, view::keys(debugger().debuggables));
978 } else if (tokens[1] == "remove_bp") {
979 // this one takes a bp id
980 completeString(tokens, getBreakPointIds());
981 } else if (tokens[1] == "remove_watchpoint") {
982 // this one takes a wp id
983 completeString(tokens, getWatchPointIds());
984 } else if (tokens[1] == "remove_condition") {
985 // this one takes a cond id
986 completeString(tokens, getConditionIds());
987 } else if (tokens[1] == "set_watchpoint") {
988 static constexpr std::array types = {
989 "write_io"sv, "write_mem"sv,
990 "read_io"sv, "read_mem"sv,
991 };
992 completeString(tokens, types);
993 } else if (tokens[1] == "probe") {
994 static constexpr std::array subCmds = {
995 "list"sv, "desc"sv, "read"sv, "set_bp"sv,
996 "remove_bp"sv, "list_bp"sv,
997 };
998 completeString(tokens, subCmds);
999 }
1000 }
1001 break;
1002 case 4:
1003 if ((tokens[1] == "probe") &&
1004 (tokens[2] == one_of("desc", "read", "set_bp"))) {
1005 completeString(tokens, view::transform(
1006 debugger().probes,
1007 [](auto* p) -> std::string_view { return p->getName(); }));
1008 }
1009 break;
1010 }
1011}
1012
1013} // namespace openmsx
Definition: one_of.hh:7
unsigned getId() const
Definition: BreakPoint.hh:22
word getAddress() const
Definition: BreakPoint.hh:21
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
void registerProbe(ProbeBase &probe)
Definition: Debugger.cc:73
Debuggable * findDebuggable(std::string_view name)
Definition: Debugger.cc:58
Debugger(const Debugger &)=delete
void transfer(Debugger &other)
Definition: Debugger.cc:167
void unregisterProbe(ProbeBase &probe)
Definition: Debugger.cc:79
void unregisterDebuggable(std::string_view name, Debuggable &debuggable)
Definition: Debugger.cc:51
ProbeBase * findProbe(std::string_view name)
Definition: Debugger.cc:85
void removeProbeBreakPoint(ProbeBreakPoint &bp)
Definition: Debugger.cc:141
void registerDebuggable(std::string name, Debuggable &debuggable)
Definition: Debugger.cc:45
void setWatchPoint(const std::shared_ptr< WatchPoint > &watchPoint)
const WatchPoints & getWatchPoints() const
MSXCPUInterface & getCPUInterface()
const std::string & getName() const
Definition: Probe.hh:19
int getInt(Interpreter &interp) const
Definition: TclObject.cc:73
zstring_view getString() const
Definition: TclObject.cc:120
const Value * lookup(const hash_map< Key, Value, Hasher, Equal > &map, const Key2 &key)
Definition: hash_map.hh:118
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:284
std::string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:730
This file implemented 3 utility functions:
Definition: Autofire.cc:9
std::vector< TclObject > parseTclArgs(Interpreter &interp, std::span< const TclObject > inArgs, std::span< const ArgsInfo > table)
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
ArgsInfo flagArg(std::string_view name, bool &flag)
Definition: TclArgParser.hh:72
TclObject makeTclList(Args &&... args)
Definition: TclObject.hh:283
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:173
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:160
auto equal_range(ForwardRange &&range, const T &value, Compare comp={})
Definition: ranges.hh:133
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:458
constexpr auto keys(Map &&map)
Definition: view.hh:463
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
Definition: stl.hh:266
constexpr auto concatArray(const std::array< T, X > &x, const std::array< T, Y > &y)
Definition: stl.hh:381
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:125
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:100
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:23
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:610
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:620
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:133
constexpr auto end(const zstring_view &x)