乗っ取りソーサ○アン

(1997/4/7)
(更新:1997/4/11)

タイトルにピンと来た人は通です。 引本さんらはどうしているかなぁ。
で、これからお話することは元ネタとは全然関係なくて(げし!)、ビーバーが「割り込み乗っ取り」というところのものについてのお話です。

MSXにおいて、割り込み発生時に何かをさせたいとき、普通、H.KEYI(FD9Ah 番地)や H.TIMI(FD9Fh 番地)といったフックを書き換え、そこからユーザが用意したプログラムにジャンプさせるようにします。

※フックの書き換え方が分からない人は、M-FAN 1994年 6,7 月号「おもちゃのマシン語」や「MSX2テクニカルハンドブック」(アスキー出版局)の「割り込み使用法」等を参考にするといいでしょう。
DOS上でフックの書き換えを行う場合は、「スロットに御用心」も参考にしてね。 ちなみに H.TIMI は「タイマ割り込み」というものが発生したときにコールされるフックで、60分の1秒毎にコールされ、BGM演奏なんかに使えます。 一方のH.KEYIは、タイマ割り込みを含むすべての割り込み(走査線割り込みなど)発生時にコールされます。

「MSX2テクニカルハンドブック」によると、MSXでは割り込みが発生すると、まず38h番地がコールされることになっています。 そこから一定の処理が行われてH.KEYIがコールされます。 タイマ割り込みの場合は、その後更にH.TIMIもコールされます。
ところで、H.KEYIやH.TIMIコールの前後にも、キーボードスキャンやスプライト衝突判定などといった処理も行われます。 で、こうした処理が余計で、高速化の妨げになると感じる人もいるわけです。

そこで彼らは、極限的(?)な高速化テクニックとして、ビーバーが「割り込み乗っ取り」というところのテクニックを用いるのです。 これは、H.KEYI、H.TIMIのかわりに、おおもとの38h番地(割り込み発生時に最初にコールされるアドレス)を書き換えてしまい(JP命令などを書く)、ユーザが用意した割り込みルーチンに、直接飛ぶようにするというものです。 割り込み処理の入口である38h番地を「乗っ取る」わけです。 CAPSキーやかなキーを押してもランプが点灯しないプログラムは大抵、「割り込み乗っ取り」を行っていると思われます。

普通は「割り込み乗っ取り」にまで手を染める必要はないと思いますが、どうしてもやりたいというのならば、以下の特徴に注意しながら挑戦してみましょう。 ちなみにビーバーは「割り込み乗っ取り」はやったことがありません(こんな事を書いていいのだろうか)。

(1)規約違反ではないらしい
(2)DOS環境用。 BASIC環境ではやらない方がよい(というか出来ないはず)
(3)RAM 32KB以下のMSXでは不可
(4)汎用性に欠ける
(5)めんどくさい
(6)RETで割り込み処理を終了し、通常処理に戻る
(7)(6)の前にEI(割り込み許可)すること
(8)割り込み処理前後でレジスタが保存されるようにすること
(9)ディスクアクセス時は38h番地を元に戻す
(A)タイマ割り込みを利用する場合、タイマ割り込みかどうかを判断する必要あり
(B)一部のシステムワークエリアが意味を持たなくなる
(C)スプライト衝突判定やキーボードスキャンは行われなくなる
(D)VDPのステータスレジスタの扱いが容易になる(かも)

※以下、「割り込み乗っ取り」により、38h番地からの新たなジャンプ先に用意する、新しい割り込み処理ルーチンのことを、「ユーザの割り込み処理ルーチン」と表現することにします。

(1)については、H.KEYIやH.TIMIというものが用意されているのに、根元から乗っ取ってもいいんかいなという心配があったのですが、別に乗っ取ってはいけないということも言ってないし、市販のソフトでも乗っ取りをやっているものがあったみたいだから、注意して使えば問題はないようです。

(2)について。 「割り込み乗っ取り」を実現するためには、38h番地、つまりページ0が「常に」RAMでなければなりません(38h番地を書き換えることができ、かつ、いつ割り込みが発生してもいいようにするため)。 これは基本的にDOS環境でしか実現できません。 BASIC環境でページ0を「常に」RAMにすることはできません(インタースロットリード/ライトなど、「一時的に」RAMにするのは可)。 なぜなら、BIOSのENASLT(スロット切替え)では、ページ0のスロット(ROMになっていますね)を切り替えることができないからです。
BASIC環境で「割り込み乗っ取り」を実現するには、この他にもいろいろ問題や不利な点があります。 BASIC環境ではおとなしくH.KEYI、H.TIMIを使った方がいいでしょう。
※「スロット」や「ページ」の意味や操作法については、「MSX2テクニカルハンドブック」や M-FAN 1994年12月号を参考にするといいでしょう。 あと、「スロットに御用心」もよろしく。

(3)は(2)で述べたとおり、「割り込み乗っ取り」を実現するためにはページ0がRAMでなければならないわけですが、RAM 32KB以下のMSXではそもそもページ0にRAMは存在しないので、どのみち乗っ取りは不可能というわけです。
ちなみにMSX2以上の機種では、RAM容量は全て64KB以上となっているので、その点は心配ないです。

(4)について。 「割り込み乗っ取り」をすると、H.KEYIやH.TIMIがコールされなくなり、それらを利用したアプリケーションとの併用はできなくなります。 他のアプリと併用しない場合、例えばゲームとか、そういう時くらいしか利用価値はないでしょう。

