Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/debug/debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
192 changes: 191 additions & 1 deletion gui/qt/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <QtGui/QWindow>
#include <QtGui/QScreen>
#include <algorithm>
#include <array>

#ifdef _MSC_VER
#include <direct.h>
Expand All @@ -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<uint8_t, 256> 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
// -----------------------------------------------
Expand Down Expand Up @@ -95,6 +153,10 @@ void MainWindow::debugInit() {
// ------------------------------------------------

void MainWindow::debugDisable() {
if (m_suppressDebugCloseOnce) {
m_suppressDebugCloseOnce = false;
return;
}
guiDebug = false;
debugGuiState(false);
}
Expand All @@ -110,6 +172,19 @@ void MainWindow::debugStep(int mode) {
} else {
disasm.base = static_cast<int32_t>(cpu.registers.PC);
disasmGet(true);

m_stepCtx.active = true;
m_stepCtx.seqNext = static_cast<uint32_t>(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<uint32_t>(disasm.next));
}
emu.resume();
Expand Down Expand Up @@ -392,6 +467,7 @@ void MainWindow::debugCommand(int reason, uint32_t data) {

if (reason == DBG_READY) {
guiReset = false;
navDisasmClear();
emu.resume();
return;
}
Expand Down Expand Up @@ -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<uint32_t>(cpu.registers.PC) != m_stepCtx.seqNext);
if (tookBranch) {
navDisasmPush(m_prevDisasmAddr, true);
} else {
navDisasmReplace(m_prevDisasmAddr, true);
}
m_stepCtx.active = false;
}

memUpdate();

Expand Down Expand Up @@ -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<uint32_t>(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<int>(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<int>(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<int>(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<int>(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<int>(e.addr), e.pane);
m_isApplyingDisasmNav = false;
return true;
}
return false;
}

void MainWindow::navDisasmClear() {
m_disasmNav.clear();
m_disasmNavIndex = -1;
}

// ------------------------------------------------
// Misc
// ------------------------------------------------
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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<QMouseEvent*>(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();

Expand Down
17 changes: 16 additions & 1 deletion gui/qt/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
m_breakpoints = ui->breakpoints;
m_ports = ui->ports;
m_disasm = ui->disasm;
m_disasm->installEventFilter(this);

ui->console->setMaximumBlockCount(1000);

Expand Down Expand Up @@ -158,9 +159,9 @@

// debug actions
connect(ui->buttonRun, &QPushButton::clicked, this, &MainWindow::debugToggle);
connect(ui->checkADLDisasm, &QCheckBox::stateChanged, this, &MainWindow::disasmUpdate);

Check warning on line 162 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]

Check warning on line 162 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]
connect(ui->checkADLStack, &QCheckBox::stateChanged, this, &MainWindow::stackUpdate);

Check warning on line 163 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]

Check warning on line 163 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]
connect(ui->checkADL, &QCheckBox::stateChanged, [this]{ disasmUpdate(); stackUpdate(); });

Check warning on line 164 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]

Check warning on line 164 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]
connect(ui->buttonAddPort, &QPushButton::clicked, this, &MainWindow::portAddSlot);
connect(ui->buttonAddBreakpoint, &QPushButton::clicked, this, &MainWindow::breakAddSlot);
connect(ui->buttonAddWatchpoint, &QPushButton::clicked, this, &MainWindow::watchAddSlot);
Expand Down Expand Up @@ -375,7 +376,7 @@
#ifdef PNG_WRITE_APNG_SUPPORTED
connect(ui->buttonRecordAnimated, &QPushButton::clicked, this, &MainWindow::recordAnimated);
connect(ui->apngSkip, &QSlider::valueChanged, this, &MainWindow::setFrameskip);
connect(ui->checkOptimizeRecording, &QCheckBox::stateChanged, this, &MainWindow::setOptimizeRecord);

Check warning on line 379 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]

Check warning on line 379 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]
#else
ui->actionRecordAnimated->setEnabled(false);
ui->buttonRecordAnimated->setEnabled(false);
Expand All @@ -390,13 +391,13 @@
connect(ui->actionReportBug, &QAction::triggered, []{ QDesktopServices::openUrl(QUrl("https://github.com/CE-Programming/CEmu/issues")); });

// other gui actions
connect(ui->checkAllowGroupDrag, &QCheckBox::stateChanged, this, &MainWindow::setDockGroupDrag);

Check warning on line 394 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]

Check warning on line 394 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]
connect(ui->buttonRunSetup, &QPushButton::clicked, this, &MainWindow::runSetup);
connect(ui->scaleLCD, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setLcdScale);
connect(ui->upscaleLCD, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MainWindow::setLcdUpscale);
connect(ui->fullscreenLCD, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MainWindow::setLcdFullscreen);
connect(ui->guiSkip, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setGuiSkip);
connect(ui->checkSkin, &QCheckBox::stateChanged, this, &MainWindow::setSkinToggle);

Check warning on line 400 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]

Check warning on line 400 in gui/qt/mainwindow.cpp

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

‘void QCheckBox::stateChanged(int)’ is deprecated: Use checkStateChanged() instead [-Wdeprecated-declarations]
connect(ui->comboBoxAsicRev, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MainWindow::setAsicRevision);
connect(ui->checkPythonEdition, &QCheckBox::stateChanged, this, &MainWindow::setPythonEdition);
connect(ui->checkKeypadGhosting, &QCheckBox::stateChanged, this, &MainWindow::setKeypadGhosting);
Expand Down Expand Up @@ -518,6 +519,11 @@
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);
Expand Down Expand Up @@ -2695,6 +2701,11 @@
uint32_t addr = static_cast<uint32_t>(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);
Expand All @@ -2706,7 +2717,11 @@
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<int>(cpu.registers.PC), true);
Expand Down
28 changes: 28 additions & 0 deletions gui/qt/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -937,6 +939,32 @@ private slots:
QTableWidget *m_ports = nullptr;
DataWidget *m_disasm = nullptr;

struct DisasmNavEntry {
uint32_t addr;
bool pane;
};

QVector<DisasmNavEntry> 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{};
Expand Down
Loading