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