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