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