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