はじめに
ゲームボーイアドバンス(以下「GBA」)のプログラミングにおいて、システムROM(BIOSとも呼ばれる)の機能(例:垂直同期を待機するVBlankIntrWaitなど)を使うことは多々ありますが、それがどのような仕組みで呼び出されているかを記述した記事となります。
動作原理
ユーザーコード(GBAソフト開発者側)からの呼び出し方法
システムROMの機能を利用する流れは以下のようになります。
- r0~r3に目的の機能に応じた値(機能によって必要なレジスタ数が異なる)を書き込む
-
オペランドに目的の機能番号を指定した
SWI命令を実行する
この時、SWIのオペランドに指定する値はCPUのモードによって異なりますモード オペランド ARMモード (機能番号) << 16 Thumbモード (機能番号) - システムROMの機能は必要に応じて戻り値をr0、r1およびr3に出力するので、必要に応じてそれらの値を回収する
SWI命令はどのようにシステムROMの機能を呼び出すのか
SWI命令って?
SWIはアセンブラの命令であるため、GBA独自の機能ではなくCPU由来の機能になります。
GBAのCPUであるARM7TDMIのデータシートによると、
- ARMモードの場合は表1.2にSoftware interrupt(ソフトウェア割り込み)として
SWI 24bit_imm - Thumbモードの場合は表1.7にSoftware Interrupt(ソフトウェア割り込み)として
SWI 8bit_imm
と記載があります。
SWI命令が実行されると、その次の命令のアドレスがlr(リンクレジスタ)に書き込まれ、ARMモードに切り替わり、ソフトウェア割り込みが発生します1。
ソフトウェア割り込みって?
ソフトウェア割り込みが発生すると、スーパーバイザーモードに移行し、0x00000008番地(割り込みベクタ内)の命令を実行します。
割り込みベクタは割り込みの種類ごとに4バイト(ARM命令1ワードぶん)しか幅がないため、一般的にはここにそれぞれの処理へのB(無条件ジャンプ)命令が格納されていることが一般的かと思われます。
GBAでは、そこに0x00000140番地へのB命令が置かれています。
GBAのソフトウェア割り込み処理
0x00000140~0x0000018b番地には、CPUの演算フラグをクリアし、ソフトウェア割り込みに応じたシステムROMの機能を呼び出し、割り込み状態から復帰する処理が記述されています。
そのうち重要な処理は以下の4命令です。
-
lr - 2番地の値を1バイトぶんロードする(0x0144) - システムROM機能へのポインタテーブルの先頭アドレス(
0x000001c8)を取得する(0x0148) - ポインタテーブルから
[1.を2ビット左シフトした値]のオフセットの位置にある4バイトの値をロードする(0x014c) - 3.で取得した値を引数に
BX命令を実行する(ここでそれぞれの機能に飛ぶ)(0x016c)
1.の処理で読み込まれる値は、復帰後のアドレスの2バイト前で、ソフトウェア割り込みを発生させたSWI命令の一部です。
SWI命令の機械語は以下のようになっています。
| モード | 2進表記 | 16進表記 |
|---|---|---|
| ARM |
wwww11112 xxxxxxxx yyyyyyyy zzzzzzzz3
|
wFxxyyzz23
|
| Thumb |
11011111 xxxxxxxx3
|
DFxx3
|
上の表で言う「16進表記」の上位3~4桁目(xxの部分)が取得されています。なのでARMモードの機能番号の指定は16ビット左シフトする必要があったんですね。
さらに1バイトぶんなので、機能番号の範囲は0~255になります。
BX命令はCPUモードを切り替えて無条件ジャンプを行う命令です。
オペランドは1つで、アドレス値を格納したレジスタを指定します。
移行するモードはアドレス値の最下位ビットによって決定され、0であればARMモード、1であればThumbモードに移行します。
このため、実際にジャンプするアドレスは最下位ビットを無視した値となります。
(例:r0が0x01234567である場合にbx r0が実行されると、Thumbモードに移行して0x01234566番地にジャンプする)
GBAプログラミングの資料等で「範囲外の機能番号を指定するとクラッシュする」と書かれているのは、機能へのポインタの取得時に機能番号の範囲の判定をしていない結果、ポインタテーブルの先にある無関係な4バイト値をロードしてジャンプしてしまうことが原因です(ポインタテーブルの後に続く値のうち、大半はGBAのメモリ空間の外を示す数値になっています)。
GBAではポインタテーブルに43個の値が存在するため、0x00 (0)~0x2A (42)が有効な機能番号となっています。
まとめ
GBAのシステムROMの機能はおおまかに、
- 【ユーザーコード】 必要に応じて
r0~r3に値を書き込む - 【ユーザーコード】 SWI命令を実行する
-
【CPUの割り込み処理】
0x00000008番地にジャンプする -
【システムROM】
0x00000140番地にジャンプする - 【システムROM】 SWI命令のオペランドの上位8ビットを取得する
-
【システムROM】 ポインタテーブルの
[4.で取得した値]番の4バイト値を取得する -
【システムROM】
[5.で取得した値]番地にジャンプする - 【システムROM】 ソフトウェア割り込み状態から復帰する
という流れで実行され、機能によってはr0、r1およびr3に結果などの値が出力されます。
余談
GBAの起動シーケンス(効果音とともにGAME BOYの文字が手前から奥に向かって移動し、反射エフェクトがかかってホワイトアウトする等の処理)もシステムROMの機能を呼び出していますが、SWI命令ではなく、BL(次の命令のアドレスをlrに書き込んでジャンプ)命令等の直接的なジャンプ命令で呼び出されています。
そのため、ソフトウェア割り込みを検知することでシステム機能の呼び出しを検知する機能を持つエミュレーター等では起動シーケンスのシステム機能の呼び出しを検知できません。