Skip to content

Commit e96fa93

Browse files
committed
Initial forward and back navigation impl in disassembly view
1 parent 128ccf6 commit e96fa93

File tree

4 files changed

+244
-5
lines changed

4 files changed

+244
-5
lines changed

gui/qt/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "G
7878
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GLOBAL_COMPILE_FLAGS} -Werror=implicit-function-declaration -Werror=missing-prototypes")
7979
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GLOBAL_COMPILE_FLAGS}")
8080
# useful flags for debugging
81-
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,bounds -fsanitize-undefined-trap-on-error ")
82-
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,bounds -fsanitize-undefined-trap-on-error ")
81+
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,bounds -fsanitize-undefined-trap-on-error ")
82+
# set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,bounds -fsanitize-undefined-trap-on-error ")
8383
endif()
8484

8585
include(GNUInstallDirs)

gui/qt/debugger.cpp

Lines changed: 198 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <QtGui/QWindow>
4141
#include <QtGui/QScreen>
4242
#include <algorithm>
43+
#include <array>
4344

4445
#ifdef _MSC_VER
4546
#include <direct.h>
@@ -51,6 +52,63 @@
5152
const QString MainWindow::DEBUG_UNSET_ADDR = QStringLiteral("XXXXXX");
5253
const QString MainWindow::DEBUG_UNSET_PORT = QStringLiteral("XXXX");
5354

