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