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