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