Skip to content

Commit af8642e

Browse files
Fix more stack exhaustion scenarios
Invoking external programs with DeskTop and Selector both "leaked" stack space. Plug those holes by resetting the stack before launching anything external.
1 parent 8b2496a commit af8642e

File tree

7 files changed

+87
-33
lines changed

7 files changed

+87
-33
lines changed

RELEASE-NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ Project Page: https://github.com/a2stuff/a2d
3333
* Add Apple+Escape as shortcut to clear selection.
3434
* Prevent volume icon flicker on startup with empty Disk II drives.
3535
* Don't show About dialog when Ctrl+Shift+2 is pressed.
36+
* Fix hang after 60 restarts.
3637

3738
### Selector
3839

40+
* Fix potential hang after 30 restarts.
3941

4042
### Disk Copy
4143

src/desktop/main.s

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4930,6 +4930,10 @@ show_unexpected_errors_flag:
49304930
;; Restore system state: devices, /RAM, ROM/ZP banks.
49314931
jsr RestoreSystem
49324932

4933+
;; Reset stack
4934+
ldx #$FF
4935+
txs
4936+
49334937
;; also used by launcher code
49344938
target := *+1
49354939
jmp SELF_MODIFIED

src/inc/prodos.inc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -488,15 +488,16 @@ int_num: .byte 0
488488
.endparams
489489
.endmacro
490490
491-
.macro DEFINE_QUIT_PARAMS name, ext, pathname
491+
.macro DEFINE_QUIT_PARAMS name, type, pathname
492492
WARN_IF_SHADOWING name
493493
.ifnblank pathname
494494
.assert pathname < $C000, error, "MLI params can't be in LC RAM"
495495
.endif
496496
.params name
497497
param_count: .byte 4
498-
.ifnblank ext
499-
.byte ext
498+
quit_type:
499+
.ifnblank type
500+
.byte type
500501
.else
501502
.byte 0
502503
.endif

src/lib/invoker.s

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222

2323
;; Set prefix
2424
MLI_CALL SET_PREFIX, invoker::set_prefix_params
25-
bcs ret
25+
bcs quit
2626

2727
;; Check file type
2828
MLI_CALL GET_FILE_INFO, invoker::get_info_params
29-
bcs ret
29+
bcs quit
3030
lda invoker::get_info_params::file_type
3131

3232
;;; Binary file (BIN) - load and invoke at A$=AuxType
@@ -55,10 +55,12 @@
5555

5656
;;; ProDOS 16 System file (S16) - invoke via QUIT call
5757
cmp #FT_S16
58-
beq quit_call
58+
bne quit
59+
copy8 #$EE, invoker::quit_params::quit_type
60+
FALL_THROUGH_TO quit
5961

60-
;; Failure
61-
ret: rts
62+
quit:
63+
MLI_CALL QUIT, invoker::quit_params
6264

6365
;;; ============================================================
6466
;;; Interpreter - `INVOKER_INTERPRETER` populated by caller
@@ -86,19 +88,19 @@ use_interpreter:
8688

8789
load_target:
8890
MLI_CALL OPEN, invoker::open_params
89-
bcs exit
91+
bcs quit
9092
lda invoker::open_params::ref_num
9193
sta invoker::read_params::ref_num
9294
MLI_CALL READ, invoker::read_params
93-
bcs exit
95+
bcs quit
9496
MLI_CALL CLOSE, invoker::close_params
95-
bcs exit
97+
bcs quit
9698

9799
;; If interpreter, copy filename to interpreter buffer.
98100
lda INVOKER_INTERPRETER
99101
IF NOT_ZERO
100102
MLI_CALL SET_PREFIX, invoker::set_prefix_params
101-
bcs exit
103+
bcs quit
102104
ldy INVOKER_FILENAME
103105
DO
104106
lda INVOKER_FILENAME,y
@@ -136,16 +138,11 @@ load_target:
136138

137139
;; Set return address to the QUIT call below
138140
;; (mostly for invoking BIN files)
139-
PUSH_RETURN_ADDRESS quit_call
141+
PUSH_RETURN_ADDRESS quit
140142

141143
jmp_addr := *+1
142144
jmp PRODOS_SYS_START
143145

144-
quit_call:
145-
MLI_CALL QUIT, invoker::quit_params
146-
147-
exit: rts
148-
149146
;;; ============================================================
150147

151148
PAD_TO ::INVOKER_INTERPRETER
@@ -166,7 +163,7 @@ exit: rts
166163
DEFINE_GET_FILE_INFO_PARAMS get_info_params, INVOKER_FILENAME
167164

168165
;; $EE = extended call signature for IIgs/GS/OS variation.
169-
DEFINE_QUIT_PARAMS quit_params, $EE, INVOKER_FILENAME
166+
DEFINE_QUIT_PARAMS quit_params, $0, INVOKER_FILENAME
170167

