openMSX
ImGuiBreakPoints.cc
Go to the documentation of this file.
1#include "ImGuiBreakPoints.hh"
2
3#include "ImGuiCpp.hh"
4#include "ImGuiManager.hh"
5#include "ImGuiUtils.hh"
6
7#include "BreakPoint.hh"
8#include "Dasm.hh"
9#include "DebugCondition.hh"
10#include "Debugger.hh"
11#include "Interpreter.hh"
12#include "MSXCPU.hh"
13#include "MSXCPUInterface.hh"
14#include "MSXMotherBoard.hh"
15#include "Reactor.hh"
16#include "SymbolManager.hh"
17#include "WatchPoint.hh"
18
19#include "narrow.hh"
20#include "one_of.hh"
21#include "ranges.hh"
22#include "stl.hh"
23#include "strCat.hh"
24#include "unreachable.hh"
25
26#include <imgui.h>
27#include <imgui_stdlib.h>
28
29#include <cstdint>
30#include <tuple>
31#include <vector>
32
33using namespace std::literals;
34
35
36namespace openmsx {
37
39 : ImGuiPart(manager_)
40 , symbolManager(manager.getReactor().getSymbolManager())
41{
42}
43
44static void saveItems(zstring_view label, const std::vector<ImGuiBreakPoints::GuiItem>& items, ImGuiTextBuffer& buf)
45{
46 auto saveAddr = [](std::optional<uint16_t> addr) {
47 return addr ? TclObject(*addr) : TclObject();
48 };
49 for (const auto& item : items) {
50 auto list = makeTclList(
51 item.wantEnable,
52 item.wpType,
53 saveAddr(item.addr),
54 saveAddr(item.endAddr),
55 item.addrStr,
56 item.endAddrStr,
57 item.cond,
58 item.cmd);
59 buf.appendf("%s=%s\n", label.c_str(), list.getString().c_str());
60 }
61}
62
63void ImGuiBreakPoints::save(ImGuiTextBuffer& buf)
64{
65 savePersistent(buf, *this, persistentElements);
66 saveItems("breakpoint", guiBps, buf);
67 saveItems("watchpoint", guiWps, buf);
68 saveItems("condition", guiConditions, buf);
69}
70
71template<typename Item>
72void ImGuiBreakPoints::loadItem(zstring_view value)
73{
74 auto& interp = manager.getInterpreter();
75
76 auto loadAddr = [&](const TclObject& o) -> std::optional<uint16_t> {
77 if (o.getString().empty()) return {};
78 return o.getInt(interp);
79 };
80
81 try {
82 TclObject list(value);
83 if (list.getListLength(interp) != 8) return; // ignore
84 GuiItem item {
85 .id = --idCounter,
86 .wantEnable = list.getListIndex(interp, 0).getBoolean(interp),
87 .wpType = list.getListIndex(interp, 1).getInt(interp),
88 .addr = loadAddr(list.getListIndex(interp, 2)),
89 .endAddr = loadAddr(list.getListIndex(interp, 3)),
90 .addrStr = list.getListIndex(interp, 4),
91 .endAddrStr = list.getListIndex(interp, 5),
92 .cond = list.getListIndex(interp, 6),
93 .cmd = list.getListIndex(interp, 7),
94 };
95 if (item.wpType < 0 || item.wpType > 3) return;
96
97 Item* tag = nullptr;
98 auto& items = getItems(tag);
99 items.push_back(std::move(item));
100
101 if (auto* motherBoard = manager.getReactor().getMotherBoard()) {
102 auto& cpuInterface = motherBoard->getCPUInterface();
103 auto& debugger = motherBoard->getDebugger();
104 syncToOpenMsx<Item>(cpuInterface, debugger, interp, items.back());
105 }
106 } catch (CommandException&) {
107 // ignore
108 }
109}
110
112{
113 if (auto* motherBoard = manager.getReactor().getMotherBoard()) {
114 auto& cpuInterface = motherBoard->getCPUInterface();
115 clear(static_cast<BreakPoint *>(nullptr), cpuInterface);
116 clear(static_cast<WatchPoint *>(nullptr), cpuInterface);
117 clear(static_cast<DebugCondition*>(nullptr), cpuInterface);
118 }
119}
120
121void ImGuiBreakPoints::loadLine(std::string_view name, zstring_view value)
122{
123 if (loadOnePersistent(name, value, *this, persistentElements)) {
124 // already handled
125 } else if (name == "breakpoint"sv) {
126 loadItem<BreakPoint>(value);
127 } else if (name == "watchpoint"sv) {
128 loadItem<WatchPoint>(value);
129 } else if (name == "condition"sv) {
130 loadItem<DebugCondition>(value);
131 }
132}
133
135{
136 refreshSymbols(); // after both breakpoints and symbols have been loaded
137}
138
140{
141 if (!show || !motherBoard) return;
142
143 ImGui::SetNextWindowSize(gl::vec2{25, 14} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
144 im::Window("Breakpoints", &show, [&]{
145 im::TabBar("tabs", [&]{
146 auto& cpuInterface = motherBoard->getCPUInterface();
147 auto& debugger = motherBoard->getDebugger();
148 im::TabItem("Breakpoints", [&]{
149 paintTab<BreakPoint>(cpuInterface, debugger);
150 });
151 im::TabItem("Watchpoints", [&]{
152 paintTab<WatchPoint>(cpuInterface, debugger);
153 });
154 im::TabItem("Conditions", [&]{
155 paintTab<DebugCondition>(cpuInterface, debugger);
156 });
157 });
158 });
159}
160
161static std::string_view getCheckCmd(BreakPoint*) { return "pc_in_slot"; }
162static std::string_view getCheckCmd(WatchPoint*) { return "watch_in_slot"; }
163static std::string_view getCheckCmd(DebugCondition*) { return "pc_in_slot"; }
164
165ParsedSlotCond::ParsedSlotCond(std::string_view checkCmd, std::string_view cond)
166 : rest(cond) // for when we fail to match a slot-expression
167{
168 size_t pos = 0;
169 auto end = cond.size();
170 std::optional<int> o_ps;
171 std::optional<int> o_ss;
172 std::optional<int> o_seg;
173
174 auto next = [&](std::string_view s) {
175 if (cond.substr(pos).starts_with(s)) {
176 pos += s.size();
177 return true;
178 }
179 return false;
180 };
181 auto done = [&]{
182 bool stop = cond.substr(pos) == "]";
183 if (stop || (next("] && (") && cond.ends_with(')'))) {
184 if (stop) {
185 rest.clear();
186 } else {
187 rest = cond.substr(pos, cond.size() - pos - 1);
188 }
189 if (o_ps) { hasPs = true; ps = *o_ps; }
190 if (o_ss) { hasSs = true; ss = *o_ss; }
191 if (o_seg) { hasSeg = true; seg = narrow<uint8_t>(*o_seg); }
192 return true;
193 }
194 return false;
195 };
196 auto isDigit = [](char c) { return ('0' <= c) && (c <= '9'); };
197 auto getInt = [&](unsigned max) -> std::optional<int> {
198 unsigned i = 0;
199 if ((pos == end) || !isDigit(cond[pos])) return {};
200 while ((pos != end) && isDigit(cond[pos])) {
201 i = 10 * i + (cond[pos] - '0');
202 ++pos;
203 }
204 if (i >= max) return {};
205 return i;
206 };
207
208 if (!next(tmpStrCat('[', checkCmd, ' '))) return; // no slot
209 o_ps = getInt(4);
210 if (!o_ps) return; // invalid ps
211 if (done()) return; // ok, only ps
212 if (!next(" ")) return; // invalid separator
213 if (!next("X")) {
214 o_ss = getInt(4);
215 if (!o_ss) return; // invalid ss
216 }
217 if (done()) return; // ok, ps + ss
218 if (!next(" ")) return; // invalid separator
219 o_seg = getInt(256);
220 if (!o_seg) return; // invalid seg
221 if (done()) return; // ok, ps + ss + seg
222 // invalid terminator
223}
224
225std::string ParsedSlotCond::toTclExpression(std::string_view checkCmd) const
226{
227 if (!hasPs) return rest;
228 std::string result = strCat('[', checkCmd, ' ', ps);
229 if (hasSs) {
230 strAppend(result, ' ', ss);
231 } else {
232 if (hasSeg) strAppend(result, " X");
233 }
234 if (hasSeg) {
235 strAppend(result, ' ', seg);
236 }
237 strAppend(result, ']');
238 if (!rest.empty()) {
239 strAppend(result, " && (", rest, ')');
240 }
241 return result;
242}
243
245{
246 if (!hasPs) return rest;
247 std::string result = strCat("Slot:", ps, '-');
248 if (hasSs) {
249 strAppend(result, ss);
250 } else {
251 strAppend(result, 'X');
252 }
253 if (hasSeg) {
254 strAppend(result, ',', seg);
255 }
256 if (!rest.empty()) {
257 strAppend(result, " && ", rest);
258 }
259 return result;
260}
261
262template<typename Item> struct HasAddress : std::true_type {};
263template<> struct HasAddress<DebugCondition> : std::false_type {};
264
265template<typename Item> struct AllowEmptyCond : std::true_type {};
266template<> struct AllowEmptyCond<DebugCondition> : std::false_type {};
267
268static void remove(BreakPoint*, MSXCPUInterface& cpuInterface, unsigned id)
269{
270 cpuInterface.removeBreakPoint(id);
271}
272static void remove(WatchPoint*, MSXCPUInterface& cpuInterface, unsigned id)
273{
274 cpuInterface.removeWatchPoint(id);
275}
276static void remove(DebugCondition*, MSXCPUInterface& cpuInterface, unsigned id)
277{
278 cpuInterface.removeCondition(id);
279}
280
281void ImGuiBreakPoints::clear(BreakPoint* tag, MSXCPUInterface& cpuInterface)
282{
283 while (!guiBps.empty()) {
284 if (const auto& bp = guiBps.back(); bp.id > 0) {
285 remove(tag, cpuInterface, bp.id);
286 }
287 guiBps.pop_back();
288 }
289}
290void ImGuiBreakPoints::clear(WatchPoint* tag, MSXCPUInterface& cpuInterface)
291{
292 while (!guiWps.empty()) {
293 if (auto& wp = guiWps.back(); wp.id > 0) {
294 remove(tag, cpuInterface, wp.id);
295 }
296 guiWps.pop_back();
297 }
298}
299
300void ImGuiBreakPoints::clear(DebugCondition* tag, MSXCPUInterface& cpuInterface)
301{
302 while (!guiConditions.empty()) {
303 if (auto& cond = guiConditions.back(); cond.id > 0) {
304 remove(tag, cpuInterface, cond.id);
305 }
306 guiConditions.pop_back();
307 }
308}
309
310void ImGuiBreakPoints::paintBpTab(MSXCPUInterface& cpuInterface, Debugger& debugger, uint16_t addr)
311{
312 paintTab<BreakPoint>(cpuInterface, debugger, addr);
313}
314
315template<typename Item>
316void ImGuiBreakPoints::paintTab(MSXCPUInterface& cpuInterface, Debugger& debugger, std::optional<uint16_t> addr)
317{
318 constexpr bool isWatchPoint = std::is_same_v<Item, WatchPoint>;
319 constexpr bool isCondition = std::is_same_v<Item, DebugCondition>;
320 bool hasAddress = HasAddress<Item>{} && !addr; // don't draw address-column if filtering a specific address
321 Item* tag = nullptr;
322 auto& items = getItems(tag);
323
324 int flags = ImGuiTableFlags_RowBg |
325 ImGuiTableFlags_BordersV |
326 ImGuiTableFlags_BordersOuter |
327 ImGuiTableFlags_Resizable |
328 ImGuiTableFlags_Sortable |
329 ImGuiTableFlags_Hideable |
330 ImGuiTableFlags_Reorderable |
331 ImGuiTableFlags_ContextMenuInBody |
332 ImGuiTableFlags_SizingStretchProp;
333 if (!addr) {
334 flags |= ImGuiTableFlags_ScrollY
335 | ImGuiTableFlags_ScrollX;
336 }
337 const auto& style = ImGui::GetStyle();
338 auto width = style.ItemSpacing.x + 2.0f * style.FramePadding.x + ImGui::CalcTextSize("Remove").x;
339 bool disableRemove = true;
340 int count = 0;
341 int lastDrawnRow = -1; // should only be used when count=1
342 im::Table("items", 5, flags, {-width, 0}, [&]{
343 syncFromOpenMsx<Item>(items, cpuInterface);
344
345 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
346 ImGui::TableSetupColumn("Enable", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort);
347 int typeFlags = isWatchPoint ? ImGuiTableColumnFlags_NoHide : ImGuiTableColumnFlags_Disabled;
348 ImGui::TableSetupColumn("Type", typeFlags);
349 int addressFlags = hasAddress ? ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_DefaultSort
350 : ImGuiTableColumnFlags_Disabled;
351 ImGui::TableSetupColumn("Address", addressFlags);
352 ImGui::TableSetupColumn("Condition", isCondition ? ImGuiTableColumnFlags_NoHide : 0);
353 ImGui::TableSetupColumn("Action", addr ? 0 : ImGuiTableColumnFlags_DefaultHide);
354 ImGui::TableHeadersRow();
355
356 checkSort(items);
357
358 im::ID_for_range(items.size(), [&](int row) {
359 if (!addr || (items[row].addr == addr)) {
360 ++count; lastDrawnRow = row;
361 if (row == selectedRow) disableRemove = false;
362 drawRow<Item>(cpuInterface, debugger, row, items[row]);
363 }
364 });
365 });
366 if (count == 1) disableRemove = false;
367 ImGui::SameLine();
368 im::Group([&] {
369 if (ImGui::Button("Add")) {
370 --idCounter;
371 GuiItem item{
372 idCounter,
373 true, // enabled, but still invalid
375 {}, {}, {}, {}, // address
376 {}, // cond
377 makeTclList("debug", "break")};
378 if (addr) {
379 item.wantEnable = false;
380 item.addr = addr;
381 item.addrStr = TclObject(tmpStrCat("0x", hex_string<4>(*addr)));
382 }
383 items.push_back(std::move(item));
384 selectedRow = -1;
385 }
386 im::Disabled(disableRemove, [&]{
387 if (ImGui::Button("Remove")) {
388 int removeRow = (count == 1) ? lastDrawnRow : selectedRow;
389 auto it = items.begin() + removeRow;
390 if (it->id > 0) {
391 remove(tag, cpuInterface, it->id);
392 }
393 items.erase(it);
394 selectedRow = -1;
395 }
396 });
397 if (!addr) {
398 ImGui::Spacing();
399 im::Disabled(items.empty() ,[&]{
400 if (ImGui::Button("Clear")) {
401 clear(tag, cpuInterface);
402 }
403 });
404 }
405 });
406}
407
408static const std::vector<BreakPoint>& getOpenMSXItems(BreakPoint*, const MSXCPUInterface&)
409{
410 return MSXCPUInterface::getBreakPoints();
411}
412static const std::vector<std::shared_ptr<WatchPoint>>& getOpenMSXItems(WatchPoint*, const MSXCPUInterface& cpuInterface)
413{
414 return cpuInterface.getWatchPoints();
415}
416static const std::vector<DebugCondition>& getOpenMSXItems(DebugCondition*, const MSXCPUInterface&)
417{
418 return MSXCPUInterface::getConditions();
419}
420
421[[nodiscard]] static unsigned getId(const BreakPoint& bp) { return bp.getId(); }
422[[nodiscard]] static unsigned getId(const std::shared_ptr<WatchPoint>& wp) { return wp->getId(); }
423[[nodiscard]] static unsigned getId(const DebugCondition& cond) { return cond.getId(); }
424
425[[nodiscard]] static uint16_t getAddress(const BreakPoint& bp) { return bp.getAddress(); }
426[[nodiscard]] static uint16_t getAddress(const std::shared_ptr<WatchPoint>& wp) { return narrow<uint16_t>(wp->getBeginAddress()); }
427[[nodiscard]] static uint16_t getAddress(const DebugCondition& cond) = delete;
428
429[[nodiscard]] static uint16_t getEndAddress(const BreakPoint& bp) { return bp.getAddress(); } // same as begin
430[[nodiscard]] static uint16_t getEndAddress(const std::shared_ptr<WatchPoint>& wp) { return narrow<uint16_t>(wp->getEndAddress()); }
431[[nodiscard]] static uint16_t getEndAddress(const DebugCondition& cond) = delete;
432
433[[nodiscard]] static TclObject getConditionObj(const BreakPointBase& bp) { return bp.getConditionObj(); }
434[[nodiscard]] static TclObject getConditionObj(const std::shared_ptr<WatchPoint>& wp) { return wp->getConditionObj(); }
435
436[[nodiscard]] static TclObject getCommandObj(const BreakPointBase& bp) { return bp.getCommandObj(); }
437[[nodiscard]] static TclObject getCommandObj(const std::shared_ptr<WatchPoint>& wp) { return wp->getCommandObj(); }
438
439
440[[nodiscard]] std::vector<ImGuiBreakPoints::GuiItem>& ImGuiBreakPoints::getBps(MSXCPUInterface& cpuInterface)
441{
442 syncFromOpenMsx<BreakPoint>(guiBps, cpuInterface);
443 return guiBps;
444}
445
446template<typename Item>
447void ImGuiBreakPoints::syncFromOpenMsx(std::vector<GuiItem>& items, MSXCPUInterface& cpuInterface)
448{
449 constexpr bool hasAddress = HasAddress<Item>{};
450 constexpr bool isWatchPoint = std::is_same_v<Item, WatchPoint>;
451 Item* tag = nullptr;
452
453 // remove items that no longer exist on the openMSX side
454 const auto& openMsxItems = getOpenMSXItems(tag, cpuInterface);
455 std::erase_if(items, [&](auto& item) {
456 int id = item.id;
457 if (id < 0) return false; // keep disabled bp
458 bool remove = !contains(openMsxItems, unsigned(id), [](const auto& i) { return getId(i); });
459 if (remove) {
460 selectedRow = -1;
461 }
462 return remove;
463 });
464 for (const auto& item : openMsxItems) {
465 auto formatAddr = [&](uint16_t addr) {
466 if (auto syms = symbolManager.lookupValue(addr); !syms.empty()) {
467 return TclObject(syms.front()->name);
468 }
469 return TclObject(tmpStrCat("0x", hex_string<4>(addr)));
470 };
471 if (auto it = ranges::find(items, narrow<int>(getId(item)), &GuiItem::id);
472 it != items.end()) {
473 // item exists on the openMSX side, make sure it's in sync
474 if constexpr (isWatchPoint) {
475 it->wpType = to_underlying(item->getType());
476 }
477 if constexpr (hasAddress) {
478 assert(it->addr);
479 auto addr = getAddress(item);
480 auto endAddr = getEndAddress(item);
481 bool needUpdate =
482 (*it->addr != addr) ||
483 (it->endAddr && (it->endAddr != endAddr)) ||
484 (!it->endAddr && (addr != endAddr));
485 if (needUpdate) {
486 it->addr = addr;
487 it->endAddr = (addr != endAddr) ? std::optional<uint16_t>(endAddr) : std::nullopt;
488 it->addrStr = formatAddr(addr);
489 it->endAddrStr = (addr != endAddr) ? formatAddr(endAddr) : TclObject{};
490 }
491 } else {
492 assert(!it->addr);
493 }
494 it->cond = getConditionObj(item);
495 it->cmd = getCommandObj(item);
496 } else {
497 // item was added on the openMSX side, copy to the GUI side
498 WatchPoint::Type wpType = WatchPoint::Type::WRITE_MEM;
499 std::optional<uint16_t> addr;
500 std::optional<uint16_t> endAddr;
501 TclObject addrStr;
502 TclObject endAddrStr;
503 if constexpr (isWatchPoint) {
504 wpType = item->getType();
505 }
506 if constexpr (hasAddress) {
507 addr = getAddress(item);
508 endAddr = getEndAddress(item);
509 if (*addr == *endAddr) endAddr.reset();
510 addrStr = formatAddr(*addr);
511 if (endAddr) endAddrStr = formatAddr(*endAddr);
512 }
513 items.push_back(GuiItem{
514 narrow<int>(getId(item)),
515 true,
516 to_underlying(wpType),
517 addr, endAddr, std::move(addrStr), std::move(endAddrStr),
518 getConditionObj(item), getCommandObj(item)});
519 selectedRow = -1;
520 }
521 }
522}
523
524void ImGuiBreakPoints::checkSort(std::vector<GuiItem>& items) const
525{
526 auto* sortSpecs = ImGui::TableGetSortSpecs();
527 if (!sortSpecs->SpecsDirty) return;
528
529 sortSpecs->SpecsDirty = false;
530 assert(sortSpecs->SpecsCount == 1);
531 assert(sortSpecs->Specs);
532 assert(sortSpecs->Specs->SortOrder == 0);
533
534 switch (sortSpecs->Specs->ColumnIndex) {
535 case 1: // addr
536 sortUpDown_T(items, sortSpecs, &GuiItem::wpType);
537 break;
538 case 2: // addr
539 sortUpDown_T(items, sortSpecs, [](const auto& item) {
540 return std::tuple(item.addr, item.endAddr,
541 item.addrStr.getString(),
542 item.endAddrStr.getString());
543 });
544 break;
545 case 3: // cond
546 sortUpDown_String(items, sortSpecs, [](const auto& item) { return item.cond.getString(); });
547 break;
548 case 4: // action
549 sortUpDown_String(items, sortSpecs, [](const auto& item) { return item.cmd.getString(); });
550 break;
551 default:
553 }
554}
555
556std::optional<uint16_t> ImGuiBreakPoints::parseAddress(const TclObject& o)
557{
558 return symbolManager.parseSymbolOrValue(o.getString());
559}
560
561template<typename Item>
562static bool isValidAddr(const ImGuiBreakPoints::GuiItem& i)
563{
564 constexpr bool hasAddress = HasAddress<Item>{};
565 constexpr bool isWatchPoint = std::is_same_v<Item, WatchPoint>;
566
567 if (!hasAddress) return true;
568 if (!i.addr) return false;
569 if (isWatchPoint) {
570 if (i.endAddr && (*i.endAddr < *i.addr)) return false;
571 if ((WatchPoint::Type(i.wpType) == one_of(WatchPoint::Type::READ_IO, WatchPoint::Type::WRITE_IO)) &&
572 ((*i.addr >= 0x100) || (i.endAddr && (*i.endAddr >= 0x100)))) {
573 return false;
574 }
575 }
576 return true;
577}
578
579template<typename Item>
580static bool isValidCond(std::string_view cond, Interpreter& interp)
581{
582 if (cond.empty()) return AllowEmptyCond<Item>{};
583 return interp.validExpression(cond);
584}
585
586static bool isValidCmd(std::string_view cmd, Interpreter& interp)
587{
588 return !cmd.empty() && interp.validCommand(cmd);
589}
590
591static void create(BreakPoint*, MSXCPUInterface& cpuInterface, Debugger&, ImGuiBreakPoints::GuiItem& item)
592{
593 BreakPoint newBp(*item.addr, item.cmd, item.cond, false);
594 item.id = narrow<int>(newBp.getId());
595 cpuInterface.insertBreakPoint(std::move(newBp));
596}
597static void create(WatchPoint*, MSXCPUInterface&, Debugger& debugger, ImGuiBreakPoints::GuiItem& item)
598{
599 item.id = debugger.setWatchPoint(
600 item.cmd, item.cond,
601 static_cast<WatchPoint::Type>(item.wpType),
602 *item.addr, (item.endAddr ? *item.endAddr : *item.addr),
603 false);
604}
605static void create(DebugCondition*, MSXCPUInterface& cpuInterface, Debugger&, ImGuiBreakPoints::GuiItem& item)
606{
607 DebugCondition newCond(item.cmd, item.cond, false);
608 item.id = narrow<int>(newCond.getId());
609 cpuInterface.setCondition(std::move(newCond));
610}
611
612template<typename Item>
613void ImGuiBreakPoints::syncToOpenMsx(
614 MSXCPUInterface& cpuInterface, Debugger& debugger,
615 Interpreter& interp, GuiItem& item) const
616{
617 Item* tag = nullptr;
618
619 if (item.id > 0) {
620 // (temporarily) remove it from the openMSX side
621 remove(tag, cpuInterface, item.id); // temp remove it
622 item.id = --idCounter;
623 }
624 if (item.wantEnable &&
625 isValidAddr<Item>(item) &&
626 isValidCond<Item>(item.cond.getString(), interp) &&
627 isValidCmd(item.cmd.getString(), interp)) {
628 // (re)create on the openMSX side
629 create(tag, cpuInterface, debugger, item);
630 assert(item.id > 0);
631 }
632}
633
634template<typename Item>
635void ImGuiBreakPoints::drawRow(MSXCPUInterface& cpuInterface, Debugger& debugger, int row, GuiItem& item)
636{
637 constexpr bool isWatchPoint = std::is_same_v<Item, WatchPoint>;
638 Item* tag = nullptr;
639
640 auto& interp = manager.getInterpreter();
641 const auto& style = ImGui::GetStyle();
642 float rowHeight = 2.0f * style.FramePadding.y + ImGui::GetTextLineHeight();
643
644 auto setRedBg = [](bool valid) {
645 if (valid) return;
646 ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, getColor(imColor::RED_BG));
647 };
648
649 bool needSync = false;
650 std::string cond{item.cond.getString()};
651 std::string cmd {item.cmd .getString()};
652 bool validAddr = isValidAddr<Item>(item);
653 bool validCond = isValidCond<Item>(cond, interp);
654 bool validCmd = isValidCmd(cmd, interp);
655
656 if (ImGui::TableNextColumn()) { // enable
657 auto pos = ImGui::GetCursorPos();
658 if (ImGui::Selectable("##selection", selectedRow == row,
659 ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
660 {0.0f, rowHeight})) {
661 selectedRow = row;
662 }
663 ImGui::SetCursorPos(pos);
664 im::Disabled(!validAddr || !validCond || !validCmd, [&]{
665 if (ImGui::Checkbox("##enabled", &item.wantEnable)) {
666 needSync = true;
667 }
668 if (ImGui::IsItemActive()) selectedRow = row;
669 });
670 }
671 if (ImGui::TableNextColumn()) { // type
672 ImGui::SetNextItemWidth(-FLT_MIN);
673 if (ImGui::Combo("##type", &item.wpType, "read IO\000write IO\000read memory\000write memory\000")) {
674 validAddr = isValidAddr<Item>(item);
675 needSync = true;
676 }
677 if (ImGui::IsItemActive()) selectedRow = row;
678 }
679 if (ImGui::TableNextColumn()) { // address
680 auto addrToolTip = [&]{
681 if (!item.addr) return;
682 simpleToolTip([&]{
683 auto tip = strCat("0x", hex_string<4>(*item.addr));
684 if (item.endAddr) {
685 strAppend(tip, "...0x", hex_string<4>(*item.endAddr));
686 }
687 return tip;
688 });
689 };
690 setRedBg(validAddr);
691 bool addrChanged = false;
692 std::string addr{item.addrStr.getString()};
693 std::string endAddr{item.endAddrStr.getString()};
694 ImGui::SetNextItemWidth(-FLT_MIN);
695 if constexpr (isWatchPoint) {
696 auto pos = ImGui::GetCursorPos();
697 std::string displayAddr = addr;
698 if (!endAddr.empty()) {
699 strAppend(displayAddr, "...", endAddr);
700 }
701 im::Font(manager.fontMono, [&]{
702 ImGui::TextUnformatted(displayAddr);
703 });
704 addrToolTip();
705 ImGui::SetCursorPos(pos);
706 if (ImGui::InvisibleButton("##range-button", {-FLT_MIN, rowHeight})) {
707 ImGui::OpenPopup("range-popup");
708 }
709 if (ImGui::IsItemActive()) selectedRow = row;
710 im::Popup("range-popup", [&]{
711 addrChanged |= editRange(addr, endAddr);
712 });
713 } else {
714 assert(endAddr.empty());
715 im::Font(manager.fontMono, [&]{
716 addrChanged |= ImGui::InputText("##addr", &addr);
717 });
718 addrToolTip();
719 if (ImGui::IsItemActive()) selectedRow = row;
720 }
721 if (addrChanged) {
722 item.addrStr = addr;
723 item.endAddrStr = endAddr;
724 item.addr = parseAddress(item.addrStr);
725 item.endAddr = parseAddress(item.endAddrStr);
726 if (item.endAddr && !item.addr) item.endAddr.reset();
727 needSync = true;
728 }
729 }
730 if (ImGui::TableNextColumn()) { // condition
731 setRedBg(validCond);
732 auto checkCmd = getCheckCmd(tag);
733 ParsedSlotCond slot(checkCmd, cond);
734 auto pos = ImGui::GetCursorPos();
735 im::Font(manager.fontMono, [&]{
736 ImGui::TextUnformatted(slot.toDisplayString());
737 });
738 ImGui::SetCursorPos(pos);
739 if (ImGui::InvisibleButton("##cond-button", {-FLT_MIN, rowHeight})) {
740 ImGui::OpenPopup("cond-popup");
741 }
742 if (ImGui::IsItemActive()) selectedRow = row;
743 im::Popup("cond-popup", [&]{
744 if (editCondition(slot)) {
745 cond = slot.toTclExpression(checkCmd);
746 item.cond = cond;
747 needSync = true;
748 }
749 });
750 }
751 if (ImGui::TableNextColumn()) { // action
752 setRedBg(validCmd);
753 im::Font(manager.fontMono, [&]{
754 ImGui::SetNextItemWidth(-FLT_MIN);
755 if (ImGui::InputText("##cmd", &cmd)) {
756 item.cmd = cmd;
757 needSync = true;
758 }
759 if (ImGui::IsItemActive()) selectedRow = row;
760 });
761 }
762 if (needSync) {
763 syncToOpenMsx<Item>(cpuInterface, debugger, interp, item);
764 }
765}
766
767bool ImGuiBreakPoints::editRange(std::string& begin, std::string& end)
768{
769 bool changed = false;
770 ImGui::TextUnformatted("address range"sv);
771 im::Indent([&]{
772 const auto& style = ImGui::GetStyle();
773 auto pos = ImGui::GetCursorPos().x + ImGui::CalcTextSize("end: (?)").x + 2.0f * style.ItemSpacing.x;
774
775 ImGui::AlignTextToFramePadding();
776 ImGui::TextUnformatted("begin: "sv);
777 ImGui::SameLine(pos);
778 im::Font(manager.fontMono, [&]{
779 changed |= ImGui::InputText("##begin", &begin);
780 });
781
782 ImGui::AlignTextToFramePadding();
783 ImGui::TextUnformatted("end:"sv);
784 HelpMarker("End address is included in the range.\n"
785 "Leave empty for a single address.");
786 ImGui::SameLine(pos);
787 im::Font(manager.fontMono, [&]{
788 changed |= ImGui::InputText("##end", &end);
789 });
790 });
791 return changed;
792}
793
794bool ImGuiBreakPoints::editCondition(ParsedSlotCond& slot)
795{
796 bool changed = false;
797 ImGui::TextUnformatted("slot"sv);
798 im::Indent([&]{
799 const auto& style = ImGui::GetStyle();
800 auto pos = ImGui::GetCursorPos().x + ImGui::GetFrameHeight() +
801 ImGui::CalcTextSize("secondary").x + 2.0f * style.ItemSpacing.x;
802
803 changed |= ImGui::Checkbox("primary", &slot.hasPs);
804 ImGui::SameLine(pos);
805 im::Disabled(!slot.hasPs, [&]{
806 changed |= ImGui::Combo("##ps", &slot.ps, "0\0001\0002\0003\000");
807
808 changed |= ImGui::Checkbox("secondary", &slot.hasSs);
809 ImGui::SameLine(pos);
810 im::Disabled(!slot.hasSs, [&]{
811 changed |= ImGui::Combo("##ss", &slot.ss, "0\0001\0002\0003\000");
812 });
813
814 changed |= ImGui::Checkbox("segment", &slot.hasSeg);
815 ImGui::SameLine(pos);
816 im::Disabled(!slot.hasSeg, [&]{
817 uint8_t one = 1;
818 changed |= ImGui::InputScalar("##seg", ImGuiDataType_U8, &slot.seg, &one);
819 });
820 });
821 });
822 ImGui::TextUnformatted("Tcl expression"sv);
823 im::Indent([&]{
824 im::Font(manager.fontMono, [&]{
825 ImGui::SetNextItemWidth(-FLT_MIN);
826 changed |= ImGui::InputText("##cond", &slot.rest);
827 });
828 });
829 return changed;
830}
831
832void ImGuiBreakPoints::refreshSymbols()
833{
834 refresh<BreakPoint>(guiBps);
835 refresh<WatchPoint>(guiWps);
836 //refresh<DebugCondition>(guiConditions); // not needed, doesn't have an address
837}
838
839template<typename Item>
840void ImGuiBreakPoints::refresh(std::vector<GuiItem>& items)
841{
843 constexpr bool isWatchPoint = std::is_same_v<Item, WatchPoint>;
844
845 auto* motherBoard = manager.getReactor().getMotherBoard();
846 if (!motherBoard) return;
847
848 for (auto& item : items) {
849 bool sync = false;
850 auto adjust = [&](std::optional<uint16_t>& addr, TclObject& str) {
851 if (addr) {
852 // was valid
853 if (auto newAddr = parseAddress(str)) {
854 // and is still valid
855 if (*newAddr != *addr) {
856 // but the value changed
857 addr = newAddr;
858 sync = true;
859 } else {
860 // heuristic: try to replace strings of the form "0x...." with a symbol name
861 auto s = str.getString();
862 if ((s.size() == 6) && s.starts_with("0x")) {
863 if (auto newSyms = symbolManager.lookupValue(*addr); !newSyms.empty()) {
864 str = newSyms.front()->name;
865 // no need to sync with openMSX
866 }
867 }
868 }
869 } else {
870 // but now no longer valid, revert to hex string
871 str = TclObject(tmpStrCat("0x", hex_string<4>(*addr)));
872 // no need to sync with openMSX
873 }
874 } else {
875 // was invalid, (re-)try to resolve symbol
876 if (auto newAddr = parseAddress(str)) {
877 // and now became valid
878 addr = newAddr;
879 sync = true;
880 }
881 }
882 };
883 adjust(item.addr, item.addrStr);
884 if (isWatchPoint) {
885 adjust(item.endAddr, item.endAddrStr);
886 }
887 if (sync) {
888 auto& cpuInterface = motherBoard->getCPUInterface();
889 auto& debugger = motherBoard->getDebugger();
890 auto& interp = manager.getInterpreter();
891 syncToOpenMsx<Item>(cpuInterface, debugger, interp, item);
892 }
893 }
894}
895
896} // namespace openmsx
Base class for CPU breakpoints.
Definition BreakPoint.hh:14
General debugger condition Like breakpoints, but not tied to a specific address.
ImGuiBreakPoints(ImGuiManager &manager)
void paintBpTab(MSXCPUInterface &cpuInterface, Debugger &debugger, uint16_t addr)
void save(ImGuiTextBuffer &buf) override
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
Interpreter & getInterpreter()
ImGuiManager & manager
Definition ImGuiPart.hh:30
void removeBreakPoint(const BreakPoint &bp)
MSXCPUInterface & getCPUInterface()
MSXMotherBoard * getMotherBoard() const
Definition Reactor.cc:409
Base class for CPU breakpoints.
Definition WatchPoint.hh:14
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition lz4.cc:146
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:459
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void TabBar(const char *str_id, ImGuiTabBarFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:480
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:510
void Group(std::invocable<> auto next)
Definition ImGuiCpp.hh:236
void Font(ImFont *font, std::invocable<> auto next)
Definition ImGuiCpp.hh:131
void TabItem(const char *label, bool *p_open, ImGuiTabItemFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:493
void Indent(float indent_w, std::invocable<> auto next)
Definition ImGuiCpp.hh:224
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:395
void ID_for_range(int count, std::invocable< int > auto next)
Definition ImGuiCpp.hh:281
std::unique_ptr< IDEDevice > create(const DeviceConfig &config)
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void sortUpDown_String(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:66
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
void sortUpDown_T(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void HelpMarker(std::string_view desc)
Definition ImGuiUtils.cc:23
ImU32 getColor(imColor col)
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:293
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
constexpr auto to_underlying(E e) noexcept
Definition stl.hh:465
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition stl.hh:32
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
std::string toDisplayString() const
std::string toTclExpression(std::string_view checkCmd) const
ParsedSlotCond(std::string_view checkCmd, std::string_view cond)
#define UNREACHABLE
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)