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