
いよいよファーストバイト・オペコードマップの塗りつぶしも佳境。今回はソフトウエア割り込み命令、INTとINTOを練習してみます。といってINT命令の方はDOSのシステムコールで毎度お馴染み。いつも割り込みを操作するときは緊張して夜しか眠れないのだけれども、今回はお気楽。エミュレータ(QEMU)上での作業だし。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
ソフトウエア割り込み命令
8086/8088レベルの16ビットのx86の場合、該当するのは以下の3命令です。
-
- INT 3
- INT xx(xxには0から255が入る)
- INTO
毎度お世話になっているのは、2番目の形式のINT 21h(Intel式表記、C言語風なら 0x21)です。16進0x21番のソフトウエア割り込みにてMS-DOS(今回はその上位互換のFreeDOSですが)のシステムコールを呼び出してます。
一方、最初のINT 3 は明示的に使ってはいないですが、これまたお世話になってます。デバッガがブレークポイントを置くときに使うコードね(当然書き替え可能な主記憶上のオブジェクトに対して。)なんとなれば2番目の INT xxでも3番の割り込みは発生できるけれども2番目の形式だと2バイト必要です。一方、INT3 だけは1バイト命令です。まあ、あんまり自分で書くようなコードでもないっす。
そして3番目のINTOは、計算結果のチェック用。計算途中でオーバーフローが発生する可能性がある場合にINTO命令を通過するように、各所にINTOを散りばめておけば、オーバーフロー発生時には割り込みで教えてくれます。オーバーフローがなければ割り込み発生せず通過です。
その他の割り込み関係命令
割り込みを練習するために、以下の命令どもも必要です。
-
- CLI
- STI
- IRET
CLIは割り込み許可フラグをおろして割り込み禁止とするもの。それに対してSTIは許可する命令です。ホンモノのハードウエアであれば、各種のハードウエアが頻々と割り込んでくる筈なので、割り込み禁止区間は最小になるように制御せんとならないです。しかし今回はお気楽。暴走できるもんならやってみろ、と。まあ、ベクタ・テーブルを操作している最中のみ形だけ割り込み禁止にしておくね。どうせ、割り込むヒトはいないはず。
またIRETは、割り込みハンドラの末尾に置かねばならない命令です。割り込み発生時には、ハードウエア割り込みでもソフトウエア割り込みでもFlags、CS、IPの3つがスタックに退避されて割り込みハンドラに制御が移ります。IRETはFlags、CS、IPを復帰させて、割り込まれた命令の次に制御を移す命令です。
FreeDOSの割り込みベクタテーブルのチョイ変
今回は、割り込みを練習するだけに、割り込みベクタテーブルに「自前の」ハンドラをセットせにゃなりません。MS-DOS(そしてFreeDOS)のシステムコールの何番目かに割り込みハンドラを設定するためのシステムコールがあったハズ。でも、お気楽な今回は、自前でベクタテーブルを直接書き替えてお茶を濁してます。
とは言え元の状態を確認しておかないとなりませぬ。
まず、インテル予約の割り込み0番から1Fh番までの割り込みテーブルのダンプが以下に。
上記のダンプの中、0000:0010h に始まる赤枠の部分が、INTO割り込み時のトビ先アドレスです。0から初めて4番目の割り込みです。そのトビ先を逆アセンブルしたものが以下に。
デフォルトではIRETで即戻るようになってました。よって赤枠部分をINTOがオーバーフローを検出したときに飛ぶ自前のハンドラのアドレスに書き換えてしまえばよい筈。
一方、割り込み20hから3Fhまでの割り込みテーブルのダンプが以下に。
割り込み 20h から 2Fh までは皆大好きなMS-DOS(そしてFreeDOS)が使っている割り込み番号です(一部使ってないところもあるみたいですが。)
30h番以降も誰かが使っている感じだけれども34h以降は使ってないのかな。今回は、上記の赤枠部分、38hの割り込みを自前ハンドラで使うことにいたします。念のため、デフォルトのトビ先を逆アセンブルしたものが以下に。
使われてないようだね。
今回実験のプログラム
強力なx86用アセンブラNASM用のソースです。以下の初期コードのままだと、INT 38hは動作しますが、INTOはOVでないので割り込み発生せずスルーです。後でデバッガで定数を書き替えて強制OVにしてINTOを効かせる予定。
segment code ..start: mov ax, stack mov ss, ax mov sp, stacktop setvec: mov ax, 0 mov ds, ax mov bx, 0x10 mov si, 0x38 * 4 cli mov word [bx], ovint mov word [bx+2], handler mov word [si], i38h mov word [si+2], handler sti test: mov ax, data mov ds, ax int 0x38 mov al, 121 mov ah, 136 add al, ah into fin: mov ax, 0x4c00 int 0x21 resb 2048 segment handler align=16 ovint: push bx mov bx, ovcnt inc word [bx] pop bx iret i38h: push bx mov bx, icnt inc word [bx] pop bx iret segment data align=16 resb 1024 * 63 ovcnt: dw 0 icnt: dw 0 segment stack class=STACK align=16 resb 2048 stacktop: dw 1024 dup (0) stackend:
アセンブルして実行
MS-DOS互換で機能強化されてるフリーなFreeDOS上、以下のステップで上記のアセンブラソース into.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)
nasm -f obj -l into.lst into.asm wlink name into.exe format dos file into.obj debug into.exe
まず、いつもの通り、オブジェクトコードの逆アセンブルからです。最初は本体部分。
赤枠つけた INT 38hは無条件に動作しますが、緑の矢印の値だとオーバーフローにならないので緑の枠のINTOで割り込み発生せず下に落ちます。
どちらのハンドラも呼ばれたら自分の「カウンタ」を1インクリメントして戻るだけです。
赤枠がINTOの発生回数をカウントするもの、緑枠がINT 38hの回数を数えるものです。初期値はオブジェクトコードの中で静的にセットしているので、オブジクトの先頭から再実行されると前の値をどんどんインクリメントしていく形です。
赤枠のINTOの割り込みは呼び出されず、緑枠のINT 38hは1回(メモリの順番はリトルエンディアンですぞ)だけ呼び出されとります。
こんどはオーバーフローするようにコードを書き替えて再実行した場合。
計算する数値を書き替えて符号付バイト整数をはみ出すようにしてみたらOVとなってます。
赤に1回が入ってます。なお緑枠が4回になっているのは、さらに2回ばかりOVにならないケースを走らせてしまっているため。お間抜け。
まあ、エミュレータは気楽。緊張感ないのう。