55+
namespace {
56+
constexpr uint8_t OP_CALL = 0xCD;
57+
constexpr uint8_t OP_RET = 0xC9;
58+
constexpr uint8_t OP_JP_HL = 0xE9;
59+
constexpr uint8_t OP_PREFIX_ED = 0xED;
60+
constexpr uint8_t OP_PREFIX_DD = 0xDD;
61+
constexpr uint8_t OP_PREFIX_FD = 0xFD;
62+
constexpr uint8_t OP_DJNZ = 0x10;
63+
constexpr uint8_t OP_JR = 0x18;
64+
constexpr uint8_t OP_JR_NZ = 0x20;
65+
constexpr uint8_t OP_JR_Z = 0x28;
66+
constexpr uint8_t OP_JR_NC = 0x30;
67+
constexpr uint8_t OP_JR_C = 0x38;
68+
constexpr uint8_t OP_JP = 0xC3;
69+
70+
constexpr uint8_t OP_RETN_ED = 0x45;
71+
constexpr uint8_t OP_RETI_ED = 0x4D;
72+
73+
constexpr uint8_t CF_NONE = 0;
74+
constexpr uint8_t CF_CALL = 1u << 0;
75+
constexpr uint8_t CF_RET = 1u << 1;
76+
constexpr uint8_t CF_JUMP = 1u << 2;
77+
constexpr uint8_t CF_RST = 1u << 3;
78+
79+
constexpr auto kCtrlLut = []{
80+
std::array<uint8_t, 256> lut{};
81+
// CALL nn and CALL cc,nn
82+
lut[OP_CALL] |= CF_CALL; lut[0xC4] |= CF_CALL; lut[0xCC] |= CF_CALL; lut[0xD4] |= CF_CALL; lut[0xDC] |= CF_CALL;
83+
lut[0xE4] |= CF_CALL; lut[0xEC] |= CF_CALL; lut[0xF4] |= CF_CALL; lut[0xFC] |= CF_CALL;
84+
// RET
85+
lut[OP_RET] |= CF_RET;
86+
// JP nn and JP cc, nn
87+
lut[OP_JP] |= CF_JUMP;
88+
lut[0xC2] |= CF_JUMP; lut[0xCA] |= CF_JUMP; lut[0xD2] |= CF_JUMP; lut[0xDA] |= CF_JUMP;
89+
lut[0xE2] |= CF_JUMP; lut[0xEA] |= CF_JUMP; lut[0xF2] |= CF_JUMP; lut[0xFA] |= CF_JUMP;
90+
// JR e and JR cc,e
91+
lut[OP_JR] |= CF_JUMP; lut[OP_JR_NZ] |= CF_JUMP; lut[OP_JR_Z] |= CF_JUMP; lut[OP_JR_NC] |= CF_JUMP; lut[OP_JR_C] |= CF_JUMP;
92+
// DJNZ
93+
lut[OP_DJNZ] |= CF_JUMP;
94+
// JP (HL)
95+
lut[OP_JP_HL] |= CF_JUMP;
96+
// RST t (C7, CF, D7, DF, E7, EF, F7, FF)
97+
lut[0xC7] |= CF_RST; lut[0xCF] |= CF_RST; lut[0xD7] |= CF_RST; lut[0xDF] |= CF_RST;
98+
lut[0xE7] |= CF_RST; lut[0xEF] |= CF_RST; lut[0xF7] |= CF_RST; lut[0xFF] |= CF_RST;
99+
return lut;
100+
}();
101+
102+
bool isCtrlFlowOpcode(uint8_t b0, uint8_t b1) {
103+
if (kCtrlLut[b0] != CF_NONE) { return true; }
104+
// RETN/RETI
105+
if (b0 == OP_PREFIX_ED && (b1 == OP_RETN_ED || b1 == OP_RETI_ED)) { return true; }
106+
// JP (IX)/(IY)
107+
if ((b0 == OP_PREFIX_DD || b0 == OP_PREFIX_FD) && b1 == OP_JP_HL) { return true; }
108+
return false;
109+
}
110+
}
111+
54112
// -----------------------------------------------
55113
// Debugger Initialization
56114
// -----------------------------------------------
@@ -95,6 +153,10 @@ void MainWindow::debugInit() {
95153
// ------------------------------------------------
96154

97155
void MainWindow::debugDisable() {
156+
if (m_suppressDebugCloseOnce) {
157+
m_suppressDebugCloseOnce = false;
158+
return;
159+
}
98160
guiDebug = false;
99161
debugGuiState(false);
100162
}
@@ -110,6 +172,19 @@ void MainWindow::debugStep(int mode) {
110172
} else {
111173
disasm.base = static_cast<int32_t>(cpu.registers.PC);
112174
disasmGet(true);
175+
176+
m_stepCtx.active = true;
177+
m_stepCtx.seqNext = static_cast<uint32_t>(disasm.next);
178+
179+
if (mode == DBG_STEP_OVER) {
180+
const uint32_t pc0 = cpu.registers.PC;
181+
const uint8_t b0 = mem_peek_byte(pc0);
182+
const uint8_t b1 = mem_peek_byte(pc0 + 1);
183+
if (!isCtrlFlowOpcode(b0, b1)) {
184+
mode = DBG_STEP_NEXT;
185+
m_suppressDebugCloseOnce = true;
186+
}
187+
}
113188
debug_step(mode, static_cast<uint32_t>(disasm.next));
114189
}
115190
emu.resume();
@@ -392,6 +467,7 @@ void MainWindow::debugCommand(int reason, uint32_t data) {
392467

393468
if (reason == DBG_READY) {
394469
guiReset = false;
470+
navDisasmClear();
395471
emu.resume();
396472
return;
397473
}
@@ -850,6 +926,17 @@ void MainWindow::debugPopulate() {
850926
osUpdate();
851927
stackUpdate();
852928
disasmUpdateAddr(m_prevDisasmAddr = cpu.registers.PC, true);
929+
// Track step navigation: append on control-flow (branch taken),
930+
// replace on linear advance. Non-step stops do not modify history.
931+
if (m_stepCtx.active) {
932+
bool tookBranch = (static_cast<uint32_t>(cpu.registers.PC) != m_stepCtx.seqNext);
933+
if (tookBranch) {
934+
navDisasmPush(m_prevDisasmAddr, true);
935+
} else {
936+
navDisasmReplace(m_prevDisasmAddr, true);
937+
}
938+
m_stepCtx.active = false;
939+
}
853940

854941
memUpdate();
855942

@@ -1970,6 +2057,104 @@ void MainWindow::disasmUpdateAddr(int base, bool pane) {
19702057
connect(m_disasm->verticalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::disasmScroll);
19712058
}
19722059

2060+
// ------------------------------------------------
2061+
// Disassembly navigation history helpers
2062+
// ------------------------------------------------
2063+
2064+
uint32_t MainWindow::currentDisasmAddress() const {
2065+
if (m_prevDisasmAddr) {
2066+
return m_prevDisasmAddr;
2067+
}
2068+
QString sel = m_disasm ? m_disasm->getSelectedAddr() : QString();
2069+
if (!sel.isEmpty()) {
2070+
return static_cast<uint32_t>(hex2int(sel));
2071+
}
2072+
return static_cast<uint32_t>(cpu.registers.PC);
2073+
}
2074+
2075+
void MainWindow::navDisasmEnsureSeeded() {
2076+
if (m_disasmNavIndex == -1) {
2077+
m_disasmNav.reserve(kMaxDisasmHistory);
2078+
// seed with the last PC location and current pane mode so that fully backing out returns to the same stop context
2079+
m_disasmNav.push_back({ currentDisasmAddress(), m_disasmPane });
2080+
m_disasmNavIndex = 0;
2081+
updateNavUiEnabledStates();
2082+
}
2083+
}
2084+
2085+
void MainWindow::navDisasmPush(uint32_t addr, bool pane) {
2086+
if (m_isApplyingDisasmNav) {
2087+
disasmUpdateAddr(static_cast<int>(addr), pane);
2088+
return;
2089+
}
2090+
if (m_disasmNavIndex >= 0 && m_disasmNavIndex < m_disasmNav.size()) {
2091+
const DisasmNavEntry &cur = m_disasmNav[m_disasmNavIndex];
2092+
if (cur.addr == addr && cur.pane == pane) {
2093+
disasmUpdateAddr(static_cast<int>(addr), pane);
2094+
return;
2095+
}
2096+
}
2097+
if (m_disasmNavIndex + 1 < m_disasmNav.size()) {
2098+
m_disasmNav.resize(m_disasmNavIndex + 1);
2099+
}
2100+
if (m_disasmNav.size() >= kMaxDisasmHistory) {
2101+
m_disasmNav.remove(0);
2102+
if (m_disasmNavIndex > 0) { --m_disasmNavIndex; }
2103+
}
2104+
m_disasmNav.push_back({addr, pane});
2105+
m_disasmNavIndex = m_disasmNav.size() - 1;
2106+
m_isApplyingDisasmNav = true;
2107+
disasmUpdateAddr(static_cast<int>(addr), pane);
2108+
m_isApplyingDisasmNav = false;
2109+
updateNavUiEnabledStates();
2110+
}
2111+
2112+
void MainWindow::navDisasmReplace(uint32_t addr, bool pane) {
2113+
if (m_isApplyingDisasmNav) {
2114+
return;
2115+
}
2116+
navDisasmEnsureSeeded();
2117+
if (m_disasmNavIndex < 0) {
2118+
m_disasmNav.push_back({addr, pane});
2119+
m_disasmNavIndex = m_disasmNav.size() - 1;
2120+
} else {
2121+
m_disasmNav[m_disasmNavIndex] = {addr, pane};
2122+
}
2123+
updateNavUiEnabledStates();
2124+
}
2125+
2126+
bool MainWindow::navDisasmBack() {
2127+
if (m_disasmNavIndex > 0) {
2128+
--m_disasmNavIndex;
2129+
const auto &e = m_disasmNav[m_disasmNavIndex];
2130+
m_isApplyingDisasmNav = true;
2131+
disasmUpdateAddr(static_cast<int>(e.addr), e.pane);
2132+
m_isApplyingDisasmNav = false;
2133+
updateNavUiEnabledStates();
2134+
return true;
2135+
}
2136+
return false;
2137+
}
2138+
2139+
bool MainWindow::navDisasmForward() {
2140+
if (m_disasmNavIndex >= 0 && m_disasmNavIndex + 1 < m_disasmNav.size()) {
2141+
++m_disasmNavIndex;
2142+
const auto &e = m_disasmNav[m_disasmNavIndex];
2143+
m_isApplyingDisasmNav = true;
2144+
disasmUpdateAddr(static_cast<int>(e.addr), e.pane);
2145+
m_isApplyingDisasmNav = false;
2146+
updateNavUiEnabledStates();
2147+
return true;
2148+
}
2149+
return false;
2150+
}
2151+
2152+
void MainWindow::navDisasmClear() {
2153+
m_disasmNav.clear();
2154+
m_disasmNavIndex = -1;
2155+
updateNavUiEnabledStates();
2156+
}
2157+
19732158
// ------------------------------------------------
19742159
// Misc
19752160
// ------------------------------------------------
@@ -1986,7 +2171,7 @@ void MainWindow::gotoPressed() {
19862171
QString resolved = resolveAddressOrEquate(typed, &ok);
19872172
if (ok) {
19882173
m_gotoAddr = typed;
1989-
disasmUpdateAddr(static_cast<int>(hex2int(resolved)), false);
2174+
gotoDisasmAddr(static_cast<uint32_t>(hex2int(resolved)));
19902175

19912176
auto &hist = m_disasmGotoHistory;
19922177
hist.erase(std::remove_if(hist.begin(), hist.end(), [&](const QString &s){ return s.compare(typed, Qt::CaseInsensitive) == 0; }), hist.end());
@@ -1999,7 +2184,8 @@ void MainWindow::gotoPressed() {
19992184
}
20002185

20012186
void MainWindow::gotoDisasmAddr(uint32_t address) {
2002-
disasmUpdateAddr(address, false);
2187+
navDisasmEnsureSeeded();
2188+
navDisasmPush(address, false);
20032189
raiseContainingDock(ui->disasm);
20042190
ui->disasm->setFocus();
20052191
}
@@ -2114,6 +2300,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) {
21142300
return QMainWindow::eventFilter(obj, e);
21152301
}
21162302

2303+
// Mouse back/forward in Disassembly view
2304+
if (obj == m_disasm && e->type() == QEvent::MouseButtonPress) {
2305+
auto *me = static_cast<QMouseEvent*>(e);
2306+
if (me->button() == Qt::BackButton) {
2307+
if (navDisasmBack()) { e->accept(); return true; }
2308+
} else if (me->button() == Qt::ForwardButton) {
2309+
if (navDisasmForward()) { e->accept(); return true; }
2310+
}
2311+
}
2312+
21172313
if (e->type() == QEvent::MouseButtonPress) {
21182314
QString name = obj->objectName();
21192315

gui/qt/mainwindow.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
107107
m_breakpoints = ui->breakpoints;
108108
m_ports = ui->ports;
109109
m_disasm = ui->disasm;
110+
m_disasm->installEventFilter(this);
110111

111112
ui->console->setMaximumBlockCount(1000);
112113

@@ -528,6 +529,11 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
528529
m_shortcutStepOver = new QShortcut(QKeySequence(Qt::Key_F7), this);
529530
m_shortcutStepNext = new QShortcut(QKeySequence(Qt::Key_F8), this);
530531
m_shortcutStepOut = new QShortcut(QKeySequence(Qt::Key_F9), this);
532+
m_shortcutNavBack = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Left), this);
533+
m_shortcutNavForward = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Right), this);
534+
535+
connect(m_shortcutNavBack, &QShortcut::activated, this, [this]{ navDisasmBack(); });
536+
connect(m_shortcutNavForward, &QShortcut::activated, this, [this]{ navDisasmForward(); });
531537
m_shortcutDebug = new QShortcut(QKeySequence(Qt::Key_F10), this);
532538
m_shortcutFullscreen = new QShortcut(QKeySequence(Qt::Key_F11), this);
533539
m_shortcutAsm = new QShortcut(QKeySequence(Qt::Key_Pause), this);
@@ -2707,6 +2713,11 @@ void MainWindow::contextDisasm(const QPoint &posa) {
27072713
uint32_t addr = static_cast<uint32_t>(hex2int(addrStr));
27082714

27092715
QMenu menu;
2716+
QAction *backAct = menu.addAction(tr("Back"));
2717+
QAction *fwdAct = menu.addAction(tr("Forward"));
2718+
backAct->setEnabled(m_disasmNavIndex > 0);
2719+
fwdAct->setEnabled(m_disasmNavIndex >= 0 && m_disasmNavIndex + 1 < m_disasmNav.size());
2720+
menu.addSeparator();
27102721
QAction *runUntil = menu.addAction(ACTION_RUN_UNTIL);
27112722
menu.addSeparator();
27122723
QAction *toggleBreak = menu.addAction(ACTION_TOGGLE_BREAK);
@@ -2718,7 +2729,11 @@ void MainWindow::contextDisasm(const QPoint &posa) {
27182729
QAction *setPc = menu.addAction(tr("Set PC"));
27192730

27202731
QAction *item = menu.exec(globalPos);
2721-
if (item == setPc) {
2732+
if (item == backAct) {
2733+
navDisasmBack();
2734+
} else if (item == fwdAct) {
2735+
navDisasmForward();
2736+
} else if (item == setPc) {
27222737
ui->pcregView->setText(addrStr);
27232738
debug_set_pc(addr);
27242739
disasmUpdateAddr(static_cast<int>(cpu.registers.PC), true);

gui/qt/mainwindow.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,8 @@ private slots:
706706
QShortcut *m_shortcutStepOver;
707707
QShortcut *m_shortcutStepNext;
708708
QShortcut *m_shortcutStepOut;
709+
QShortcut *m_shortcutNavBack;
710+
QShortcut *m_shortcutNavForward;
709711
QShortcut *m_shortcutDebug;
710712
QShortcut *m_shortcutFullscreen;
711713
QShortcut *m_shortcutAsm;
@@ -937,6 +939,32 @@ private slots:
937939
QTableWidget *m_ports = nullptr;
938940
DataWidget *m_disasm = nullptr;
939941

942+
struct DisasmNavEntry {
943+
uint32_t addr;
944+
bool pane;
945+
};
946+
947+
QVector<DisasmNavEntry> m_disasmNav;
948+
int m_disasmNavIndex = -1;
949+
bool m_isApplyingDisasmNav = false;
950+
static constexpr int kMaxDisasmHistory = 200;
951+
952+
[[nodiscard]] uint32_t currentDisasmAddress() const;
953+
void navDisasmEnsureSeeded();
954+
void navDisasmPush(uint32_t addr, bool pane);
955+
void navDisasmReplace(uint32_t addr, bool pane);
956+
bool navDisasmBack();
957+
bool navDisasmForward();
958+
void navDisasmClear();
959+
960+
struct StepNavCtx {
961+
bool active = false;
962+
uint32_t seqNext = 0; // sequential next PC at step start
963+
} m_stepCtx;
964+
965+
// suppression to avoid GUI close/reopen flicker when mapping Step Over to Step Next
966+
bool m_suppressDebugCloseOnce = false;
967+
940968
#ifdef LIBUSB_SUPPORT
941969
libusb_context *m_usbContext = nullptr;
942970
libusb_hotplug_callback_handle m_usbHotplugCallbackHandle{};

0 commit comments

Comments
 (0)