171168

172169
.endscope ; invoker

src/selector/app.s

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,14 +1591,11 @@ check_path:
15911591

15921592
jsr RestoreSystem
15931593

1594-
jsr INVOKER
1595-
1596-
;; If we got here, invoker failed somehow. Relaunch.
1597-
jsr Bell
1598-
jsr Bell
1599-
jsr Bell
1600-
MLI_CALL QUIT, quit_params
1601-
brk
1594+
;; Reset stack
1595+
ldx #$FF
1596+
txs
1597+
1598+
jmp INVOKER
16021599

16031600
.endproc ; LaunchPath
16041601

tests/lib/test.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ function test.ExpectLessThanOrEqual(a, b, message, options, level)
128128
test.Expect(a <= b, message .. " - actual " .. format(a) .. " should be <= " .. format(b), options, inc(level))
129129
end
130130

131+
function test.ExpectGreaterThan(a, b, message, options, level)
132+
test.Expect(a > b, message .. " - actual " .. format(a) .. " should be > " .. format(b), options, inc(level))
133+
end
134+
135+
function test.ExpectGreaterThanOrEqual(a, b, message, options, level)
136+
test.Expect(a >= b, message .. " - actual " .. format(a) .. " should be >= " .. format(b), options, inc(level))
137+
end
138+
131139
function test.ExpectError(pattern, func, message, options, level)
132140
local status, err = pcall(func)
133141
test.Expect(not status, "saw no error; " .. message, options, inc(level))

tests/stack_exhaustion.lua

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,61 @@ DISKARGS="-hard1 $HARDIMG"
1111
-- however.
1212

1313
test.Step(
14-
"Stack exhaustion (takes about 122 seconds to run)",
14+
"Restarting launcher (takes about 122 seconds to run)",
1515
function()
16-
local cpu = manager.machine.devices[":maincpu"]
16+
a2d.OpenPath("/A2.DESKTOP")
1717

1818
-- Prior to fix, crashes around iteration 40
19-
for i = 1,50 do
20-
--print(string.format("i=%d SP=%04X", i, cpu.state.SP.value))
21-
a2d.CloseAllWindows()
22-
a2d.OpenPath("/A2.DESKTOP/DESKTOP.SYSTEM")
19+
local cpu = manager.machine.devices[":maincpu"]
20+
for i = 1, 50 do
21+
a2d.SelectAndOpen("DESKTOP.SYSTEM")
22+
a2d.WaitForRestart()
23+
print(string.format("i=%d SP=%02X", i, cpu.state.SP.value))
24+
test.Expect(not apple2.IsCrashedToMonitor(), "should not have crashed to monitor")
25+
test.ExpectGreaterThan(cpu.state.SP.value, 0x120, "stack should not be exausted")
26+
end
27+
28+
a2d.CloseAllWindows()
29+
end)
30+
31+
test.Step(
32+
"Running external programs - DeskTop",
33+
function()
34+
a2d.OpenPath("/A2.DESKTOP/SAMPLE.MEDIA")
35+
36+
-- Prior to fix, hangs around iteration 60 (but doesn't crash to monitor)
37+
local cpu = manager.machine.devices[":maincpu"]
38+
for i = 1, 70 do
39+
a2d.SelectAndOpen("KARATEKA.YELL")
2340
a2d.WaitForRestart()
41+
print(string.format("i=%d SP=%02X", i, cpu.state.SP.value))
2442
test.Expect(not apple2.IsCrashedToMonitor(), "should not have crashed to monitor")
43+
test.ExpectGreaterThan(cpu.state.SP.value, 0x120, "stack should not be exausted")
2544
end
45+
46+
a2d.CloseAllWindows()
47+
end)
48+
49+
test.Step(
50+
"Running external programs - Selector",
51+
function()
52+
a2d.AddShortcut("/A2.DESKTOP/SAMPLE.MEDIA/KARATEKA.YELL")
53+
a2d.ToggleOptionShowShortcutsOnStartup()
54+
a2d.Reboot()
55+
56+
-- Prior to fix, runs out of stack around iteration 30 (but doesn't crash!)
57+
local cpu = manager.machine.devices[":maincpu"]
58+
for i = 1, 40 do
59+
apple2.Type("1")
60+
a2d.DialogOK()
61+
a2d.WaitForRestart()
62+
print(string.format("i=%d SP=%02X", i, cpu.state.SP.value))
63+
test.Expect(not apple2.IsCrashedToMonitor(), "should not have crashed to monitor")
64+
test.ExpectGreaterThan(cpu.state.SP.value, 0x120, "stack should not be exausted")
65+
end
66+
67+
apple2.Type("D") -- Desktop
68+
a2d.WaitForRestart()
69+
a2d.DeletePath("/A2.DESKTOP/LOCAL")
70+
a2d.Reboot()
2671
end)

0 commit comments

Comments
 (0)