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