IchigoJam BASIC 1.0.0 (VER()=10017) のUSR
関数にはバグがあり、
# 700
以外のアドレスを指定するとまともに動かない…と考えられてきました。
#IchigoJam
— みけCAT (@mikecat_mixc) March 7, 2019
0x700番地にRETを書き込んで実行→渡したパラメータがそのまま返る
0x708番地にRETを書き込んで実行→渡したパラメータになぜか8192が足されて返る
なんでやねん💢💢💢💢💢💢💢 pic.twitter.com/xkJfg3k2e3
しかし、先日の記事 IchigoJam R に関する諸々 - Qiita を書いた際に行ったレジスタの調査に伴い、
ある仮説を思いつきました。
この時得られた1.0.0でのマシン語を呼び出したときのレジスタの値がこちら。
ARG0 = #00001234
ARG1 = #00001234
ARG2 = #10000188
ARG3 = #10000187
PC = #10000188
SP = #10000DF0
(ARG0
~ARG3
は、レジスタR0
~R3
に対応します)
ARG3 (R3)
がPC
の値-1になっているのが気になります。
これを呼び出しに用いているのかはわかりませんが、このことから、
1.0.0では指定されたアドレスから2引いた場所を呼び出すのではないか、という仮説を思いつきました。
検証してみましょう。
呼び出される位置を調べるコード
# 700
から# 800
までの領域を命令R0 += 2
で埋め、# 800
にRET
を置くことで、
どこを呼び出したかによって返る値が変わり、どこが呼び出されたかがわかる仕組みです。
10 ' マシンゴ ヨビダシ イチ テスト
20 FOR I=#700 TO #800 STEP 2
30 POKE I,#02,#30
40 NEXT
50 POKE #800,#70,#47
60 FOR I=#700 TO #800 STEP 2
70 ?HEX$(#800-USR(I,0),3);
80 IF (I>>1)%8<7 ?" "; ELSE ?""
90 NEXT
実行結果は、1.4.1では
700 702 704 706 708 70A 70C 70E
710 712 714 716 718 71A 71C 71E
720 722 724 726 728 72A 72C 72E
730 732 734 736 738 73A 73C 73E
740 742 744 746 748 74A 74C 74E
750 752 754 756 758 75A 75C 75E
760 762 764 766 768 76A 76C 76E
770 772 774 776 778 77A 77C 77E
780 782 784 786 788 78A 78C 78E
790 792 794 796 798 79A 79C 79E
7A0 7A2 7A4 7A6 7A8 7AA 7AC 7AE
7B0 7B2 7B4 7B6 7B8 7BA 7BC 7BE
7C0 7C2 7C4 7C6 7C8 7CA 7CC 7CE
7D0 7D2 7D4 7D6 7D8 7DA 7DC 7DE
7E0 7E2 7E4 7E6 7E8 7EA 7EC 7EE
7F0 7F2 7F4 7F6 7F8 7FA 7FC 7FE
800 OK
ときれいに並んでいるのに対し、1.0.0では
700 700 702 704 706 708 70A 70C
70E 710 712 714 716 718 71A 71C
71E 720 722 724 726 728 72A 72C
72E 730 732 734 736 738 73A 73C
73E 740 742 744 746 748 74A 74C
74E 750 752 754 756 758 75A 75C
75E 760 762 764 766 768 76A 76C
76E 770 772 774 776 778 77A 77C
77E 780 782 784 786 788 78A 78C
78E 790 792 794 796 798 79A 79C
79E 7A0 7A2 7A4 7A6 7A8 7AA 7AC
7AE 7B0 7B2 7B4 7B6 7B8 7BA 7BC
7BE 7C0 7C2 7C4 7C6 7C8 7CA 7CC
7CE 7D0 7D2 7D4 7D6 7D8 7DA 7DC
7DE 7E0 7E2 7E4 7E6 7E8 7EA 7EC
7EE 7F0 7F2 7F4 7F6 7F8 7FA 7FC
800 OK
となり、少なくとも# 702
~# 7FE
の範囲においては2バイトずつきれいにずれていることがわかります。
しかし、# 800
については、予想に反してずれませんでした。
アドレスが# 100
の倍数の時はずれないのでしょうか?
もう少し調査する範囲を広げてみます。
呼び出される位置を調べるコード (# 880まで)
先程のコードで# 800
としていた部分を# 880
とし、もう少し先まで調べてみます。
(このような合わせて変える値は直接書かず、変数に入れるべきですよね。ごめんなさい)
10 ' マシンゴ ヨビダシ イチ テスト 2
20 FOR I=#700 TO #880 STEP 2
30 POKE I,#02,#30
40 NEXT
50 POKE #880,#70,#47
60 FOR I=#700 TO #880 STEP 2
70 ?HEX$(#880-USR(I,0),3);
80 IF (I>>1)%8<7 ?" "; ELSE ?""
90 NEXT
1.4.1での実行結果は、予想通りです。
700 702 704 706 708 70A 70C 70E
710 712 714 716 718 71A 71C 71E
720 722 724 726 728 72A 72C 72E
730 732 734 736 738 73A 73C 73E
740 742 744 746 748 74A 74C 74E
750 752 754 756 758 75A 75C 75E
760 762 764 766 768 76A 76C 76E
770 772 774 776 778 77A 77C 77E
780 782 784 786 788 78A 78C 78E
790 792 794 796 798 79A 79C 79E
7A0 7A2 7A4 7A6 7A8 7AA 7AC 7AE
7B0 7B2 7B4 7B6 7B8 7BA 7BC 7BE
7C0 7C2 7C4 7C6 7C8 7CA 7CC 7CE
7D0 7D2 7D4 7D6 7D8 7DA 7DC 7DE
7E0 7E2 7E4 7E6 7E8 7EA 7EC 7EE
7F0 7F2 7F4 7F6 7F8 7FA 7FC 7FE
800 802 804 806 808 80A 80C 80E
810 812 814 816 818 81A 81C 81E
820 822 824 826 828 82A 82C 82E
830 832 834 836 838 83A 83C 83E
840 842 844 846 848 84A 84C 84E
850 852 854 856 858 85A 85C 85E
860 862 864 866 868 86A 86C 86E
870 872 874 876 878 87A 87C 87E
880 OK
1.0.0での実行結果は、以下のようになりました。
700 700 702 704 706 708 70A 70C
70E 710 712 714 716 718 71A 71C
71E 720 722 724 726 728 72A 72C
72E 730 732 734 736 738 73A 73C
73E 740 742 744 746 748 74A 74C
74E 750 752 754 756 758 75A 75C
75E 760 762 764 766 768 76A 76C
76E 770 772 774 776 778 77A 77C
77E 780 782 784 786 788 78A 78C
78E 790 792 794 796 798 79A 79C
79E 7A0 7A2 7A4 7A6 7A8 7AA 7AC
7AE 7B0 7B2 7B4 7B6 7B8 7BA 7BC
7BE 7C0 7C2 7C4 7C6 7C8 7CA 7CC
7CE 7D0 7D2 7D4 7D6 7D8 7DA 7DC
7DE 7E0 7E2 7E4 7E6 7E8 7EA 7EC
7EE 7F0 7F2 7F4 7F6 7F8 7FA 7FC
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 880 880 880 880 880 880 880
880 OK
USR
に# 800
以降を指定した時の結果が、全て880
(すなわち、返り値は0)となってしまいました。
これは、# 800
以降を指定した時はマシン語の実行を拒否して0を返す、と解釈するのが妥当でしょう。
周辺のメモリの観察
1.0.0では「# 800
以降のマシン語の呼び出しは拒否されていそう」ということはわかりましたが、
これだけでは# 700
の実行がまともに動く理由が確定できません。
「アドレスが# 700
の時は呼び出し位置がずれない」という可能性も考えられますし、
「呼び出し位置はずれるが、# 700
の前はNOPなどの影響が出ない命令なので、まともに動く」
という可能性も考えられます。
そこで、# 700
の前後のメモリの値を見てみます。
10 ' メモリ シュトク
20 POKE#700,#3F,#A2,#13,#46,#80,#3B,#90,#3B
30 POKE#708,#20,#21,#18,#78,#10,#70,#01,#33
40 POKE#710,#01,#32,#01,#39,#F9,#D1,#70,#47
50 X=USR(#700,0)
60 FOR I=0 TO #1F
70 ?HEX$(PEEK(#800+I),2);
80 IF I%8<7 ?" "; ELSE ?""
90 NEXT
マシン語部分のソースコード
R2 = @ARRAY
R3 = R2
R3 -= #80
R3 -= #90
R1 = #20
@LOOP
R0 = [R3 + 0]
[R2 + 0] = R0
R3 += 1
R2 += 1
R1 -= 1
IF !0 GOTO @LOOP
RET
ORGR #100
@ARRAY
1.0.0での実行結果は、以下のようになりました。
0A 00 00 00 00 00 00 00
00 00 06 00 00 00 00 00
3F A2 13 46 80 3B 90 3B
20 21 18 78 10 70 01 33
参考として、1.4.1での実行結果は、以下のようになりました。
02 0E 00 10 00 00 00 00
01 00 00 00 29 52 00 00
3F A2 13 46 80 3B 90 3B
20 21 18 78 10 70 01 33
これは、# 700
の前後16バイトずつを出力しています。
予想通り、# 700
の手前にはNOP命令を表すデータが入っていることがわかりました。
しかし、このNOPを実行しているかどうかはわかりません。
そこで、ここに他の命令を入れ、実行されるかをみてみましょう。
# 700の前の命令の書き換え
# 700
の前をRET命令に書き換えるコードを# 710
に配置し、その手前にNOPを配置しました。
また、# 700
には、引数に1を足して返すコードを配置しました。
このコードを実行し、# 700
の前の命令を書き換える前後での# 700
を呼び出した時の挙動が変わるかをみます。
10 ' 1.0.0 USR(#700,0) テスト
20 POKE#700,#01,#30,#70,#47
30 POKE#70E,#00,#00,#03,#A0,#22,#38,#70,#21
40 POKE#716,#01,#70,#47,#21,#41,#70,#70,#47
50 ?"BEFORE : ";USR(#700,0)
60 X=USR(#710,0)
70 ?"AFTER : ";USR(#700,0)
実行結果は、以下のようになりました。
IchigoJam BASIC 1.0.0 by jig.jp
OK
LOAD 170
Loaded 218byte
OK
RUN
BEFORE : 1
AFTER : 0
OK
RUN
BEFORE : 0
AFTER : 0
OK
電源投入後最初の実行では、# 700
の手前を書き換えたことによって、引数に1が足されなくなりました。
このことから、アドレスの指定が# 700
であっても、2バイト前にずれることが読み取れます。
その次の実行では、最初から引数に1が足されていません。
最初の実行で書き換えた効果が残っているようです。
ちなみに
冒頭のツイートで0x708番地を指定して実行した時、2バイト前の0x706番地を実行しているはずです。
ここには何があるのでしょうか?
IchigoJam BASIC 1.0.0 by jig.jp
OK
?HEX$(PEEK(#707),2);HEX$(PEEK(#706),2)
1838
これはR0 = R7 + R0
という命令に相当します。
R7の値がたまたま8192だったので、8192が足されたと考えられます。
結論
IchigoJam BASIC 1.0.0のUSR関数は、指定したアドレス-2を呼び出す上、
# 800
以降の呼び出しを拒否するようであることがわかりました。
これまでは「# 700
しかまともに使えない」と考えていましたが、
-
# 800
未満のアドレスを指定する - 指定した場所の1命令前にNOPを入れる
ということに注意すれば、互換性を保って他のアドレスも使えそうであることがわかりました。
環境によって実行を指示した命令の隣の命令も実行されたりされなかったりするというのは、
遅延分岐スロットみたいですね。
※IchigoJamはjig.jpの登録商標です。