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