Skip to content

Commit 0bde2b5

Browse files
committed
Initial forward and back navigation impl in disassembly view
1 parent 218c0b7 commit 0bde2b5

File tree

3 files changed

+235
-2
lines changed

3 files changed

+235
-2
lines changed

gui/qt/debugger.cpp

Lines changed: 191 additions & 1 deletion
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

@@ -1964,6 +2051,98 @@ void MainWindow::disasmUpdateAddr(int base, bool pane) {
19642051
connect(m_disasm->verticalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::disasmScroll);
19652052
}
19662053

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

19952174
void MainWindow::gotoDisasmAddr(uint32_t address) {
1996-
disasmUpdateAddr(address, false);
2175+
navDisasmEnsureSeeded();
2176+
navDisasmPush(address, false);
19972177
raiseContainingDock(ui->disasm);
19982178
ui->disasm->setFocus();
19992179
}
@@ -2108,6 +2288,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) {
21082288
return QMainWindow::eventFilter(obj, e);
21092289
}
21102290

2291+
// Mouse back/forward in Disassembly view
2292+
if (obj == m_disasm && e->type() == QEvent::MouseButtonPress) {
2293+
auto *me = static_cast<QMouseEvent*>(e);
2294+
if (me->button() == Qt::BackButton) {
2295+
if (navDisasmBack()) { e->accept(); return true; }
2296+
} else if (me->button() == Qt::ForwardButton) {
2297+
if (navDisasmForward()) { e->accept(); return true; }
2298+
}
2299+
}
2300+
21112301
if (e->type() == QEvent::MouseButtonPress) {
21122302
QString name = obj->objectName();
21132303

gui/qt/mainwindow.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
9797
m_breakpoints = ui->breakpoints;
9898
m_ports = ui->ports;
9999
m_disasm = ui->disasm;
100+
m_disasm->installEventFilter(this);
100101

101102
ui->console->setMaximumBlockCount(1000);
102103

@@ -518,6 +519,11 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
518519
m_shortcutStepOver = new QShortcut(QKeySequence(Qt::Key_F7), this);
519520
m_shortcutStepNext = new QShortcut(QKeySequence(Qt::Key_F8), this);
520521
m_shortcutStepOut = new QShortcut(QKeySequence(Qt::Key_F9), this);
522+
m_shortcutNavBack = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Left), this);
523+
m_shortcutNavForward = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Right), this);
524+
525+
connect(m_shortcutNavBack, &QShortcut::activated, this, [this]{ navDisasmBack(); });
526+
connect(m_shortcutNavForward, &QShortcut::activated, this, [this]{ navDisasmForward(); });
521527
m_shortcutDebug = new QShortcut(QKeySequence(Qt::Key_F10), this);
522528
m_shortcutFullscreen = new QShortcut(QKeySequence(Qt::Key_F11), this);
523529
m_shortcutAsm = new QShortcut(QKeySequence(Qt::Key_Pause), this);
@@ -2695,6 +2701,11 @@ void MainWindow::contextDisasm(const QPoint &posa) {
26952701
uint32_t addr = static_cast<uint32_t>(hex2int(addrStr));
26962702

26972703
QMenu menu;
2704+
QAction *backAct = menu.addAction(tr("Back"));
2705+
QAction *fwdAct = menu.addAction(tr("Forward"));
2706+
backAct->setEnabled(m_disasmNavIndex > 0);
2707+
fwdAct->setEnabled(m_disasmNavIndex >= 0 && m_disasmNavIndex + 1 < m_disasmNav.size());
2708+
menu.addSeparator();
26982709
QAction *runUntil = menu.addAction(ACTION_RUN_UNTIL);
26992710
menu.addSeparator();
27002711
QAction *toggleBreak = menu.addAction(ACTION_TOGGLE_BREAK);
@@ -2706,7 +2717,11 @@ void MainWindow::contextDisasm(const QPoint &posa) {
27062717
QAction *setPc = menu.addAction(tr("Set PC"));
27072718

27082719
QAction *item = menu.exec(globalPos);
2709-
if (item == setPc) {
2720+
if (item == backAct) {
2721+
navDisasmBack();
2722+
} else if (item == fwdAct) {
2723+
navDisasmForward();
2724+
} else if (item == setPc) {
27102725
ui->pcregView->setText(addrStr);
27112726
debug_set_pc(addr);
27122727
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)