Skip to content

Commit 5330ab7

Browse files
authored
Execute until ret (#542)
* Implement execute until return * support JP (HL/IX/IY) tail calls * simplify debug_until_ret_handle_indirect_jump
1 parent cbfbb43 commit 5330ab7

10 files changed

Lines changed: 118 additions & 4 deletions

File tree

core/cpu.c

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ static void cpu_inst_start(void) {
5555
#endif
5656
}
5757

58+
#ifdef DEBUG_SUPPORT
59+
static void debug_break_before_ret(const uint32_t len) {
60+
if (unlikely(debug.untilRet)) {
61+
const uint32_t curSp = cpu_address_mode(cpu.registers.stack[cpu.L].hl, cpu.L);
62+
if (curSp >= debug.untilRetBase) {
63+
const uint32_t start = cpu_mask_mode(cpu.registers.PC - (len + (cpu.SUFFIX ? 1u : 0u)), cpu.ADL);
64+
cpu.registers.PC = start;
65+
debug_open(DBG_STEP, cpu.registers.PC);
66+
}
67+
}
68+
}
69+
#endif
70+
5871
uint32_t cpu_address_mode(uint32_t address, bool mode) {
5972
if (mode) {
6073
return address & 0xFFFFFF;
@@ -1185,6 +1198,9 @@ void cpu_execute(void) {
11851198
cpu.cycles++;
11861199
if (cpu_read_cc(context.y)) {
11871200
r->R += 2;
1201+
#ifdef DEBUG_SUPPORT
1202+
debug_break_before_ret(1);
1203+
#endif
11881204
cpu_return();
11891205
}
11901206
break;
@@ -1204,6 +1220,9 @@ void cpu_execute(void) {
12041220
}
12051221
switch (context.p) {
12061222
case 0: /* RET */
1223+
#ifdef DEBUG_SUPPORT
1224+
debug_break_before_ret(1);
1225+
#endif
12071226
cpu_return();
12081227
break;
12091228
case 1: /* EXX */
@@ -1217,10 +1236,21 @@ void cpu_execute(void) {
12171236
REG_WRITE_EX(HL, r->HL, r->_HL);
12181237
REG_WRITE_EX(HLP, r->_HL, w);
12191238
break;
1220-
case 2: /* JP (rr) */
1239+
case 2: { /* JP (rr) */
1240+
uint32_t target = cpu_read_index();
12211241
cpu_prefetch_discard();
1242+
#ifdef DEBUG_SUPPORT
1243+
if (unlikely(debug.untilRet)) {
1244+
uint32_t curSp = cpu_address_mode(cpu.registers.stack[cpu.L].hl, cpu.L);
1245+
/* if this indirect jp is a logical return for the frame
1246+
* where DBG_UNTIL_RET started, the hook rewinds PC and opens
1247+
* the debugger */
1248+
debug_until_ret_handle_indirect_jump(target, curSp);
1249+
}
1250+
#endif
12221251
cpu_jump(cpu_read_index(), cpu.L);
12231252
break;
1253+
}
12241254
case 3: /* LD SP, HL */
12251255
cpu_write_sp(cpu_read_index());
12261256
break;
@@ -1485,6 +1515,9 @@ void cpu_execute(void) {
14851515
/* This is actually identical to reti on the z80 */
14861516
case 1: /* RETI */
14871517
cpu.IEF1 = cpu.IEF2;
1518+
#ifdef DEBUG_SUPPORT
1519+
debug_break_before_ret(2);
1520+
#endif
14881521
cpu_return();
14891522
break;
14901523
case 2: /* LEA IY, IX + d */

core/debug/debug.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,12 @@ void debug_step(int mode, uint32_t addr) {
312312
gui_debug_close();
313313
debug.tempExec = addr;
314314
break;
315+
case DBG_UNTIL_RET:
316+
gui_debug_close();
317+
debug.untilRet = true;
318+
debug.untilRetBase = cpu_address_mode(cpu.registers.stack[cpu.L].hl, cpu.L);
319+
debug.untilRetIndex = debug.stackIndex;
320+
break;
315321
case DBG_BASIC_STEP_IN:
316322
case DBG_BASIC_STEP_NEXT:
317323
gui_debug_close();
@@ -328,6 +334,32 @@ void debug_step(int mode, uint32_t addr) {
328334
void debug_clear_step(void) {
329335
debug.step = debug.stepOver = false;
330336
debug.tempExec = debug.stepOut = ~0u;
337+
debug.untilRet = false;
338+
debug.untilRetBase = 0;
339+
debug.untilRetIndex = 0;
340+
}
341+
342+
void debug_until_ret_handle_indirect_jump(const uint32_t target, const uint32_t currentSp) {
343+
const debug_stack_entry_t *e = &debug.stack[debug.untilRetIndex];
344+
345+
if (!(e->mode == cpu.L &&
346+
/* only consider frames above current SP baseline */
347+
e->stack >= debug.untilRetBase &&
348+
/* target must also match the frame's retAddr window */
349+
(target - e->retAddr) <= e->range &&
350+
/* SP restored to precall value, or frame popped */
351+
(currentSp == e->stack || e->popped))) {
352+
return;
353+
}
354+
355+
const uint32_t len = 1 + (cpu.PREFIX != 0);
356+
const uint32_t start = cpu_mask_mode(
357+
cpu.registers.PC - (len + (cpu.SUFFIX ? 1u : 0u)),
358+
cpu.ADL);
359+
360+
cpu.registers.PC = start;
361+
362+
debug_open(DBG_STEP, cpu.registers.PC);
331363
}
332364

333365
void debug_clear_basic_step(void) {

core/debug/debug.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ typedef struct {
145145
int64_t flashDelayCycles;
146146
bool step, stepOver;
147147
uint32_t tempExec, stepOut;
148+
bool untilRet;
149+
uint32_t untilRetBase; /* normalized 24bit stack pointer baseline */
150+
uint32_t untilRetIndex; /* call-stack index when DBG_UNTIL_RET started */
148151

149152
uint32_t stackIndex, stackSize;
150153
debug_stack_entry_t *stack;
@@ -179,6 +182,7 @@ enum {
179182
DBG_STEP_OVER,
180183
DBG_STEP_NEXT,
181184
DBG_RUN_UNTIL,
185+
DBG_UNTIL_RET,
182186
DBG_BASIC_STEP_IN,
183187
DBG_BASIC_STEP_NEXT,
184188
};
@@ -187,6 +191,7 @@ enum {
187191
void debug_step_switch(void);
188192
void debug_clear_step(void);
189193
void debug_clear_basic_step(void);
194+
void debug_until_ret_handle_indirect_jump(uint32_t target, uint32_t currentSp);
190195
#endif
191196

192197
/* register watchpoints */

gui/qt/debugger.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ void MainWindow::debugEnable() {
147147
void MainWindow::debugStep(int mode) {
148148
if (mode == DBG_RUN_UNTIL) {
149149
debug_step(mode, m_runUntilAddr);
150+
} else if (mode == DBG_UNTIL_RET) {
151+
// no address needed, cpu checks for returns internally
152+
debug_step(mode, 0);
150153
} else {
151154
disasm.base = static_cast<int32_t>(cpu.registers.PC);
152155
disasmGet(true);
@@ -678,6 +681,7 @@ void MainWindow::debugGuiState(bool state) const {
678681
ui->buttonStepOver->setEnabled(state);
679682
ui->buttonStepNext->setEnabled(state);
680683
ui->buttonStepOut->setEnabled(state);
684+
ui->buttonUntilRet->setEnabled(state);
681685
ui->buttonCertID->setEnabled(state);
682686
ui->groupCPU->setEnabled(state);
683687
ui->groupFlags->setEnabled(state);
@@ -3002,6 +3006,17 @@ void MainWindow::stepOut() {
30023006
debugStep(DBG_STEP_OUT);
30033007
}
30043008

3009+
void MainWindow::stepUntilRet() {
3010+
if (!guiDebug) {
3011+
return;
3012+
}
3013+
3014+
disconnect(m_shortcutStepUntilRet, &QShortcut::activated, this, &MainWindow::stepUntilRet);
3015+
3016+
debugSync();
3017+
debugStep(DBG_UNTIL_RET);
3018+
}
3019+
30053020
//------------------------------------------------
30063021
// Other Functions
30073022
//------------------------------------------------

gui/qt/mainwindow.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
183183
connect(ui->buttonStepOver, &QPushButton::clicked, this, &MainWindow::stepOver);
184184
connect(ui->buttonStepNext, &QPushButton::clicked, this, &MainWindow::stepNext);
185185
connect(ui->buttonStepOut, &QPushButton::clicked, this, &MainWindow::stepOut);
186+
connect(ui->buttonUntilRet, &QPushButton::clicked, this, &MainWindow::stepUntilRet);
186187
connect(ui->buttonGoto, &QPushButton::clicked, this, &MainWindow::gotoPressed);
187188
connect(ui->console, &QWidget::customContextMenuRequested, this, &MainWindow::contextConsole);
188189
connect(m_disasm, &QWidget::customContextMenuRequested, this, &MainWindow::contextDisasm);
@@ -534,6 +535,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
534535
m_shortcutStepOver = new QShortcut(QKeySequence(Qt::Key_F7), this);
535536
m_shortcutStepNext = new QShortcut(QKeySequence(Qt::Key_F8), this);
536537
m_shortcutStepOut = new QShortcut(QKeySequence(Qt::Key_F9), this);
538+
m_shortcutStepUntilRet = new QShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F9), this);
537539
m_shortcutNavBack = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Left), this);
538540
m_shortcutNavForward = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_Right), this);
539541

@@ -560,6 +562,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U
560562
connect(m_shortcutStepOver, &QShortcut::activated, this, &MainWindow::stepOver);
561563
connect(m_shortcutStepNext, &QShortcut::activated, this, &MainWindow::stepNext);
562564
connect(m_shortcutStepOut, &QShortcut::activated, this, &MainWindow::stepOut);
565+
connect(m_shortcutStepUntilRet, &QShortcut::activated, this, &MainWindow::stepUntilRet);
563566

564567
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
565568
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

gui/qt/mainwindow.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ private slots:
366366
void stepOver();
367367
void stepNext();
368368
void stepOut();
369+
void stepUntilRet();
369370

370371
// os view
371372
void osUpdate();
@@ -722,6 +723,7 @@ private slots:
722723
QShortcut *m_shortcutStepOver;
723724
QShortcut *m_shortcutStepNext;
724725
QShortcut *m_shortcutStepOut;
726+
QShortcut *m_shortcutStepUntilRet;
725727
QShortcut *m_shortcutNavBack;
726728
QShortcut *m_shortcutNavForward;
727729
QShortcut *m_shortcutDebug;

gui/qt/mainwindow.ui

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@
532532
</widget>
533533
</item>
534534
<item>
535-
<widget class="QPushButton" name="buttonStepOut">
535+
<widget class="QPushButton" name="buttonStepOut">
536536
<property name="enabled">
537537
<bool>false</bool>
538538
</property>
@@ -552,8 +552,31 @@
552552
<iconset resource="resources.qrc">
553553
<normaloff>:/icons/resources/icons/stepout.png</normaloff>:/icons/resources/icons/stepout.png</iconset>
554554
</property>
555-
</widget>
556-
</item>
555+
</widget>
556+
</item>
557+
<item>
558+
<widget class="QPushButton" name="buttonUntilRet">
559+
<property name="enabled">
560+
<bool>false</bool>
561+
</property>
562+
<property name="sizePolicy">
563+
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
564+
<horstretch>0</horstretch>
565+
<verstretch>0</verstretch>
566+
</sizepolicy>
567+
</property>
568+
<property name="focusPolicy">
569+
<enum>Qt::NoFocus</enum>
570+
</property>
571+
<property name="text">
572+
<string>Until RET</string>
573+
</property>
574+
<property name="icon">
575+
<iconset resource="resources.qrc">
576+
<normaloff>:/icons/resources/icons/untilret.png</normaloff>:/icons/resources/icons/untilret.png</iconset>
577+
</property>
578+
</widget>
579+
</item>
557580
<item>
558581
<widget class="QToolButton" name="buttonToggleBreakpoints">
559582
<property name="enabled">

gui/qt/resources.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<file>resources/icons/stepout.png</file>
7373
<file>resources/icons/stepover.png</file>
7474
<file>resources/icons/stop.png</file>
75+
<file>resources/icons/untilret.png</file>
7576
<file>resources/icons/timers.png</file>
7677
<file>resources/icons/toggle_console.png</file>
7778
<file>resources/icons/ui_edit.png</file>

gui/qt/resources/icons/stepout.png

2.96 KB
Loading
3.58 KB
Loading

0 commit comments

Comments
 (0)