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