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