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