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