前回は、ENTERが作り出すスタックフレームを眺めながらボヤイてましたが演習には至りませんでした。「やりたくないな~」感が充満。Debug.exeは強力なソフトウエアデバッガですが、「ソフトウエアの」デバッガです。自身がブレークかけるためにもスタックを消費。スタック操作の証拠を残すのがメンドイです。仕方ねえ、やるです。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
遥か太古の時代、ICEというものありけり
遥か太古の時代、In Circuit Emulator (ICE)という名のハードウエアデバッガが幅をきかせていた時代がありました。お値段、超高かったです。まさにプロが使うためのツール。ハードウエアのデバッガなので、CPUボードのCPUを抜いて刺したり、CPUの上に重ねたり、専用のソケットを用いたりして接続。まあ最近もJTAGポートを介して接続するエミュレータ(簡易ICE)というものがありますが、そいつらよりかは相当に気張った装置でした。
まあそんな装置を使うと、ユーザーのスタックやらメモリ空間やらにまったく痕跡を残さずに止めたり、プログラムを改竄(デバッグのためですが)したり、CPUレジスタを書き替えたりとやり放題です。
まあ、今回のENTERの動作の観察にそんなものがあったらな、とちょいと思い出した次第。まあ、時代の流れとともに、お高くて速度の遅いハードウエアデバッガは滅していったような気がします。いつの時代の話だ?
今回は、FreeDOS上のソフトウエアデバッガであるDebugを使ってます。こいつはステップ実行などできて便利なツールですが、ステップ実行時のトレース割り込みとかかかれば、スタックを使ってしまいます。勿論、ブレークポイントでも消費。よってスタックフレームを観察して~などという今回の目的ではステップ実行などはやたら使うことができませぬ。そこで、
-
- 途中で止めずに一気に実行する
- ENTERをやる前とやった後でスタックフレームの様子の「メモリ・スナップショット」をプログラム自身で取得して、実行後にその様子を観察する
というスタイルで行うことにいたしました。メンドイのよ。
今回実験のプログラム
強力なx86用アセンブラNASM用のソースです。上記のようにメンドクセー処理をしてます。実体はスタックフレームあたりをブロック転送で書き替えが起こらないエリアに退避しているだけですが。
segment code ..start: mov ax, data mov es, ax mov ax, stack mov ss, ax mov ds, ss mov si, srcarea mov di, dstarea mov cx, 16 mov sp, stacktop mov bp, oldbp test: push 0x3001 ; param 1 rep movsw call far ftarget fin: mov ax, 0x4c00 int 0x21 resb 2048 segment data align=16 dstarea; resb 1024 * 63 segment code2 align=16 resb 1024 * 63 ftarget: enter 4, 2 mov di, dstarea + 32 mov si, sp mov cx, 24 rep movsw leave retf 2 segment stack class=STACK align=16 resb 2048 srcarea: dw 0x4002 dw 0x4001 dw 0 ; area for param 1 stacktop: dw 0x2002 ; old local 2 dw 0x2001 ; old local 1 dw 0x1003 ; old BP 3 dw 0x1002 ; old BP 2 oldbp: dw 0x1001 ; old BP 1 dw 0x7777 ; dummy IP dw 0x8888 ; dummy CS dw 0x9999 ; dummy old PARAM dw 1024 dup (0) stackend:
なおスタックの後ろの方に空虚な領域を残しているのは、デバッガの起動時のあれやこれやでやっぱりスタック使ってしまうみたいなのでその領域のためにエイヤーで確保した領域っす。
アセンブルして実行
MS-DOS互換で機能強化されてるフリーなFreeDOS上、以下のステップで上記のアセンブラソース enter.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)
nasm -f obj enter.asm wlink name enter.exe format dos file enter.obj debug enter.exe
今回のは、アセンブルされた結果のアドレス(16進表記)などを多数頭に入れておかないと何だか分からないので、デバッガ起動直後にディスアセンブルしてます。忘却力の老人はとても覚えられないので、画面のコピーをちまちま見ながら操作してます。メイン部分。
本来あるべき位置でその様子を拝めるのはここだけなので、ここのアドレスが重要っす。黄色の枠があるところをSP(スタックポインタ)が指してます。紫色の枠があるところの直上をBP(ベースポインタ)が指してます。黄色よりもアドレス下側はCALL命令とENTER命令によって値が書き換わりますが、上の方のイメージはデバッガに破壊されない限り残ります。後で黄色枠のところには 0x3001(リトルエンディアンなので 01 30と見えますが)がPUSHされ、その位置(SS:0804番地)は動くことがないので観察の基点となります。
さてENTERをふくむプロシージャをFAR CALLする前とFAR CALL後、ENTERをした時点のスタックの様子(ただしES:0番地以降に転送)が以下に。
上の2行(ES:0からES:1FにMOVSされている)が実行前のスタックの様子です。上記の実行前のスタックの様子と同じであることが分かります。
下の2行(ES:20からES:3FにMOVSされている)がENTER実行後のスタックの様子です。上の2行とはアドレスがズレていますが、黄色の枠(プロシージャへのスタック渡しの引数)の位置は同じです。黄色の枠より下側(上では左側)にCall命令の戻り番地とEnter命令で生成されたスタックフレームが伸びているのが分かると思います。上のアドレスから
-
- 赤枠、戻り番地のCS:IP
- 灰枠、呼び出し側プログラム実行時のBPの値 080E(0E 08)
- 紫枠、スコープ外側のBPの値。旧フレームの紫枠位置からコピー(02 10)
- オレンジ枠、セーブされたフレーム・ポインタの値(灰枠のアドレスを指す, FE 07=0x07FE番地)
- 青色、ENTERによって確保されたローカル変数領域(4バイト、ENTER 4,2の4という引数の効果)
そしてSPは青色のエリアの最下部を指しているので、その後スタック操作があれば青色領域の下に伸びていくことになります。
メンドクセーなあ、自分で書いていても分けわからなくなったぜ。忘却力の老人には過ぎたる操作よ。