From 02679b4030ef8861ea61999c7e891f5f9323d731 Mon Sep 17 00:00:00 2001 From: Sightem Date: Sat, 4 Oct 2025 09:18:01 -0700 Subject: [PATCH 1/3] Initial forward and back navigation impl in disassembly view (cherry picked from commit 0bde2b5da48567c05ae543a399527d7e9dd60c1e) --- gui/qt/debugger.cpp | 192 +++++++++++++++++++++++++++++++++++++++++- gui/qt/mainwindow.cpp | 17 +++- gui/qt/mainwindow.h | 28 ++++++ 3 files changed, 235 insertions(+), 2 deletions(-) diff --git a/gui/qt/debugger.cpp b/gui/qt/debugger.cpp index 946a54da..7366f2ad 100644 --- a/gui/qt/debugger.cpp +++ b/gui/qt/debugger.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #ifdef _MSC_VER #include @@ -51,6 +52,63 @@ const QString MainWindow::DEBUG_UNSET_ADDR = QStringLiteral("XXXXXX"); const QString MainWindow::DEBUG_UNSET_PORT = QStringLiteral("XXXX"); +namespace { + constexpr uint8_t OP_CALL = 0xCD; + constexpr uint8_t OP_RET = 0xC9; + constexpr uint8_t OP_JP_HL = 0xE9; + constexpr uint8_t OP_PREFIX_ED = 0xED; + constexpr uint8_t OP_PREFIX_DD = 0xDD; + constexpr uint8_t OP_PREFIX_FD = 0xFD; + constexpr uint8_t OP_DJNZ = 0x10; + constexpr uint8_t OP_JR = 0x18; + constexpr uint8_t OP_JR_NZ = 0x20; + constexpr uint8_t OP_JR_Z = 0x28; + constexpr uint8_t OP_JR_NC = 0x30; + constexpr uint8_t OP_JR_C = 0x38; + constexpr uint8_t OP_JP = 0xC3; + + constexpr uint8_t OP_RETN_ED = 0x45; + constexpr uint8_t OP_RETI_ED = 0x4D; + + constexpr uint8_t CF_NONE = 0; + constexpr uint8_t CF_CALL = 1u << 0; + constexpr uint8_t CF_RET = 1u << 1; + constexpr uint8_t CF_JUMP = 1u << 2; + constexpr uint8_t CF_RST = 1u << 3; + + constexpr auto kCtrlLut = []{ + std::array lut{}; + // CALL nn and CALL cc,nn + lut[OP_CALL] |= CF_CALL; lut[0xC4] |= CF_CALL; lut[0xCC] |= CF_CALL; lut[0xD4] |= CF_CALL; lut[0xDC] |= CF_CALL; + lut[0xE4] |= CF_CALL; lut[0xEC] |= CF_CALL; lut[0xF4] |= CF_CALL; lut[0xFC] |= CF_CALL; + // RET + lut[OP_RET] |= CF_RET; + // JP nn and JP cc, nn + lut[OP_JP] |= CF_JUMP; + lut[0xC2] |= CF_JUMP; lut[0xCA] |= CF_JUMP; lut[0xD2] |= CF_JUMP; lut[0xDA] |= CF_JUMP; + lut[0xE2] |= CF_JUMP; lut[0xEA] |= CF_JUMP; lut[0xF2] |= CF_JUMP; lut[0xFA] |= CF_JUMP; + // JR e and JR cc,e + 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; + // DJNZ + lut[OP_DJNZ] |= CF_JUMP; + // JP (HL) + lut[OP_JP_HL] |= CF_JUMP; + // RST t (C7, CF, D7, DF, E7, EF, F7, FF) + lut[0xC7] |= CF_RST; lut[0xCF] |= CF_RST; lut[0xD7] |= CF_RST; lut[0xDF] |= CF_RST; + lut[0xE7] |= CF_RST; lut[0xEF] |= CF_RST; lut[0xF7] |= CF_RST; lut[0xFF] |= CF_RST; + return lut; + }(); + + bool isCtrlFlowOpcode(uint8_t b0, uint8_t b1) { + if (kCtrlLut[b0] != CF_NONE) { return true; } + // RETN/RETI + if (b0 == OP_PREFIX_ED && (b1 == OP_RETN_ED || b1 == OP_RETI_ED)) { return true; } + // JP (IX)/(IY) + if ((b0 == OP_PREFIX_DD || b0 == OP_PREFIX_FD) && b1 == OP_JP_HL) { return true; } + return false; + } +} + // ----------------------------------------------- // Debugger Initialization // ----------------------------------------------- @@ -95,6 +153,10 @@ void MainWindow::debugInit() { // ------------------------------------------------ void MainWindow::debugDisable() { + if (m_suppressDebugCloseOnce) { + m_suppressDebugCloseOnce = false; + return; + } guiDebug = false; debugGuiState(false); } @@ -110,6 +172,19 @@ void MainWindow::debugStep(int mode) { } else { disasm.base = static_cast(cpu.registers.PC); disasmGet(true); + + m_stepCtx.active = true; + m_stepCtx.seqNext = static_cast(disasm.next); + + if (mode == DBG_STEP_OVER) { + const uint32_t pc0 = cpu.registers.PC; + const uint8_t b0 = mem_peek_byte(pc0); + const uint8_t b1 = mem_peek_byte(pc0 + 1); + if (!isCtrlFlowOpcode(b0, b1)) { + mode = DBG_STEP_NEXT; + m_suppressDebugCloseOnce = true; + } + } debug_step(mode, static_cast(disasm.next)); } emu.resume(); @@ -392,6 +467,7 @@ void MainWindow::debugCommand(int reason, uint32_t data) { if (reason == DBG_READY) { guiReset = false; + navDisasmClear(); emu.resume(); return; } @@ -850,6 +926,17 @@ void MainWindow::debugPopulate() { osUpdate(); stackUpdate(); disasmUpdateAddr(m_prevDisasmAddr = cpu.registers.PC, true); + // Track step navigation: append on control-flow (branch taken), + // replace on linear advance. Non-step stops do not modify history + if (m_stepCtx.active) { + bool tookBranch = (static_cast(cpu.registers.PC) != m_stepCtx.seqNext); + if (tookBranch) { + navDisasmPush(m_prevDisasmAddr, true); + } else { + navDisasmReplace(m_prevDisasmAddr, true); + } + m_stepCtx.active = false; + } memUpdate(); @@ -1964,6 +2051,98 @@ void MainWindow::disasmUpdateAddr(int base, bool pane) { connect(m_disasm->verticalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::disasmScroll); } +// ------------------------------------------------ +// Disassembly navigation history helpers +// ------------------------------------------------ + +uint32_t MainWindow::currentDisasmAddress() const { + if (m_prevDisasmAddr) { + return m_prevDisasmAddr; + } + QString sel = m_disasm ? m_disasm->getSelectedAddr() : QString(); + if (!sel.isEmpty()) { + return static_cast(hex2int(sel)); + } + return cpu.registers.PC; +} + +void MainWindow::navDisasmEnsureSeeded() { + if (m_disasmNavIndex == -1) { + m_disasmNav.reserve(kMaxDisasmHistory); + // seed with the last PC location and current pane mode so that fully backing out returns to the same stop context + m_disasmNav.push_back({ currentDisasmAddress(), m_disasmPane }); + m_disasmNavIndex = 0; + } +} + +void MainWindow::navDisasmPush(uint32_t addr, bool pane) { + if (m_isApplyingDisasmNav) { + disasmUpdateAddr(static_cast(addr), pane); + return; + } + if (m_disasmNavIndex >= 0 && m_disasmNavIndex < m_disasmNav.size()) { + const DisasmNavEntry &cur = m_disasmNav[m_disasmNavIndex]; + if (cur.addr == addr && cur.pane == pane) { + disasmUpdateAddr(static_cast(addr), pane); + return; + } + } + if (m_disasmNavIndex + 1 < m_disasmNav.size()) { + m_disasmNav.resize(m_disasmNavIndex + 1); + } + if (m_disasmNav.size() >= kMaxDisasmHistory) { + m_disasmNav.remove(0); + if (m_disasmNavIndex > 0) { --m_disasmNavIndex; } + } + m_disasmNav.push_back({addr, pane}); + m_disasmNavIndex = m_disasmNav.size() - 1; + m_isApplyingDisasmNav = true; + disasmUpdateAddr(static_cast(addr), pane); + m_isApplyingDisasmNav = false; +} + +void MainWindow::navDisasmReplace(uint32_t addr, bool pane) { + if (m_isApplyingDisasmNav) { + return; + } + navDisasmEnsureSeeded(); + if (m_disasmNavIndex < 0) { + m_disasmNav.push_back({addr, pane}); + m_disasmNavIndex = m_disasmNav.size() - 1; + } else { + m_disasmNav[m_disasmNavIndex] = {addr, pane}; + } +} + +bool MainWindow::navDisasmBack() { + if (m_disasmNavIndex > 0) { + --m_disasmNavIndex; + const auto &e = m_disasmNav[m_disasmNavIndex]; + m_isApplyingDisasmNav = true; + disasmUpdateAddr(static_cast(e.addr), e.pane); + m_isApplyingDisasmNav = false; + return true; + } + return false; +} + +bool MainWindow::navDisasmForward() { + if (m_disasmNavIndex >= 0 && m_disasmNavIndex + 1 < m_disasmNav.size()) { + ++m_disasmNavIndex; + const auto &e = m_disasmNav[m_disasmNavIndex]; + m_isApplyingDisasmNav = true; + disasmUpdateAddr(static_cast(e.addr), e.pane); + m_isApplyingDisasmNav = false; + return true; + } + return false; +} + +void MainWindow::navDisasmClear() { + m_disasmNav.clear(); + m_disasmNavIndex = -1; +} + // ------------------------------------------------ // Misc // ------------------------------------------------ @@ -1993,7 +2172,8 @@ void MainWindow::gotoPressed() { } void MainWindow::gotoDisasmAddr(uint32_t address) { - disasmUpdateAddr(address, false); + navDisasmEnsureSeeded(); + navDisasmPush(address, false); raiseContainingDock(ui->disasm); ui->disasm->setFocus(); } @@ -2111,6 +2291,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) { return QMainWindow::eventFilter(obj, e); } + // Mouse back/forward in Disassembly view + if (obj == m_disasm && e->type() == QEvent::MouseButtonPress) { + auto *me = static_cast(e); + if (me->button() == Qt::BackButton) { + if (navDisasmBack()) { e->accept(); return true; } + } else if (me->button() == Qt::ForwardButton) { + if (navDisasmForward()) { e->accept(); return true; } + } + } + if (e->type() == QEvent::MouseButtonPress) { QString name = obj->objectName(); diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index c01248a7..6cdb83b0 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -97,6 +97,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U m_breakpoints = ui->breakpoints; m_ports = ui->ports; m_disasm = ui->disasm; + m_disasm->installEventFilter(this); ui->console->setMaximumBlockCount(1000); @@ -518,6 +519,11 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U m_shortcutStepOver = new QShortcut(QKeySequence(Qt::Key_F7), this); m_shortcutStepNext = new QShortcut(QKeySequence(Qt::Key_F8), this); m_shortcutStepOut = new QShortcut(QKeySequence(Qt::Key_F9), this); + m_shortcutNavBack = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Left), this); + m_shortcutNavForward = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Right), this); + + connect(m_shortcutNavBack, &QShortcut::activated, this, [this]{ navDisasmBack(); }); + connect(m_shortcutNavForward, &QShortcut::activated, this, [this]{ navDisasmForward(); }); m_shortcutDebug = new QShortcut(QKeySequence(Qt::Key_F10), this); m_shortcutFullscreen = new QShortcut(QKeySequence(Qt::Key_F11), this); m_shortcutAsm = new QShortcut(QKeySequence(Qt::Key_Pause), this); @@ -2695,6 +2701,11 @@ void MainWindow::contextDisasm(const QPoint &posa) { uint32_t addr = static_cast(hex2int(addrStr)); QMenu menu; + QAction *backAct = menu.addAction(tr("Back")); + QAction *fwdAct = menu.addAction(tr("Forward")); + backAct->setEnabled(m_disasmNavIndex > 0); + fwdAct->setEnabled(m_disasmNavIndex >= 0 && m_disasmNavIndex + 1 < m_disasmNav.size()); + menu.addSeparator(); QAction *runUntil = menu.addAction(ACTION_RUN_UNTIL); menu.addSeparator(); QAction *toggleBreak = menu.addAction(ACTION_TOGGLE_BREAK); @@ -2706,7 +2717,11 @@ void MainWindow::contextDisasm(const QPoint &posa) { QAction *setPc = menu.addAction(tr("Set PC")); QAction *item = menu.exec(globalPos); - if (item == setPc) { + if (item == backAct) { + navDisasmBack(); + } else if (item == fwdAct) { + navDisasmForward(); + } else if (item == setPc) { ui->pcregView->setText(addrStr); debug_set_pc(addr); disasmUpdateAddr(static_cast(cpu.registers.PC), true); diff --git a/gui/qt/mainwindow.h b/gui/qt/mainwindow.h index 5dbb1064..dde4dcbd 100644 --- a/gui/qt/mainwindow.h +++ b/gui/qt/mainwindow.h @@ -706,6 +706,8 @@ private slots: QShortcut *m_shortcutStepOver; QShortcut *m_shortcutStepNext; QShortcut *m_shortcutStepOut; + QShortcut *m_shortcutNavBack; + QShortcut *m_shortcutNavForward; QShortcut *m_shortcutDebug; QShortcut *m_shortcutFullscreen; QShortcut *m_shortcutAsm; @@ -937,6 +939,32 @@ private slots: QTableWidget *m_ports = nullptr; DataWidget *m_disasm = nullptr; + struct DisasmNavEntry { + uint32_t addr; + bool pane; + }; + + QVector m_disasmNav; + int m_disasmNavIndex = -1; + bool m_isApplyingDisasmNav = false; + static constexpr int kMaxDisasmHistory = 200; + + [[nodiscard]] uint32_t currentDisasmAddress() const; + void navDisasmEnsureSeeded(); + void navDisasmPush(uint32_t addr, bool pane); + void navDisasmReplace(uint32_t addr, bool pane); + bool navDisasmBack(); + bool navDisasmForward(); + void navDisasmClear(); + + struct StepNavCtx { + bool active = false; + uint32_t seqNext = 0; // sequential next PC at step start + } m_stepCtx; + + // suppression to avoid GUI close/reopen flicker when mapping Step Over to Step Next + bool m_suppressDebugCloseOnce = false; + #ifdef LIBUSB_SUPPORT libusb_context *m_usbContext = nullptr; libusb_hotplug_callback_handle m_usbHotplugCallbackHandle{}; From 4bab5c7dd4067ea2b98a75e469b35d80707ca17c Mon Sep 17 00:00:00 2001 From: Sightem Date: Wed, 8 Oct 2025 13:19:59 -0700 Subject: [PATCH 2/3] Step Next now stops after a single branch instruction (cherry picked from commit 82cd176cb76240dfe0c21981af647d994e0ff8c2) --- core/debug/debug.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/debug/debug.c b/core/debug/debug.c index a41b6507..da8cb32a 100644 --- a/core/debug/debug.c +++ b/core/debug/debug.c @@ -182,6 +182,11 @@ void debug_step(int mode, uint32_t addr) { debug.stepOut = debug.stackIndex; break; case DBG_STEP_NEXT: + gui_debug_close(); + debug.step = true; + debug.stepOver = false; + debug.tempExec = ~0u; + break; case DBG_RUN_UNTIL: gui_debug_close(); debug.tempExec = addr; From c3211baeb78a8580762ad414defa3cdf132f750e Mon Sep 17 00:00:00 2001 From: Sightem Date: Sat, 11 Oct 2025 16:04:33 -0700 Subject: [PATCH 3/3] revert Step Next behavior and remove Step Over -> Next mapping --- core/debug/debug.c | 5 ---- gui/qt/debugger.cpp | 72 --------------------------------------------- gui/qt/mainwindow.h | 3 -- 3 files changed, 80 deletions(-) diff --git a/core/debug/debug.c b/core/debug/debug.c index da8cb32a..a41b6507 100644 --- a/core/debug/debug.c +++ b/core/debug/debug.c @@ -182,11 +182,6 @@ void debug_step(int mode, uint32_t addr) { debug.stepOut = debug.stackIndex; break; case DBG_STEP_NEXT: - gui_debug_close(); - debug.step = true; - debug.stepOver = false; - debug.tempExec = ~0u; - break; case DBG_RUN_UNTIL: gui_debug_close(); debug.tempExec = addr; diff --git a/gui/qt/debugger.cpp b/gui/qt/debugger.cpp index 7366f2ad..655a33e2 100644 --- a/gui/qt/debugger.cpp +++ b/gui/qt/debugger.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #ifdef _MSC_VER #include @@ -52,63 +51,6 @@ const QString MainWindow::DEBUG_UNSET_ADDR = QStringLiteral("XXXXXX"); const QString MainWindow::DEBUG_UNSET_PORT = QStringLiteral("XXXX"); -namespace { - constexpr uint8_t OP_CALL = 0xCD; - constexpr uint8_t OP_RET = 0xC9; - constexpr uint8_t OP_JP_HL = 0xE9; - constexpr uint8_t OP_PREFIX_ED = 0xED; - constexpr uint8_t OP_PREFIX_DD = 0xDD; - constexpr uint8_t OP_PREFIX_FD = 0xFD; - constexpr uint8_t OP_DJNZ = 0x10; - constexpr uint8_t OP_JR = 0x18; - constexpr uint8_t OP_JR_NZ = 0x20; - constexpr uint8_t OP_JR_Z = 0x28; - constexpr uint8_t OP_JR_NC = 0x30; - constexpr uint8_t OP_JR_C = 0x38; - constexpr uint8_t OP_JP = 0xC3; - - constexpr uint8_t OP_RETN_ED = 0x45; - constexpr uint8_t OP_RETI_ED = 0x4D; - - constexpr uint8_t CF_NONE = 0; - constexpr uint8_t CF_CALL = 1u << 0; - constexpr uint8_t CF_RET = 1u << 1; - constexpr uint8_t CF_JUMP = 1u << 2; - constexpr uint8_t CF_RST = 1u << 3; - - constexpr auto kCtrlLut = []{ - std::array lut{}; - // CALL nn and CALL cc,nn - lut[OP_CALL] |= CF_CALL; lut[0xC4] |= CF_CALL; lut[0xCC] |= CF_CALL; lut[0xD4] |= CF_CALL; lut[0xDC] |= CF_CALL; - lut[0xE4] |= CF_CALL; lut[0xEC] |= CF_CALL; lut[0xF4] |= CF_CALL; lut[0xFC] |= CF_CALL; - // RET - lut[OP_RET] |= CF_RET; - // JP nn and JP cc, nn - lut[OP_JP] |= CF_JUMP; - lut[0xC2] |= CF_JUMP; lut[0xCA] |= CF_JUMP; lut[0xD2] |= CF_JUMP; lut[0xDA] |= CF_JUMP; - lut[0xE2] |= CF_JUMP; lut[0xEA] |= CF_JUMP; lut[0xF2] |= CF_JUMP; lut[0xFA] |= CF_JUMP; - // JR e and JR cc,e - 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; - // DJNZ - lut[OP_DJNZ] |= CF_JUMP; - // JP (HL) - lut[OP_JP_HL] |= CF_JUMP; - // RST t (C7, CF, D7, DF, E7, EF, F7, FF) - lut[0xC7] |= CF_RST; lut[0xCF] |= CF_RST; lut[0xD7] |= CF_RST; lut[0xDF] |= CF_RST; - lut[0xE7] |= CF_RST; lut[0xEF] |= CF_RST; lut[0xF7] |= CF_RST; lut[0xFF] |= CF_RST; - return lut; - }(); - - bool isCtrlFlowOpcode(uint8_t b0, uint8_t b1) { - if (kCtrlLut[b0] != CF_NONE) { return true; } - // RETN/RETI - if (b0 == OP_PREFIX_ED && (b1 == OP_RETN_ED || b1 == OP_RETI_ED)) { return true; } - // JP (IX)/(IY) - if ((b0 == OP_PREFIX_DD || b0 == OP_PREFIX_FD) && b1 == OP_JP_HL) { return true; } - return false; - } -} - // ----------------------------------------------- // Debugger Initialization // ----------------------------------------------- @@ -153,10 +95,6 @@ void MainWindow::debugInit() { // ------------------------------------------------ void MainWindow::debugDisable() { - if (m_suppressDebugCloseOnce) { - m_suppressDebugCloseOnce = false; - return; - } guiDebug = false; debugGuiState(false); } @@ -175,16 +113,6 @@ void MainWindow::debugStep(int mode) { m_stepCtx.active = true; m_stepCtx.seqNext = static_cast(disasm.next); - - if (mode == DBG_STEP_OVER) { - const uint32_t pc0 = cpu.registers.PC; - const uint8_t b0 = mem_peek_byte(pc0); - const uint8_t b1 = mem_peek_byte(pc0 + 1); - if (!isCtrlFlowOpcode(b0, b1)) { - mode = DBG_STEP_NEXT; - m_suppressDebugCloseOnce = true; - } - } debug_step(mode, static_cast(disasm.next)); } emu.resume(); diff --git a/gui/qt/mainwindow.h b/gui/qt/mainwindow.h index dde4dcbd..affc1f01 100644 --- a/gui/qt/mainwindow.h +++ b/gui/qt/mainwindow.h @@ -962,9 +962,6 @@ private slots: uint32_t seqNext = 0; // sequential next PC at step start } m_stepCtx; - // suppression to avoid GUI close/reopen flicker when mapping Step Over to Step Next - bool m_suppressDebugCloseOnce = false; - #ifdef LIBUSB_SUPPORT libusb_context *m_usbContext = nullptr; libusb_hotplug_callback_handle m_usbHotplugCallbackHandle{};