ORANGE-4 でプログラムの実行と停止を繰り返すとスタックオーバーフローすることがある

[ORANGE-4](http://www.picosoft.co.jp/ORANGE-4/) Ver 1.09 には、値のプッシュ・ポップや、サブルーチン呼び出し・復帰という、スタックを使う命令がある。
しかし、RSTキーによるプログラムの実行停止時や、RUNキーによるプログラムの実行開始時にはスタックポインタが初期化されないため、スタックへのプッシュとポップが適切に対応している正しいプログラムでも、実行と停止を繰り返すとスタックオーバーフローを起こしてプログラムが破壊されることがある。

# プログラム例

以下のプログラムは、関数をネストして呼び出し、7セグメントLEDに表示する値を1ずつ増やしていくものである。
[MikeAssembler](https://mikecat.github.io/mike_assembler/) でアセンブルできる。

```
target orange4

	ldi 0
	ldyi 4
loop1:
	call func1
	outn
	jmpf loop1

func1:
	call func2
	ret

func2:
	call func3
	ret

func3:
	call func4
	ret

func4:
	call func5
	ret

func5:
	addi 1
	ay
	scall 0xC
	ay
	ret
```

以下は、このプログラムの ORANGE-4 のモニター用表現である。

```
E00:80A4F600D1F04F60
E10:15F61F601DF61F60
E20:25F61F602DF61913
E30:EC3F61
```

# ステップ実行による動作の確認

モニターを用い、ステップ実行でこのプログラムが適切に動作することを確認する。

```

ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: FFF    jmpf   FF
A=F, B=F, Y=F, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|

>E00:80A4F600D1F04F60
>E10:15F61F601DF61F60
>E20:25F61F602DF61913
>E30:EC3F61
>x
00: 80     ldi    0
A=F, B=F, Y=F, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
02: A4     ldyi   4
A=0, B=F, Y=F, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
04: F600D  call   0D
A=0, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
0D: F6015  call   15
A=0, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FD
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
15: F601D  call   1D
A=0, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FB
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
1D: F6025  call   25
A=0, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F9
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
25: F602D  call   2D
A=0, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F7
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
2D: 91     addi   1
A=0, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F5
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
2F: 3      ay
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=0, SP=F5
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
30: EC     scall  C
A=4, B=F, Y=1, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F5
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
32: 3      ay
A=4, B=F, Y=1, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F5
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
33: F61    ret
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F5
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
2A: F61    ret
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F7
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
22: F61    ret
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F9
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
1A: F61    ret
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FB
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
12: F61    ret
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FD
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
09: 1      outn
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
0A: F04    jmpf   04
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>t
04: F600D  call   0D
A=1, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>
```

スタックポインタは最大で F5 まで進んだ。
これは F0～FF のスタック領域に収まっており、問題ない。
また、04 番地から実行を進め、04 番地に戻ってきたとき、スタックポインタは同じ FF となっている。
よって、プッシュとポップ (今回は call と ret) の対応が取れており、問題ない。

# RUN による実行

一旦ハードウェアリセットし、D → RUN でモニターを起動し、プログラムを流し込んだ。

```
ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: FFF    jmpf   FF
A=F, B=F, Y=F, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|

>E00:80A4F600D1F04F60
>E10:15F61F601DF61F60
>E20:25F61F602DF61913
>E30:EC3F61
>x
00: 80     ldi    0
A=F, B=F, Y=F, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=FF
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
>q
```

そして、1 → RUN でプログラムを実行し、数秒後 RST キーで実行を止めた。
そして、D → RUN で再びモニターを起動し、状態を確認した。

```
ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: 80     ldi    0
A=4, B=F, Y=5, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=F5
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|

>
```

すると、プログラムカウンタは 00 に戻っているにもかかわらず、スタックポインタは初期値の FF から F5 に変化している。
しかし、これだけではまだプログラムの実行開始時にスタックポインタを初期化する可能性が考えられる。
そこで、一旦 `q` コマンドでモニターを抜け、もう一度 1 → RUN でプログラムを実行し、数秒後 RST キーで実行を止めた。
その後 D → RUN でモニターを起動すると、以下の状態になった。

```
ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: 80     ldi    0
A=4, B=F, Y=6, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=EB
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|

>
```

スタックポインタの値がさらに減り、EB となった。
プログラムの実行開始時にもスタックポインタは初期化されず、変化が蓄積されている。
スタックポインタが 80～EF の「プログラム領域/スタック領域」に侵入しており、ここにプログラムを書いていた場合破壊される恐れがある。
しかし、ここはまだ「スタック領域」でもある。
`q` コマンドでモニターを抜け、プログラムの実行と停止をさらに10回繰り返してみた。
そして、D → RUN でモニターを起動すると、以下の状態になった。

```
ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: 80     ldi    0
A=4, B=F, Y=4, Z=F, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=87
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|

>
```

スタックポインタは 87 まで進み、「プログラム領域/スタック領域」の端が近付いてきた。
`q` コマンドでモニターを抜け、プログラムの実行と停止をさらに繰り返してみた。
すると、3回繰り返したところで、最初に7セグメントLEDに値が表示されるまでの時間が長くなった気がし、さらに7セグメントLEDに表示される値が「2」固定になった。
そこで、D → RUN でモニターを起動してみると、以下の状態であった。

```
ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: 80     ldi    0
A=A, B=2, Y=2, Z=2, A'=F, B'=F, Y'=F, Z'=F, flag=1, SP=69
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|

>
```

スタックポインタは 69 となっている。
「プログラム領域/スタック領域」を超え、60～7F の「システム領域」に侵入している。
ここで、モニターでメモリの状態を見てみる。

```
>d
|00|01|02|03|04|05|06|07|08|09|0A|0B|0C|0D|0E|0F|
| 8| 0| A| 4| F| 6| 0| 0| D| 1| F| 0| 4| F| 6| 0|
|10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F|
| 1| 5| F| 6| 1| F| 6| 0| 1| D| F| 6| 1| F| 6| 0|
|20|21|22|23|24|25|26|27|28|29|2A|2B|2C|2D|2E|2F|
| 2| 5| F| 6| 1| F| 6| 0| 2| D| F| 6| 1| 9| 1| 3|
|30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F|
| E| C| 3| F| 6| 1| F| F| F| F| F| F| F| F| F| F|
|40|41|42|43|44|45|46|47|48|49|4A|4B|4C|4D|4E|4F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F| F|
|60|61|62|63|64|65|66|67|68|69|6A|6B|6C|6D|6E|6F|
| F| F| F| F| F| F| F| F| F| F| A| 2| 2| 2| 2| A|
|70|71|72|73|74|75|76|77|78|79|7A|7B|7C|7D|7E|7F|
| 2| 1| 9| 0| A| 2| 2| 2| A| 1| 2| 1| 9| 0| A| 2|
>
```

Aレジスタは 6F 番地に、Yレジスタは 6E 番地に割り当てられている。
`call` 命令により戻り先として 6E 番地に「A」、6F 番地に「1」が書き込まれることにより、AレジスタやYレジスタの値が破壊され、7セグメントLEDに表示する値や `scall 0xC` 命令による待ち時間に影響していることがわかる。

`q` コマンドでモニターを抜け、プログラムの実行と停止をさらに繰り返してみた。
すると、6回繰り返したところで、7セグメントLEDの表示が「0」固定になった。
そこで、D → RUN でモニターを起動し、`d` コマンドでメモリの状態を確認すると、以下の状態であった。

```
ORANGE-4 Ver 1.09 Copyright (C) 2017 by picosoft

00: 80     ldi    0
A=0, B=2, Y=4, Z=1, A'=0, B'=1, Y'=9, Z'=2, flag=1, SP=2B
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| A| 1| 2| 1| 9| 0| A| 2| 2| 2| A| 1| 2| 1| 9| 0|

>d
|00|01|02|03|04|05|06|07|08|09|0A|0B|0C|0D|0E|0F|
| 8| 0| A| 4| F| 6| 0| 0| D| 1| F| 0| 4| F| 6| 0|
|10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F|
| 1| 5| F| 6| 1| F| 6| 0| 1| D| F| 6| 1| F| 6| 0|
|20|21|22|23|24|25|26|27|28|29|2A|2B|2C|2D|2E|2F|
| 2| 5| F| 6| 1| F| 2| 2| A| 1| 2| 1| 9| 0| A| 2|
|30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F|
| 2| 2| A| 1| 2| 1| 9| 0| A| 2| 2| 2| A| 1| 2| 1|
|40|41|42|43|44|45|46|47|48|49|4A|4B|4C|4D|4E|4F|
| 9| 0| A| 2| 2| 2| A| 1| 2| 1| 9| 0| A| 2| 2| 2|
|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
| A| 1| 2| 1| 9| 0| A| 2| 2| 2| A| 1| 2| 1| 9| 0|
|60|61|62|63|64|65|66|67|68|69|6A|6B|6C|6D|6E|6F|
| A| 2| 2| 2| A| 1| 2| 1| 9| 0| A| 2| 2| 1| 4| 0|
|70|71|72|73|74|75|76|77|78|79|7A|7B|7C|7D|7E|7F|
| 2| 1| 9| 0| A| 2| 2| 2| A| 1| 2| 1| 9| 0| A| 2|
>
```

スタックポインタは 2B となった。
システム領域 (60～7F)、データ領域 (50～5F) を超え、プログラム領域 (00～4F) にまでスタックポインタが侵入し、書き込んだプログラムが破壊されてしまった。
プログラムが破壊され、プッシュとポップの対応などプログラムの正しさが保証できないため、検証はここで終了とする。

# モニターを使わない検証

モニターを使わずに手動でプログラムを打ち込み、プログラムの実行と停止を繰り返してみた。
すると、モニターで確認しながらの検証と同様に、

* 7セグメントLEDの表示が「2」で固定される
* 7セグメントLEDの表示が「0」で固定される
* 書き込んだプログラムが破壊される

という現象が確認できた。

<iframe width="560" height="315" src="https://www.youtube.com/embed/IItprhYiZcQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 結論

ORANGE-4 Ver 1.09 では、RUN キーによるプログラムの実行開始時や、RST キーによるプログラムの実行停止時には、スタックポインタは初期化されない。
プッシュ・ポップおよびサブルーチンの呼び出し・復帰の対応がとれた正しいプログラムであっても、プログラムの実行中にはスタックを使い、スタックポインタの値を初期値から動かすことがある。
そして、RST キーを用いると、そのようなスタックを使用中のタイミングでプログラムの実行を停止することができ、さらにこのときプログラムカウンタは 00 に戻る。
すると、プログラムを次に実行するときは、また最初、すなわちスタックを使っていない状態から実行が開始される。
すると、スタックポインタが初期値より進んだ状態になっているにもかかわらず、プログラムはスタックポインタが初期値になっていると仮定した状態から実行するため、スタックポインタが想定より多く進み、スタックオーバーフローによるプログラムの破壊に繋がる。
プログラムの実行と停止を繰り返すことで、スタックポインタの進みが蓄積され、よりスタックオーバーフローを起こしやすくなる。

モニターを用いればスタックポインタの値を確認したり、`0` コマンドでリセットしたりすることが可能であるが、モニターを用いずにスタックポインタの値を確認したり、ハードウェアリセット以外でリセットしたりする簡単な方法は今のところわからない。
簡単でない方法としては、メモリにパターンに沿った値を書き込んでポップし、どの値が読まれるかに基づいてスタックポインタの値を調べることができるかもしれない。