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