diff --git a/core/cpu.c b/core/cpu.c index e7139b5b3..3a4eb9f97 100644 --- a/core/cpu.c +++ b/core/cpu.c @@ -55,6 +55,19 @@ static void cpu_inst_start(void) { #endif } +#ifdef DEBUG_SUPPORT +static void debug_break_before_ret(const uint32_t len) { + if (unlikely(debug.untilRet)) { + const uint32_t curSp = cpu_address_mode(cpu.registers.stack[cpu.L].hl, cpu.L); + if (curSp >= debug.untilRetBase) { + const uint32_t start = cpu_mask_mode(cpu.registers.PC - (len + (cpu.SUFFIX ? 1u : 0u)), cpu.ADL); + cpu.registers.PC = start; + debug_open(DBG_STEP, cpu.registers.PC); + } + } +} +#endif + uint32_t cpu_address_mode(uint32_t address, bool mode) { if (mode) { return address & 0xFFFFFF; @@ -1185,6 +1198,9 @@ void cpu_execute(void) { cpu.cycles++; if (cpu_read_cc(context.y)) { r->R += 2; +#ifdef DEBUG_SUPPORT + debug_break_before_ret(1); +#endif cpu_return(); } break; @@ -1204,6 +1220,9 @@ void cpu_execute(void) { } switch (context.p) { case 0: /* RET */ +#ifdef DEBUG_SUPPORT + debug_break_before_ret(1); +#endif cpu_return(); break; case 1: /* EXX */ @@ -1217,10 +1236,21 @@ void cpu_execute(void) { REG_WRITE_EX(HL, r->HL, r->_HL); REG_WRITE_EX(HLP, r->_HL, w); break; - case 2: /* JP (rr) */ + case 2: { /* JP (rr) */ + uint32_t target = cpu_read_index(); cpu_prefetch_discard(); +#ifdef DEBUG_SUPPORT + if (unlikely(debug.untilRet)) { + uint32_t curSp = cpu_address_mode(cpu.registers.stack[cpu.L].hl, cpu.L); + /* if this indirect jp is a logical return for the frame + * where DBG_UNTIL_RET started, the hook rewinds PC and opens + * the debugger */ + debug_until_ret_handle_indirect_jump(target, curSp); + } +#endif cpu_jump(cpu_read_index(), cpu.L); break; + } case 3: /* LD SP, HL */ cpu_write_sp(cpu_read_index()); break; @@ -1485,6 +1515,9 @@ void cpu_execute(void) { /* This is actually identical to reti on the z80 */ case 1: /* RETI */ cpu.IEF1 = cpu.IEF2; +#ifdef DEBUG_SUPPORT + debug_break_before_ret(2); +#endif cpu_return(); break; case 2: /* LEA IY, IX + d */ diff --git a/core/debug/debug.c b/core/debug/debug.c index 204e6b244..b2e431bf1 100644 --- a/core/debug/debug.c +++ b/core/debug/debug.c @@ -312,6 +312,12 @@ void debug_step(int mode, uint32_t addr) { gui_debug_close(); debug.tempExec = addr; break; + case DBG_UNTIL_RET: + gui_debug_close(); + debug.untilRet = true; + debug.untilRetBase = cpu_address_mode(cpu.registers.stack[cpu.L].hl, cpu.L); + debug.untilRetIndex = debug.stackIndex; + break; case DBG_BASIC_STEP_IN: case DBG_BASIC_STEP_NEXT: gui_debug_close(); @@ -328,6 +334,32 @@ void debug_step(int mode, uint32_t addr) { void debug_clear_step(void) { debug.step = debug.stepOver = false; debug.tempExec = debug.stepOut = ~0u; + debug.untilRet = false; + debug.untilRetBase = 0; + debug.untilRetIndex = 0; +} + +void debug_until_ret_handle_indirect_jump(const uint32_t target, const uint32_t currentSp) { + const debug_stack_entry_t *e = &debug.stack[debug.untilRetIndex]; + + if (!(e->mode == cpu.L && + /* only consider frames above current SP baseline */ + e->stack >= debug.untilRetBase && + /* target must also match the frame's retAddr window */ + (target - e->retAddr) <= e->range && + /* SP restored to precall value, or frame popped */ + (currentSp == e->stack || e->popped))) { + return; + } + + const uint32_t len = 1 + (cpu.PREFIX != 0); + const uint32_t start = cpu_mask_mode( + cpu.registers.PC - (len + (cpu.SUFFIX ? 1u : 0u)), + cpu.ADL); + + cpu.registers.PC = start; + + debug_open(DBG_STEP, cpu.registers.PC); } void debug_clear_basic_step(void) { diff --git a/core/debug/debug.h b/core/debug/debug.h index a3ad09991..5dfd6b406 100644 --- a/core/debug/debug.h +++ b/core/debug/debug.h @@ -145,6 +145,9 @@ typedef struct { int64_t flashDelayCycles; bool step, stepOver; uint32_t tempExec, stepOut; + bool untilRet; + uint32_t untilRetBase; /* normalized 24bit stack pointer baseline */ + uint32_t untilRetIndex; /* call-stack index when DBG_UNTIL_RET started */ uint32_t stackIndex, stackSize; debug_stack_entry_t *stack; @@ -179,6 +182,7 @@ enum { DBG_STEP_OVER, DBG_STEP_NEXT, DBG_RUN_UNTIL, + DBG_UNTIL_RET, DBG_BASIC_STEP_IN, DBG_BASIC_STEP_NEXT, }; @@ -187,6 +191,7 @@ enum { void debug_step_switch(void); void debug_clear_step(void); void debug_clear_basic_step(void); +void debug_until_ret_handle_indirect_jump(uint32_t target, uint32_t currentSp); #endif /* register watchpoints */ diff --git a/gui/qt/debugger.cpp b/gui/qt/debugger.cpp index 7ded76e49..40f20b29a 100644 --- a/gui/qt/debugger.cpp +++ b/gui/qt/debugger.cpp @@ -147,6 +147,9 @@ void MainWindow::debugEnable() { void MainWindow::debugStep(int mode) { if (mode == DBG_RUN_UNTIL) { debug_step(mode, m_runUntilAddr); + } else if (mode == DBG_UNTIL_RET) { + // no address needed, cpu checks for returns internally + debug_step(mode, 0); } else { disasm.base = static_cast(cpu.registers.PC); disasmGet(true); @@ -679,6 +682,7 @@ void MainWindow::debugGuiState(bool state) const { ui->buttonStepOver->setEnabled(state); ui->buttonStepNext->setEnabled(state); ui->buttonStepOut->setEnabled(state); + ui->buttonUntilRet->setEnabled(state); ui->buttonCertID->setEnabled(state); ui->groupCPU->setEnabled(state); ui->groupFlags->setEnabled(state); @@ -3003,6 +3007,17 @@ void MainWindow::stepOut() { debugStep(DBG_STEP_OUT); } +void MainWindow::stepUntilRet() { + if (!guiDebug) { + return; + } + + disconnect(m_shortcutStepUntilRet, &QShortcut::activated, this, &MainWindow::stepUntilRet); + + debugSync(); + debugStep(DBG_UNTIL_RET); +} + //------------------------------------------------ // Other Functions //------------------------------------------------ diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index 8e44ae8d8..6fa28f417 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -170,6 +170,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U connect(ui->buttonStepOver, &QPushButton::clicked, this, &MainWindow::stepOver); connect(ui->buttonStepNext, &QPushButton::clicked, this, &MainWindow::stepNext); connect(ui->buttonStepOut, &QPushButton::clicked, this, &MainWindow::stepOut); + connect(ui->buttonUntilRet, &QPushButton::clicked, this, &MainWindow::stepUntilRet); connect(ui->buttonGoto, &QPushButton::clicked, this, &MainWindow::gotoPressed); connect(ui->console, &QWidget::customContextMenuRequested, this, &MainWindow::contextConsole); connect(m_disasm, &QWidget::customContextMenuRequested, this, &MainWindow::contextDisasm); @@ -521,6 +522,7 @@ 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_shortcutStepUntilRet = new QShortcut(QKeySequence(Qt::SHIFT | 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); @@ -547,6 +549,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U connect(m_shortcutStepOver, &QShortcut::activated, this, &MainWindow::stepOver); connect(m_shortcutStepNext, &QShortcut::activated, this, &MainWindow::stepNext); connect(m_shortcutStepOut, &QShortcut::activated, this, &MainWindow::stepOut); + connect(m_shortcutStepUntilRet, &QShortcut::activated, this, &MainWindow::stepUntilRet); setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); diff --git a/gui/qt/mainwindow.h b/gui/qt/mainwindow.h index dc13adf45..99d94f567 100644 --- a/gui/qt/mainwindow.h +++ b/gui/qt/mainwindow.h @@ -354,6 +354,7 @@ private slots: void stepOver(); void stepNext(); void stepOut(); + void stepUntilRet(); // os view void osUpdate(); @@ -710,6 +711,7 @@ private slots: QShortcut *m_shortcutStepOver; QShortcut *m_shortcutStepNext; QShortcut *m_shortcutStepOut; + QShortcut *m_shortcutStepUntilRet; QShortcut *m_shortcutNavBack; QShortcut *m_shortcutNavForward; QShortcut *m_shortcutDebug; diff --git a/gui/qt/mainwindow.ui b/gui/qt/mainwindow.ui index 9f8b5b9fc..5e63aa886 100644 --- a/gui/qt/mainwindow.ui +++ b/gui/qt/mainwindow.ui @@ -532,7 +532,7 @@ - + false @@ -552,8 +552,31 @@ :/icons/resources/icons/stepout.png:/icons/resources/icons/stepout.png - - + + + + + + false + + + + 0 + 0 + + + + Qt::NoFocus + + + Until RET + + + + :/icons/resources/icons/untilret.png:/icons/resources/icons/untilret.png + + + diff --git a/gui/qt/resources.qrc b/gui/qt/resources.qrc index b3b8a4391..45cf3c48c 100644 --- a/gui/qt/resources.qrc +++ b/gui/qt/resources.qrc @@ -72,6 +72,7 @@ resources/icons/stepout.png resources/icons/stepover.png resources/icons/stop.png + resources/icons/untilret.png resources/icons/timers.png resources/icons/toggle_console.png resources/icons/ui_edit.png diff --git a/gui/qt/resources/icons/stepout.png b/gui/qt/resources/icons/stepout.png index bfb2f4d0e..e29e6f97f 100755 Binary files a/gui/qt/resources/icons/stepout.png and b/gui/qt/resources/icons/stepout.png differ diff --git a/gui/qt/resources/icons/untilret.png b/gui/qt/resources/icons/untilret.png new file mode 100644 index 000000000..0c955bfc7 Binary files /dev/null and b/gui/qt/resources/icons/untilret.png differ