ぐだぐだ低レベルプログラミング(209)x86(16bit)、INTOとINT

Joseph Halfmoon

いよいよファーストバイト・オペコードマップの塗りつぶしも佳境。今回はソフトウエア割り込み命令、INTとINTOを練習してみます。といってINT命令の方はDOSのシステムコールで毎度お馴染み。いつも割り込みを操作するときは緊張して夜しか眠れないのだけれども、今回はお気楽。エミュレータ(QEMU)上での作業だし。

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
ソフトウエア割り込み命令

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番までの割り込みテーブルのダンプが以下に。IntelReservedIVECs

上記のダンプの中、0000:0010h に始まる赤枠の部分が、INTO割り込み時のトビ先アドレスです。0から初めて4番目の割り込みです。そのトビ先を逆アセンブルしたものが以下に。F000_FF53

デフォルトではIRETで即戻るようになってました。よって赤枠部分をINTOがオーバーフローを検出したときに飛ぶ自前のハンドラのアドレスに書き換えてしまえばよい筈。

一方、割り込み20hから3Fhまでの割り込みテーブルのダンプが以下に。DOSandUserIVECs

割り込み 20h から 2Fh までは皆大好きなMS-DOS(そしてFreeDOS)が使っている割り込み番号です(一部使ってないところもあるみたいですが。)

30h番以降も誰かが使っている感じだけれども34h以降は使ってないのかな。今回は、上記の赤枠部分、38hの割り込みを自前ハンドラで使うことにいたします。念のため、デフォルトのトビ先を逆アセンブルしたものが以下に。00D9_1285

使われてないようだね。

今回実験のプログラム

強力な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

まず、いつもの通り、オブジェクトコードの逆アセンブルからです。最初は本体部分。unassemINTO

赤枠つけた INT 38hは無条件に動作しますが、緑の矢印の値だとオーバーフローにならないので緑の枠のINTOで割り込み発生せず下に落ちます。

ハンドラ部分の逆アセンブルが以下に。unassemHANDLER

どちらのハンドラも呼ばれたら自分の「カウンタ」を1インクリメントして戻るだけです。

カウンタの初期値が以下に。CNTsInitialValue

赤枠がINTOの発生回数をカウントするもの、緑枠がINT 38hの回数を数えるものです。初期値はオブジェクトコードの中で静的にセットしているので、オブジクトの先頭から再実行されると前の値をどんどんインクリメントしていく形です。

まずはソース通り(オーバーフローなし)のとき。NO_INTO

赤枠のINTOの割り込みは呼び出されず、緑枠のINT 38hは1回(メモリの順番はリトルエンディアンですぞ)だけ呼び出されとります。

こんどはオーバーフローするようにコードを書き替えて再実行した場合。OV

計算する数値を書き替えて符号付バイト整数をはみ出すようにしてみたらOVとなってます。

そのときのカウンタの様子が以下に。INTO

赤に1回が入ってます。なお緑枠が4回になっているのは、さらに2回ばかりOVにならないケースを走らせてしまっているため。お間抜け。

まあ、エミュレータは気楽。緊張感ないのう。

ぐだぐだ低レベルプログラミング(208)x86(16bit)、LODSとSTOS へ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です