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