Tutorial 1: 基礎

このチュートリアルの読者はMASMの使い方を知っている人を対象としている。 もしMASMに詳しくなければ、このチュートリアルを読み進める前に win32asm.zipをダウンロードして、 そのパッケージに入っているファイルで勉強しましょう。 ・・・、よし、それでは準備ができたとして、開講します。

Theory:

Win32プログラムは80286CPUから利用できるプロテクトモードで動作している。 しかし、80286CPUは今となっては歴史上のものと化しているため、 (80286CPUの次に発売された)80386CPUと、その末裔たちについてだけ調べればよい。 ウィンドウズはWin32プログラムをそれぞれ個別に切り分けした仮想的な空間で実行している。 つまり、それぞれのWin32プログラムは、自分専用の 4 GBのアドレス空間を有しているのである。 その 4 GBのアドレス空間というのは、実際にマザーボードに刺さっているメモリ容量のことではなく (そんなに必要ならメモリがいくらあっても足りない!!)、 プログラムが指すことのできるメモリの範囲が 4 GB(すなわち32bit)ということである。 ウィンドウズはプログラムが指すアドレスが有効になるようなメモリをプログラムに提供する必要がある。 もちろん、プログラムはウィンドウズのルールに従わなければならない。 さもなければ、一般保護エラー(一般に強制終了とも呼ばれる)の恐怖から逃れられない。 個々のプログラムはそれぞれのアドレス空間において独立している。 これはWin16(DOS)とは対照的で、Win16は他のプログラムのメモリも覗けてしまう。 ただ、Win32ではそのようなことはなく、そのため、 あるプログラムが他のプログラムのコードやデータを間違って書き直してしまうことが少なくなった。

メモリモデルも昔の16bit時代とは劇的に変わった。 Win32では、メモリモデルや、セグメントなどについて考えなくてよくなったのだ! つまり、「フラットメモリモデル」だけになったので、64Kセグメントはもはやなくなってしまった。 フラットメモリモデルは、連続した4GBのメモリ空間となっており、 セグメントレジスタをこねくりまわす必要が無くなった。 任意のセグメントレジスタを使って、4GBのメモリスペースのうちどこでも指すことができるようになった。 これにより、Win32上において、アセンブラでプログラムするのがC言語と同じくらい簡単になってしまった(これは言い過ぎじゃないか?)。

Win32でプログラムするのなら、いくつかの重要なルールを守らなければならない。 1つは、ウィンドウズは esi, edi, ebp, ebx を内部的に(プログラマには悟られずに)使用することだ。 そのため、これらのレジスタの値が変わったかどうかがわからないのである。 まずはこのルールを覚えましょう。 もし、これら4つのレジスタをコールバック関数で使用するとしたら、 ウィンドウズに制御を移す前にこれらのレジスタを元の状態に戻しておかなければならないことを忘れるな。 あなたの登録したコールバック関数はウィンドウズからCALLされる。 これのよい例題は、ウィンドウズプロシージャである。 ウィンドウプロシージャでそれらの4つのレジスタが使えないと言うことではないのだが、 ウィンドウズに制御を返す時にはちゃんと元の状態に戻すことをちゃんと確認しよう。

Content:

以下のプログラムがどんがらのプログラムだ。 もしこのプログラムについてちんぷんかんぷんでも、ちゃんと後で説明するので大丈夫である。

.386
.MODEL Flat, STDCALL
.DATA
  <Your initialized data>
   ......
.DATA?
  <Your uninitialized data>
  ......
.CONST
  <Your constants>
  ......
.CODE
  <label>
  <Your code>
  .....
   end <label>

これだけです。じゃー、解説に移りましょうか。

.386

これは、80386CPUの命令セットを使用することをMASMに伝える命令だ。 .386だけでなく、.486や .586なども使えるのだが、.386 が一番無難である。 実は、ほとんど同一モデルである、.386/.386p と .486/.486p というCPUモデルがあるのだが、 これらの "p" というバージョンは、特権命令を動作させる際だけに必要なのである。 特権命令というのは、保護モードで動作するCPUやOSに予約されているもので、 仮想デバイスドライバといったものはこの特権命令によってでしか動作しない。 あなたが作るようなプログラムの場合は、ほぼ非特権モードで動作するので、 非特権モードバージョンのCPUの方が安全である。

.MODEL FLAT, STDCALL

.MODELは、メモリモデルを表すものである。 Win32では、FLATメモリモデルだけである。

STDCALLは、関数の呼び出し規約のことで、 関数の引数を右から左に渡すのか、左から右に渡すのかとか、 スタックフレームを誰が(呼び出した側 or 呼び出された側)操作するのかということである。

Win16では、2つの呼び出し規約、CPASCALがあった。
Cの呼び出し規約は引数が右から左の順番で渡すようになっており、 一番右にかかれた引数が一番最初にスタックにプッシュされ、呼び出した側がスタックフレームを操作する。 例えば、foo(int first_param, int second_param, int third_param) という関数があったとして、 この関数をC呼び出し規約でCALLする時のアセンブラコードは以下のようになる。

push [third_param]            ; Push the third parameter
push [second_param]           ; Followed by the second
push [first_param]            ; And the first
call  foo
add   sp, 12                  ; The caller balances the stack frame

一方、PASCAL 呼び出し規約はCのそれと逆である。引数は左から右でCALLされた関数がスタックフレームを操作する。

Win16はPASCAL にも適合していたのだが、それは、PASCAL形式の方が小さなコードとなるからである。 C形式は便利なのだが、wsprintf() 関数といった可変長引数の時にいくつの引数があるのかわからないのである。 そのため、スタックフレームを呼び出された側が操作できないのである。

STDCALLは、CとPASCALの混合型である。 引数は右から左だが(C形式)、スタックフレームを操作するのは呼び出された側(PASCAL形式)である。 Win32では wsprintf()などの可変長引数関数を除いて、もっぱら、 STDCALL形式を使用している。 wsprintf() 関数は C形式を使用しないといけないのだ。

.DATA
.DATA?
.CONST
.CODE

これらの4つは「セクション」と呼ばれているものだ。 Win32ではセグメントを扱わなくていいのを覚えているだろ? でも、絶対アドレス空間を論理セクションに分けることができるのだ。 セクションの始まりは、前のセクションの終わりを示す。 データセクションと、コードセクションという2つのグループがあり、 データセクションは3つの区分に分けることができる。
  • .DATA
    このセクションにはプログラムの初期化データがある
  • .DATA?
    このセクションには、未初期化データがある。 時々、メモリ領域はとっておきたいが、別にデータをセットしておく必要がないことがあるだろう? このセクションは、そのような時のためのものだ。 未初期化データのメリットは、実行ファイルのサイズに入らないことだ。 例えば、.DATA?セクションに 10,000バイトのメモリを取ったとしても、 その実行ファイルは10,000バイト分大きくなるということはないのだ。 プログラムに、どれだけの領域を必要とするか、ということだけを示しているのである。
  • .CONST
    ここは、定数の宣言部分で、決して変更できない場所である。つまり *constant* ということだ。

あなたがプログラムする場合、別にこれらの3つのセクションを全部使う必要は無い。 使いたいセクションだけ宣言すればよい。

唯一のプログラムコードのためのセクションである: .CODE. セクションは、あながた作ったプログラム部分を格納する場所だ。
<label>
end <label>
where <label>は、任意のラベル名を使用することができ、あなたのプログラムの範囲を示すものである。 ラベル名は同一でなければならないし、これらのラベルの間に位置しなければならない。

[戻る]