ORANGE-4 Ver 1.09 には、値のプッシュ・ポップや、サブルーチン呼び出し・復帰という、スタックを使う命令がある。
しかし、RSTキーによるプログラムの実行停止時や、RUNキーによるプログラムの実行開始時にはスタックポインタが初期化されないため、スタックへのプッシュとポップが適切に対応している正しいプログラムでも、実行と停止を繰り返すとスタックオーバーフローを起こしてプログラムが破壊されることがある。
プログラム例
以下のプログラムは、関数をネストして呼び出し、7セグメントLEDに表示する値を1ずつ増やしていくものである。
MikeAssembler でアセンブルできる。
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」で固定される
- 書き込んだプログラムが破壊される
という現象が確認できた。
結論
ORANGE-4 Ver 1.09 では、RUN キーによるプログラムの実行開始時や、RST キーによるプログラムの実行停止時には、スタックポインタは初期化されない。
プッシュ・ポップおよびサブルーチンの呼び出し・復帰の対応がとれた正しいプログラムであっても、プログラムの実行中にはスタックを使い、スタックポインタの値を初期値から動かすことがある。
そして、RST キーを用いると、そのようなスタックを使用中のタイミングでプログラムの実行を停止することができ、さらにこのときプログラムカウンタは 00 に戻る。
すると、プログラムを次に実行するときは、また最初、すなわちスタックを使っていない状態から実行が開始される。
すると、スタックポインタが初期値より進んだ状態になっているにもかかわらず、プログラムはスタックポインタが初期値になっていると仮定した状態から実行するため、スタックポインタが想定より多く進み、スタックオーバーフローによるプログラムの破壊に繋がる。
プログラムの実行と停止を繰り返すことで、スタックポインタの進みが蓄積され、よりスタックオーバーフローを起こしやすくなる。
モニターを用いればスタックポインタの値を確認したり、0
コマンドでリセットしたりすることが可能であるが、モニターを用いずにスタックポインタの値を確認したり、ハードウェアリセット以外でリセットしたりする簡単な方法は今のところわからない。
簡単でない方法としては、メモリにパターンに沿った値を書き込んでポップし、どの値が読まれるかに基づいてスタックポインタの値を調べることができるかもしれない。