(5)について。 ユーザの割り込み処理ルーチンには、本来はBIOSがやってくれていた一連の割り込み処理(レジスタ退避など)を用意してやらなければなりません。 具体的には後の(6)(7)(8)(9)(A)(B)がそれに当たります。 ただし、「割り込み乗っ取り」においては、これらの中でもある程度コンパクト化できる場合があるので、そういう意味では必要な処理に絞って高速化をはかれるということが言えなくもありません。

(6)について。 割り込みが発生すると、その地点での通常処理のアドレスから38h番地がコールされる仕組みになっています。 したがって、ユーザの割り込み処理ルーチンからは、RETで通常処理に戻ることができるというわけです。

(7)について。 割り込み発生により38h番地がコールされた地点では、割り込み禁止(DI)状態になっています。 「割り込み乗っ取り」を行う場合、ユーザの割り込み処理ルーチンにおいては、その終わりで必ず割り込みを許可(EI)しましょう。 ちなみにBIOS(乗っ取る前の38h番地から始まるもの)の場合、H.KEYIやH.TIMIを含んだ一通りの処理が終わると、BIOS内部で割り込み許可した後、通常処理に戻るようになっています。

(8)について。 割り込み処理前後では必ずレジスタが保存されているようにしましょう。 どのレジスタを使うかわからない場合は、A,F,B,C,D,E,H,L,IX,IY の各レジスタを、裏レジスタも含めて全て保存しましょう。 基本的に、38h番地からジャンプしてすぐに、保存が必要なレジスタをPUSH,PUSH,PUSH......し、通常処理に戻る(RET)前にPOP,POP,POP......して復元するという感じでしょうか。
ユーザの割り込み処理ルーチン内で使われるレジスタが限られている場合は、そのレジスタだけ保存してやればいいです(「割り込み乗っ取り」のメリット)。

(9)について。 実はこれが一番厄介なのかも。
ディスクアクセス時は38h番地を元に戻さなくてはなりません(だから乗っ取り前に、38h番地(からの3バイト)の内容をどこかに保存しておきましょう)。 こうしないと一部機種で不具合が発生します。
あと、FDDが回り続けるので、FDC-ROMの401FHを呼んで回転を止めましょう。 FDC-ROMの4029Hを呼ぶと全てのドライブの回転が止まりますが、ハードディスクがつながっている場合は危険なので呼ばないようにしましょう。
FDC-ROMのスロットアドレスの調べ方ですが、DOS1の場合はDRVTBL(FB21,8)に各FDC-ROMの管理しているドライブ数とそのスロットアドレスが入っているので、これとファンクションコールの18h番地を組み合わせれば、特定のドライブのスロットアドレスを得ることが出来ます。 DOS2の場合はファンクションコールの67h番地をコールすれば、Bレジスタにスロットアドレスが返ってきます。

他にもDOS1システムを使い、38h番地を戻さずに、FDDアクセス時だけ標準モードにしてやるという方法もあるようです。 こちらの方が楽かも。

(A)について。 発生する割り込みは1種類ではありません。 このうちタイマ割り込みかどうかの判定は、通常はBIOSが内部で判断してH.TIMIがコールされるようになっていますが、「割り込み乗っ取り」をする場合は、ユーザの割り込み処理ルーチンに判定部分を用意してやらなければなりません。
発生した割り込みがタイマ割り込みかどうかを判断する方法ですが、VDPステータスレジスタの0番のビット7(最上位ビット)を活用します。 タイマ割り込みが「あったとき」、ここは1になっています。 M-FAN 1994年10月号「おもちゃのマシン語」においては、「垂直同期割り込み(タイマ割り込み)が『なかったとき』1になる」とありましたが、これは「あったとき」の間違いでしょう。 なお、一度ステータスレジスタの0番を読むと、このビットは0に戻ります。

(B)について。 通常はタイマ割り込みが発生すると、例えばキーボードスキャンが行われて、その情報がシステムワークエリアに書き込まれるのですが、「割り込み乗っ取り」をするとその処理が行われなくなるので、システムワークエリアのその部分は更新されなくなるというわけです。

(C)について。 (B)に関連して、システムワークエリアのキーボードスキャン情報はあてにできなくなるというわけです。

(D)について。 VDPのステータスレジスタの内容を読み出す際、通常は読み出し後、ステータスレジスタの番号を0に戻さなくてはなりません。 これは、通常の割り込み処理は、ステータスレジスタの番号が0になっていることを期待して動くようになっているからです。
これに対して、「割り込み乗っ取り」を行う場合は、ユーザの割り込み処理ルーチンでステータスレジスタの番号をセットする(0であることを期待しない)ようにすれば、ステータスレジスタの番号を0に戻す必要が無くなるかもしれません。
でも、そうすると、例えば走査線割り込みのような、速度的に危急な割り込みの場合、ステータスレジスタのセットでタイムロスになる可能性が出てきますね。 やっぱり、今まで通り、通常ルーチン側でステータスレジスタの番号を0に戻す(ユーザの割り込みルーチンではステータスレジスタの番号は0であることを期待する)ようにした方がいいのかなぁ。
走査線割り込みを活用する場合、メイン側ではステータスレジスタの番号を、使用時以外は2番に保つようにすれば(今まで0番に戻していたところを2番に戻すようにする)、ユーザの割り込み処理ルーチン側で走査線を扱うのがスムーズになります(ユーザの割り込み処理ルーチン内では、ステータスレジスタの番号が2番になっていることを期待する)。

こんなところでしょうか。

「乗っ取り」といえばスクウェアがアスキーを乗っ取るといううわさを聞きましたが、もし本当だとしたら「MSXはスクウェアの商標です」になるんだろうかと余計な心配をしている今日このごろなのでした(ぼこぼこぼこぼこ…!)。


MSXの適当手帳 MSX駅 西山駅