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