
前回、x86(16bit)範疇にはまだまだ命令があることに気づきました。お尻が長いよ。今回は、8086になく80186/80286で拡張されている命令のその2(遥か昔にENTER/LEAVEはやったので)ということでBOUND命令を練習してみます。使うか使わないかはあなた次第?コンパイラによっては余計なお世話か?
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
BOUND命令
BOUND命令は、配列にアクセスするときにその添え字が配列の範囲の中に納まっているか否かを判定するための命令です。80186以降で「新設」された命令です。ENTER/LEAVE命令同様、古のセマンティック・ギャップ対策のための命令みたいです。
添え字がご指定の範囲内にあれば何も文句を言わずにスルー、しかし、範囲(下限、上限あり)を逸脱していたら5番の例外を発生してくれます。書式は
BOUND レジスタ、メモリ
ってな形です。レジスタに添え字を入れておき、メモリのところに下限値、メモリ+2番地(186、286の場合、386以降はレジスタのビット幅が広がるので+4番地もあり)に上限値を置いておきます。するとBOUND命令で上限、下限が読み出されレジスタ中の値と比較され、万が一下限値より小さかったり、上限値よりも大きかったりすると例外5番発生ということにあいなります。
ちょっと聞くと「いいんじゃね」という感想を持ちますケド、使ってない人(コンパイラ屋さん)も多いじゃないかと思います。その心は以下の2つかと
-
- そもそも添え字チェックなど速度低下をもたらすだけ、そんなものプログラマが自分で責任もって管理しろよ(C、C++や、速度命の処理系の皆さま)
- 安心、安全な処理には添え字チェックは大事だけれども、インテルBOUND命令のやり方には納得いかない(いろいろやり方あるだろ~)
まあ、処理系によりそのスタンスはいろいろでないかと。
仮想86モードの小ネタ
第236回でやってますが、現行の練習環境のFreeDOSは、Ubuntu上のQEMU「エミュレータ」で動作してます(Ubuntu自体がWSL2上で動いているのだが。)具体的に言うとQEMU上でエミュレートされている仮想86(V86モード)で動いてます。本物(リアルモード)の8086とは処理が異なるところがあり。今回、BOUND命令では5番の例外を使います。8086や80186では、メモリの下の方の固定番地にベクトルがおかれていますが、386以降の仮想86モードでは、割り込みは直接ハンドリングは許されておらず、実際には仮想86モニタというプロテクトモードのモニタ・プログラムに実行が移ります。今回環境も、さもリアルモードで動作しているかのように見えますが、実際にはQEMU内の例外ハンドラが活躍してその動作をエミュレートしてます。巧妙だけれどもメンドイ仕組みだよ。
今回実験のプログラム
今回は以下のような処理を行ってみます。
-
- 8086式の割り込みベクトル5番にBOUND命令用の割り込みハンドラをしかける(仮想86モードなので、実際にこのハンドラが直接呼び出されることはない。まず仮想86モニタ内で例外5番が扱われ、その際、仮想86モードのプログラムがそのハンドラを動作させることを「想定」していると判定して、そこに制御を移してくれる。見掛けは目論見通り8086式の動作をする筈。)
- まず「範囲内」の添え字で第1のBOUND命令を通す(下に落ちるだけ。)
- つづいて「範囲外」の添え字で第2のBOUND命令を通す(上記のハンドラに制御が移る)
なお、コマケーことを言うと、割り込みハンドラを書き替えるお作法としては、
-
- まず古いハンドラのアドレスを保存しておく(現プログラムを終了後、他のプログラムで既にメモリ上に在りもしない現プログラムのハンドラを呼び出すのはマズイので、元に戻してから終わるため。)
- アドレスはセグメントとオフセットの2つに分かれているので、書き替え途中で割り込まれないように配慮しつつ書き替える
- 現プログラム完了前に元のアドレス(多分常駐プログラムを指している)に戻しておく
といったお作法が必要かと。しかし、今回は無視。どうせ1回動作させたらおしまい、仮想マシンも閉じてしまうので、後は野となれ山となれ。。。
また、BOUND命令が発生する例外5番は
発生したBOUND命令のアドレスを指している
ようです。何も処理せず戻ると無限ループにハマる筈。通常、例外発生後は、対処のためのルーチン(エラー報告など)に飛ばすのだろうし。でも、今回は何もしてません。ハンドラの途中で「おしまい」としてます。手抜きだよ。
以下は「強力なx86用アセンブラNASM」用のソースです(MSのMASMともインテルASM86とも微妙に異なるけど、まあ分かるっしょ。)
segment code ..start: mov ax, data mov ds, ax mov ax, stack mov ss, ax mov sp, stacktop setupint: xor ax, ax mov cx, ax mov es, ax mov di, 5 * 4 cli mov word es:[di], handler mov word es:[di+2], code sti test: mov ax, 1 bound ax, arraylim mov ax, 111 bound ax, arraylim nop fin: mov ax, 0x4c00 int 0x21 resb 2048 handler: nop inc cx nop ;Break here! segment data align=16 resb 1024 * 63 arraylim: dw 0 dw 100 conut: dw 0 segment stack class=STACK align=16 resb 2048 stacktop: dw 1024 dup (0) stackend:
なお、仮想86モードでは、CLI、STI、IRETなどは直接の実行が許されておらず、例外発生して、仮想86モニタ様へ「裏でお願いする」スタイルの実行となります。まあ、CLI、STIなどしているのは、ほんの気持ちね。
アセンブルして実行
MS-DOS互換で機能強化されているフリーなFreeDOS上、以下のステップで上記のアセンブラソース bound.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)
なお、FreeDOS付属の「debugx」デバッガは、8086式の割り込みベクタテーブルのダンプも可能です。
nasm -f obj -l bound.lst bound.asm wlink name bound.exe format dos file bound.obj debugx bound.exe
黄色枠の部分が「なんちゃって」で手抜きな準備部分です。なお、緑色の矢印のところでブレークかけるようにして走らせてますが、実際には後で出てくるハンドラの中でブレークして観察してますので緑矢印までは到達しません。
ここでは戻り番地の調整をしてIRETとかせず、黄色矢印のところにしかけたブレークポイントで終了させます。
まずは、初期設定(割り込みハンドラの設定)部分まで実行したところ。
5番に、上記の「なんちゃって」ハンドラのアドレスが設定されているのが分かります。何気に debugx 使いやすいっす。
さて、肝心のBOUND命令を実行、デバッガ・コマンドは以下です。念のためブレークポイントは2個設定ね。
g 31 839
結果は以下のようです。黄色のIPの値から、09CB:0839番地でブレーク。予定どおり黄色の矢印のところです。そしてAXを見れは0x6F(10進で111)、2つ目のBOUND命令で例外発生とな。
まあ、目論見通りの動きをしているみたい。ホントか?