このテキストは、2005年7月にデータハウスから出版された「アセンブリ言語の教科書」の原稿をWEB用に修正したものです。WEB用に修正したといっても、誤植を直した程度であり、ほぼそのままの状態で公開しています。
現在でも「アセンブリ言語の教科書」は書店で売られており、一般に流通しているため、本来ならば、出版社との契約上、このようにフリーでWEB上に公開することはできません。しかし、「アセンブリ言語の教科書」は、発売後すでに一年を過ぎようとしているにも関わらず、現在でも安定した売り上げを伸ばしており、当初の予想を超えて多くの方々に読んでいただけました。
よって、出版社に「本書の値段が高くて、読みたくても買えない学生の方々や、まだ本書の存在を知らない人たちのために、原稿の一部をWEB上にも公開できないだろうか」と、相談を持ちかけたところ、本書に関わった編集者からも「原稿のすべては無理だが一部分といことなら問題ない」という承諾をいただきました。
出版社も、著者である私も、本書がもっと売れて欲しいという気持ちはもちろんあります。しかし、同時に、少しでもアセンブラに興味を持ってくれる多くの方々に読んでもらいたいとも思っており、そのような考えから、今回、原稿をWEB上に公開するということになりました。公開されているのは、原稿のほんの10パーセント程度ではありますが、著者が気に入った部分や、これからアセンブラを学ぼうとする方々に有用だと思える部分をピックアップしたつもりです。これらの情報が、少しでも読者の方々の参考になれば幸いです。(2006年08月 Kenji Aiko)
8086とは、1978年にIntel社が発売した16ビットマイクロプロセッサ(MPU)のことです。マイクロプロセッサ(超小型処理装置)とは、コンピュータ内で基本的な演算処理を行う、いわばコンピュータの心臓部に当たる半導体チップのことですが、これは、CPUと同じと捉えて問題ありません。厳密に言えば、コンピュータの演算処理は複数の半導体チップが連携して行っており、この半導体チップ群を「中央処理装置(CPU)」と呼んでいたようですが、現在ではマイクロプロセッサが全ての演算を担当することが当然となっているため、CPUという言葉もMPUと同じ意味として使われているようです。
現在のクライアントPC(サーバとして利用されない一般的なPC)には大抵32ビットCPUが搭載されています。最近では、32ビットCPUから64ビットCPUへの移行について議論されている状態であり、あと5年後にはクライアントPCでも64ビットCPUが搭載されるようになっているかもしれません。
8086アセンブラとは、16ビットCPUである8086上で動作するアセンブラのことです。しかし現在の主流は32ビットCPUであり、Windows2000やWindowsXPが動いている環境ならば、まず間違いなく32ビットCPUが搭載されているコンピュータでしょう。そのような環境で8086アセンブラを学ぶことができるのか、と思われるかもしれませんが、現在もっとも利用されているOSであるWindowsXPにも、16ビットCPUとの互換性は、完全にとはいえませんが保たれています。
現在のWindowsプログラミングは、GUIが当たり前で、膨大なライブラリを駆使してあらゆることが実現できます。それに比べると、アセンブラプログラミングとはなんとも地味で魅力を感じにくいかもしれません。しかし、アセンブラとはコンピュータが理解できるマシン語にもっとも近いプログラミング言語であることは間違いありません。そういう意味でいうなれば、アセンブラの理解が大いにスキルアップに繋がることは間違いないでしょう。たとえ、直接アセンブラを使用してツールを作ろうと思っていなくとも、32ビット、さらには64ビットとコンピュータの進化が進んでいこうとも、コンピュータの根底で処理されているのはマシン語でありアセンブリ言語なのです。そして、もちろんWindowsでもアセンブラを学ぶことは可能です。アセンブラにはいろいろな種類がありますが、最初に学ぶにはコマンドプロンプトに付属しているdebugコマンドを利用するのが良いです。
では、いよいよアセンブラの世界へ足を踏み入れてみます。コマンドプロンプトを起動し、「debug」と入力してください。

コマンドプロンプトに「debug」と入力すると、コマンドプロンプトの表示エリアがクリアされ、以下のように表示されます。

まずは、ここまで表示されます。ちなみにこれはWindowsXPで行った結果なので、他の環境では少し違うかもしれませんが、似たようなものが表示されます。では、さっそくプログラムを書いていくことにします。最初に「A 0100」と入力してください。「A」の後には、プログラムを書き始めるメモリアドレスを指定します。通常は「0100」を指定します。

するとこのように表示されます。「34F2」の部分は毎回変わりますが、これはセグメントと呼ばれておりメモリアドレスを示しています。ちなみに「0100」もメモリアドレスを示しており、これはオフセットと呼ばれています。とりあえず、いまはこの部分は気にしないでください。では、ここから実際にアセンブラのプログラムを書いていきます。以下のように入力してください。

ここまでがアセンブラのプログラミングです。「34F2:0103」は改行のみ入力してください。次にこのプログラムを実行させたいので、以下のように入力します。

「G」には実行を開始するアドレスと、終了するアドレスを渡します。よって、上記の例では「0100」番地から「0103」番地までのプログラムを実行するという意味になります。プログラムの実行が終了するとレジスタの値が表示されます。注目すべき部分は最初の「AX=0050」です。これはAXという箱(レジスタ)に0050という値が代入されていることを示しています。つまり、「mov ax, 50」は、AXレジスタに50を代入するという命令だったわけです。
まず、「mov」は命令です。これは処理の内容を意味します。次の「ax」と「50」はオペランドと呼ばれます。左のオペランドを「デスティネーションオペランド」、右のオペランドを「ソースオペランド」と呼びます。つまりこの場合は「50」という値がソースオペランドとなります。
mov命令は「デスティネーションオペランドへソースオペランドを代入しろ」という命令です。よってこの1行の意味は「axへ50という値をmov(代入)する」ということになります。そして現に「AX=0050」というようにAXレジスタに50という値が代入されているのが分かります。
さて、アセンブラを扱うためにはコマンドプロンプトに「debug」と入力すればよかったわけですが、終了するためには「q」を入力してください。

これで無事debugコマンドを終了しました。これが一連のプログラミングの流れとなります。まず、「A 開始アドレス」と入力し、プログラムを書きます。書き終えたら「G=実行アドレス 終了アドレス」と入力してプログラムを実行し、最後に「q」を入力してdebugコマンドを終了します。
レジスタとは、一言で言うならば「CPUの中にあるメモリ」です。一般的にメモリと呼ばれているものは正確にはメインメモリ(主記憶装置)と言い、コンピュータ内でデータやプログラムを記憶する装置のことを指します。それに対し、レジスタとは、CPU(Central Processing Unit)の中に組み込まれているメモリのことを指します。最近のメインメモリは、128MBや256MB、さらには512MBといった大量の記憶領域を持っています。それに比べて、CPUの中に搭載されているメモリであるレジスタは、ほんのわずかな記憶領域しか持っていません。しかし、レジスタはメインメモリよりもはるかに高速にアクセスできます。そしてアセンブラを学ぶこととは、まさに、レジスタの利用法を学ぶことに他なりません。
レジスタには、大きく分けて「汎用レジスタ」「フラグレジスタ」「命令ポインタ」「セグメントレジスタ」という4つの種類があります。まずはその中の1つの汎用レジスタと呼ばれるものをみてみます。

汎用レジスタは6つあります。これらはすべて16ビット(2バイト)の領域を持っています。以下のプログラムをみてください。
----- コマンドプロンプト C:\DOCUME~1\kenji>debug -A 0100 34D8:0100 mov ax, 1 34D8:0103 mov bx, 2 34D8:0106 mov cx, 3 34D8:0109 mov dx, 4 34D8:010C mov si, 5 34D8:010F mov di, 6 34D8:0112 -G=0100 0112 AX=0001 BX=0002 CX=0003 DX=0004 SP=FFEE BP=0000 SI=0005 DI=0006 DS=34D8 ES=34D8 SS=34D8 CS=34D8 IP=0112 NV UP EI PL NZ NA PO NC 34D8:0112 0000 ADD [BX+SI],AL DS:0007=FE -----
最後に出力されたレジスタの情報をみてください。それぞれの汎用レジスタの値が表示されています。「AX=0001」「BX=0002」「CX=0003」「DX=0004」となっています。さらに「SI=0005」「DI=0006」も代入した値になっています。
汎用レジスタはプログラマが自由に利用してよいレジスタです。基本的にどんなことに利用しても構わないのですが、一応大まかな使用方法が定義されています。一般的に、BXレジスタはメモリのアドレスを入れるために使用するべきであり、CXレジスタはカウンタなどに利用するようです。AXレジスタとDXレジスタは共にデータを入れるために利用されます。SIレジスタとDIレジスタは、特に決められていないようですが、BXレジスタと同じような使い方をされることが多いです。
汎用レジスタはすべて16ビット(2バイト)です。そして、AX、BX、CX、DXの4つに限り、それぞれさらに2つに分けられます。どういうことかというと、以下のプログラムをみてください。
----- コマンドプロンプト C:\DOCUME~1\kenji>debug -A 0100 34D8:0100 mov al, FF 34D8:0102 mov ah, 44 34D8:0104 mov bl, 12 34D8:0106 mov bh, 34 34D8:0108 -G=0100 0108 AX=44FF BX=3412 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=34D8 ES=34D8 SS=34D8 CS=34D8 IP=0108 NV UP EI PL NZ NA PO NC 34D8:0108 800000 ADD BYTE PTR [BX+SI],00 DS:3412=00 -----
AXレジスタに44FFという値が入っています。これはAXレジスタの上位8ビット(1バイト)はAHレジスタとして扱うことができるというわけです。同じようにALレジスタはAXレジスタの下位8ビット(1バイト)となります。これはAX、BX、CX、DXの4つのレジスタで同じことが言えます。図示すると以下のようになります。

AX、BX、CX、DXはそれぞれサイズが16ビットですが、AH、AL、BH、BLなどといったものは8ビット(1バイト)です。汎用レジスタにはこのような特徴があります。ただしSI、DIといったレジスタには、8ビット単位のレジスタは存在しません。あくまでAX、BX、CX、DXの4つだけです。
コンピュータとは「電子計算機」のことを意味します。そして、その名のとおりコンピュータは電子計算機です。よって加算や減算といった計算を行うことができます。では、汎用レジスタを使って加算を行うプログラムを作ってみます。
データの転送にはmov命令を使いました。この命令を使うことによってレジスタへ定数を代入したり、またメモリにレジスタの値を代入することができました。これと同じようにデータを加算する命令があります。それは、add命令です。まずは実際に加算を行ってみますので以下のプログラムを入力してください。
----- コマンドプロンプト C:\DOCUME~1\kenji>debug -A 0100 34D8:0100 mov ax, 1 34D8:0103 mov bx, 3 34D8:0106 add ax, bx 34D8:0108 -G=0100 0108 AX=0004 BX=0003 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=34D8 ES=34D8 SS=34D8 CS=34D8 IP=0108 NV UP EI PL NZ NA PO NC 34D8:0108 0000 ADD [BX+SI],AL DS:0003=9F -----
add命令を使うと、最初に渡されたレジスタ(デスティネーションオペランド)に2番目に渡されたレジスタ(ソースオペランド)の値を加算します。つまりこの例でいうならば、AXレジスタの値に、BXレジスタの値が加算されることになります。axには1を代入し、bxには3を代入していますので、最終的にAXレジスタには4が入れられることになります。add命令が実行されてもBXレジスタの値は変化しませんので、BXレジスタの値は3のままです。
同じようにして減算処理も行うことができます。減算はsub命令を使います。
----- コマンドプロンプト C:\DOCUME~1\kenji>debug -A 0100 34D8:0100 mov ax, 5 34D8:0103 mov bx, 3 34D8:0106 sub ax, bx 34D8:0108 -G=0100 0108 AX=0002 BX=0003 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=34D8 ES=34D8 SS=34D8 CS=34D8 IP=0108 NV UP EI PL NZ NA PO NC 34D8:0108 0000 ADD [BX+SI],AL DS:0003=9F -----
axには5を代入し、bxには3が代入されています。sub命令を使って、AXレジスタの値からBXレジスタの値が減算されることになります。よってAXレジスタには2が入ることになります。
ここまでのテキストは「アセンブリ言語の教科書」の40ページ以降の原稿をWEB用にリライトしたものです。
本格的にアセンブリ言語を学ぶためには、コンパイラが必要となります。世の中には様々なアセンブリ言語のコンパイラが存在しますが、本章では、現在もっとも多く利用されているアセンブラのひとつであるNASM(The Netwide Assembler)を使ってプログラミングを学んでいくことにします。
NASMとは、様々なファイルフォーマットに対応しているアセンブラであり、もちろんマイクロソフトの16ビットOBJファイル、およびWin32もサポートしています。さらに、シンプルなバイナリも出力することができ、その高い汎用性により、現在もっとも多く利用されているアセンブラのひとつです。
NASMは、以前までは「The Netwide Assembler : Home Page」からダウンロードできたはずなのですが、今はなぜかページを開いていも何も表示されないので、直接ダウンロードページへ進んでください。



NASMはオープンソースであり、Windows、Linuxを始めとしていくつものOSに対応しています。ここでは「Win32 binariess」をダウンロードしてください。その名の通りWindows用のNASMです。そして「nasm-0.98.39-win32.zip」をダウンロードします。


どこからダウンロードするのか、ということですが、特にどこでもよいです。適当なサーバを選んでダウンロードしてください。「Download」以下のリンクをクリックすると実際のダウンロードページへ進みます。そのまま待っていればダウンロードが始まりますが、もしいつまでたってもダウンロード確認のダイアログが表示されない場合は、リンクをクリックしてください。
ダウンロードが完了すると、nasm-0.98.38-win32.zipというファイルが作成されますので、解凍(展開)してフォルダを開いてください。そして、nasmw.exe、ndisasmw.exe、copyingという3つのファイルがあることを確認してください。これで無事インストール完了となります。

解凍されたフォルダは特にどこに置いてもらってもかまいませんが、便宜上本書ではnasmw.exe、ndisasmw.exe、copyingの3つのファイルが入っているフォルダの名前を「nasm」とし、C:\以下に置いていると仮定して話を進めていきます。
インストールが完了したので、次は正確にコンパイルできるかどうかを試します。プログラムの内容はシンプルに「A」という文字を標準出力に表示するだけのプログラムとします。
----- putchar.asm
org 100H (おまじない的なもの、詳細な説明は後ほど)
section .text (おまじない的なもの、詳細な説明は後ほど)
start: (ラベル、ここからプログラムがスタートする)
mov ah, 02H (AHレジスタに0x02を転送)
mov dl, 41H (DLレジスタへ0x41を転送)
int 21H (MS-DOSシステムコール呼び出し)
mov ah, 4CH (AHレジスタへ0x4Cを転送)
int 21H (MS-DOSシステムコール呼び出し)
-----
----- コマンドプロンプト C:\nasm>debug -q C:\nasm>nasmw putchar.asm -fbin -o putchar.com C:\nasm> -----
最初にdebugコマンドを起動してすぐに終了しているのは、コマンドプロンプトを16ビットモードに切り替えるためです。環境がWindows2000/XPの場合はこれが必要です。Windows9x系でのMS-DOSプロンプトでは必要ありません。Windows2000/XPに付属しているコマンドプロンプトは、通常32ビットモードで起動されます。しかし、私たちが作成しているアセンブラプログラムは16ビットCPU用に書かれてあるため正常にプログラムが動作しない場合があります。よって、あらかじめコマンドプロンプトを16ビットモードに切り替えてプログラムをコンパイルし実行しているわけです。最初にdebugコマンドを実行したら、あとはコマンドプロンプトを終了するまでは16ビットモードですので、コンパイル、実行を繰り返して構いません。以後、本書ではdebugコマンドの部分は省略します。
コンパイルには、まず「C:\nasm>nasmw <asmファイル>」と指定して「-fbin」オプションをつけてください。このオプションはバイナリファイルを作成するという意味ですが、いまは決まり事と考えてください。さらに「-o」オプションをつけた後、作成する実行ファイル名を指定します。作成する実行ファイル名の拡張子は必ず「.com」としてください。

実行すると、標準出力に「A」という文字が表示されます。
1文字出力 AH = 02H, 06H
02H Ctrl+Cを承諾
06H Ctrl+Cを拒否
DL = 出力する文字
戻り値:なし
プログラム終了 AH = 4CH
AL = リターンコード
戻り値:なし
マシン語(機械語)とは、CPUが直接解釈し実行できる言語です。数字の羅列で表現され、残念ながら人間が容易に理解できるような形式にはなっていません。しかしアセンブリ言語を学ぶのならば、マシン語に対してもある程度の知識が必要となります。よって、これから少しマシン語について見ていきたいと思います。では以下のプログラムを見てください。
----- putchar.asm
org 100H (メモリアドレス100番地から始める)
section .text (以降はテキストセクションであるという通知)
start: (ラベル、ここからプログラムがスタートする)
mov ah, 02H (AHレジスタに0x02を転送)
mov dl, 41H (DLレジスタへ0x41を転送)
int 21H (MS-DOSシステムコール呼び出し)
mov ah, 4CH (AHレジスタへ0x4Cを転送)
int 21H (MS-DOSシステムコール呼び出し)
-----
上記のプログラムは、標準出力に「A」と出力するプログラムでした。まず最初の1行はこのプログラムをメモリアドレス0100番に書き込むということを示しています。COMファイルを作成する場合は、必ず0100番地から始めることになりますので、決まりごとと考えてもらっても構いません。ちなみにメモリの0000番地から00FF番地までには、OSによってプログラムに関する様々なデータが格納されます。NASMでは、数値を表す方法として10進数で表記することもできるので、10進数と区別するために、16進数の数値の場合は最後に「H」をつけることになっています。小文字の「h」でも良いのですが、本章では大文字の「H」を使うことにします。
AHレジスタに02Hを、DLレジスタに文字「A」を意味する41Hを格納して、システムコールを呼び出しているので、これは標準出力への文字の表示です。最後の2行は、プログラムを終了し、使用していたメモリを解放するシステムコールです。以後、NASMのプログラムでは必ず書くので、覚えておいてください。
では、この実行ファイル(putchar.com)をバイナリエディタで開いてください。

----- putchar.com B4 02 B2 41 CD 21 B4 4C CD 21 -----
このような数値の羅列が表示されます。マシン語は人間には容易に理解できないものですが、アセンブリ言語ならばアルファベットですので、簡単に理解することができます。そしてマシン語とアセンブリ言語は、命令が1対1に対応しています。例えば、putchar.asmの4行目「mov ah, 02H」は、マシン語では「B4 02」に対応しています。さらに厳密にいえば「mov ah」が「B4」に対応しているわけです。同じようにputchar.asmの5行目「mov dl, 41H」は、マシン語では「B2 41」に対応しています。つまり「mov dl」が「B2」に対応していることになります。さらに6行目「int 21H」は「CD 21」に対応しています。ここまで分かれば、7行目と8行目も理解できるでしょう。以下にプログラムとの対応を示します。

さて、アセンブリ言語との対応表が分かれば、マシン語がどのようなものか、ほんの少し見えてくるのではないでしょうか。以下のようなプログラムを書いてみます。
----- test01.asm
org 100H
section .text
start:
mov al, 01H (ALレジスタに0x01を転送)
mov cl, 01H (CLレジスタに0x01を転送)
mov dl, 01H (DLレジスタに0x01を転送)
mov bl, 01H (BLレジスタに0x01を転送)
mov ah, 02H (AHレジスタに0x02を転送)
mov ch, 02H (CHレジスタに0x02を転送)
mov dh, 02H (DHレジスタに0x02を転送)
mov bh, 02H (BHレジスタに0x02を転送)
mov ax, 03H (AXレジスタに0x03を転送)
mov cx, 03H (CXレジスタに0x03を転送)
mov dx, 03H (DXレジスタに0x03を転送)
mov bx, 03H (BXレジスタに0x03を転送)
-----
プログラムの内容はただmov命令を使って、値を代入しているだけです。実行するプログラムではないので、最後の終了システムコールは省略しました。では、このプログラムをコンパイルして、バイナリエディタで開いてください。くれぐれも実行はしないでください。
----- コマンドプロンプト C:\nasm>nasmw test01.asm -fbin -o test01.com C:\nasm> -----

test01.comをバイナリエディタで開いてみると、このようなマシン語が生成されます。さて、最初はALレジスタの操作ですが、これは「B0」となっています。次のCLレジスタの操作では「B1」です。さらに次のDLレジスタは「B2」であり、BLレジスタは「B3」と対応しています。このように見ていくと「B0」から「B3」まではALレジスタ、CLレジスタ、DLレジスタ、BLレジスタまでが対応しており「B4」から「B7」まではAHレジスタ、CHレジスタ、DHレジスタ、BHレジスタまでが対応していることになります。そして「B8」から「BB」までは、AXレジスタ、CXレジスタ、DXレジスタ、BXレジスタまでが対応しています。以下に、命令に対応するマシン語を示します。

マシン語は確かに容易には理解できません。しかし、決して暗号化されているわけではなく、読もうと思えば読めないことはないのです。そして、その気になれば、マシン語でプログラミングを行うことも可能です。
では、実際にマシン語でプログラミングをやってみることにします。最初のマシン語プログラムとして、標準入力から文字を受け取るだけのプログラムを作成してみます。標準入力から文字を受け取るシステムコールを利用するには、AHレジスタを01Hとしてint命令を呼びだします。入力された文字はALレジスタに格納されます。
では、まずはバイナリエディタを起動してください。最初にやるべきことはAHレジスタに01Hを格納することです。よって「B4 01」と入力します。次にint命令を呼び出します。「int 21H」は「CD 21」でした。これで標準入力から文字を受け取るシステムコールを呼び出しました。終了プログラムも書かなければなりませんので、AHレジスタに4CHを格納します。「B4 4C」そしておなじみの「CD 21」を書いて終了です。よってプログラムは以下になります。
----- test02.com B4 01 CD 21 B4 4C CD 21 -----

では、このプログラムをtest02.comというファイル名で保存し、実行してください。直接マシン語で記述しているので、コンパイルやリンクといったことは必要ありません。まさにコンピュータが解釈し実行できる言語です。

見事にマシン語でプログラミングを行うことができました。ちなみに上記のマシン語は以下のアセンブラ命令に対応しています。

つまり、test02.comをNASMで書くとしたら、以下のようになるということです。
----- test02.asm
org 100H
section .text
start:
mov ah, 01H (B4 01)
int 21H (CD 21)
mov ah, 4CH (B4 4C)
int 21H (CD 21)
-----
さて、マシン語で最初のプログラムを書きましたが、このプログラムでは、コンピュータからのレスポンスがなく何か物足りません。よって、次はユーザが入力した文字を受け取り、その文字の次の文字を出力するように改良してみます。例えばユーザが「a」と入力したら「b」と出力し、「4」と入力したら「5」と出力するようなプログラムを作ります。
まずマシン語の最初の4バイトですが、標準入力から文字を受け取らなければならないので「B4 01 CD 21」となります。このシステムコールにより、入力された文字はALレジスタに格納されているので、その文字を次の文字にするためにインクリメントします。しかし、ALレジスタをインクリメントする命令が分かりません。もちろん実際にNASMで書いてコンパイルし、バイナリエディタで実行ファイルを閲覧すれば良いのですが、それは少々面倒くさいので、ここではdebugコマンドを使います。
----- コマンドプロンプト C:\nasm>debug -A 0100 34D6:0100 inc al 34D6:0102 -U 0100 0100 34D6:0100 FEC0 INC AL -----
「-U」コマンドは、逆アセンブルを行うコマンドです。最初に「-A」コマンドでプログラムを書いて、次に「-U」コマンドで逆アセンブルすることにより、マシン語とそれに対応するアセンブリ命令が出力されます。「-U」コマンドは以下のような定義になっています。
----- -U [開始アドレス] [終了アドレス] -----
逆アセンブルを開始するアドレスと、逆アセンブルを終了するアドレスを引数として指定してやれば、そのアドレス間の命令が逆アセンブルされます。上記に例では、inc命令に対応するマシン語が知りたかったので「-U 0100 0100」というように開始アドレスと終了アドレスを同じにしています。
さて、これにより「inc al」は「FF C0」に対応することが分かりました。よって標準入力から文字を受け取るマシン語命令と合わせて、プログラムは「B4 01 CD 21 FF C0」となりました。次は文字を出力したいので、AHレジスタに02Hを格納し、ALレジスタの値をDLレジスタに格納します。まずAHレジスタに02Hを格納するマシン語は、「B4 02」です。ALレジスタの値をDLレジスタに格納するマシン語は分からないので、再びdebugコマンドを使って調べてみます。
----- コマンドプロンプト C:\nasm>debug -A 0100 34D6:0100 mov dl, al 34D6:0102 -U 0100 0100 34D6:0100 88C2 MOV DL,AL -----
debugコマンドの逆アセンブル結果から「mov dl, al」は「88 C2」ということが分かりました。この命令を実行した後、システムコールを呼び出す「CD 21」を追加し、文字の出力が完了します。
最後にプログラムの終了を意味する「B4 4C CD 21」を追加してマシン語入力は終了です。以下が、キー入力を受け付け、入力された次の文字を出力するマシン語プログラムです。
----- test03.com B4 01 CD 21 FF C0 B4 02 88 C2 CD 21 B4 4C CD 21 -----


バイナリエディタで上記のプログラムを入力して、test03.comというファイル名で保存し、実行してください。実行後、キー入力待ちとなりますので、何かのキーを入力してください。「e」と入力すると「f」と出力され、「6」と入力すると「7」と出力されます。このように入力した文字の次の文字が出力されます。ちなみにtest03.comの対応アセンブラ命令は以下です。

これは、debugコマンドでも確認することができます。
----- コマンドプロンプト C:\nasm>debug test03.com -U 0100 010E 3528:0100 B401 MOV AH,01 3528:0102 CD21 INT 21 3528:0104 FFC0 INC AX 3528:0106 B402 MOV AH,02 3528:0108 88C2 MOV DL,AL 3528:010A CD21 INT 21 3528:010C B44C MOV AH,4C 3528:010E CD21 INT 21 -----
このように、アセンブラ命令とマシン語は1対1で対応しています。マシン語は確かに容易には解読できませんが、やろうと思えば書けないことはありません。現にコンパイラなどがなかった時代では、ハンドアセンブルといって、プログラマが手動でアセンブラ命令をマシン語へ変換していたこともあったようです。
しかし、何かのツールを作成しようとしたときに、直接マシン語で書くよりもアセンブリ言語で記述する方が簡単であることはいうまでもありませんし、さらにはCやJavaといった高級言語で書いたほうが、より実用的であることは間違いありません。しかし、高級言語で書いたほうが実用的であるからといって、アセンブリ言語を学ばなくて良いとわけではありません。プログラミング言語にも向き不向きがあります。アセンブラを使わなければ実現できない処理もありますし、Cを使ったほうが効率的に組めることも多々あります。適材適所という言葉が言い表しているように、どういったことをやりたいか、という要望に合わせてプログラミング言語を選べることに越したことはないでしょう。もしかしたら、マシン語の知識が必要になることもあるかもしれません。
1文字入力 AH = 01H, 06H, 07H, 08H
01H 入力を待ち入力された文字をディスプレイに表示する。Ctrl+C承諾
06H 入力を待たず入力された文字をディスプレイに表示しない。Ctrl+C拒否
07H 入力を待ち入力された文字をディスプレイに表示しない。Ctrl+C拒否
08H 入力を待ち入力された文字をディスプレイに表示しない。Ctrl+C承諾
DL = AHレジスタが06Hの場合のみFFHを指定
戻り値:AL 入力文字(入力がなかった場合は00Hが設定される)
いよいよ、プログラミングを学ぶ際のお約束である「Hello World!」の出力を行うプログラムを作成します。通常のプログラミング言語だと一番最初の例題として紹介されるような内容のプログラムですが、アセンブラだとやっとここまできたかという感じがします。では以下のプログラムをみてください。
----- hello.asm
org 100H
section .text
start:
mov ax, 0200H
mov bx, HELLO
PRINT:
mov dl, [ds:bx]
cmp dl, 00H
jz PRINTEND
int 21H
inc bx
jmp PRINT
PRINTEND:
mov ah, 4CH
int 21H
section .data
HELLO db 'Hello World!', 00H
-----
まず最初のAXレジスタに0200Hを入れているのは、AHレジスタに02Hを格納するためです。別に「mov ah, 02H」と書いても良いのですが、下の1行も2バイト移動なので読みやすくするために合わせただけです。BXレジスタには「Hello World!」文字列のアドレスを格納します。
「PRINT:」はラベルです。debugコマンドの8086アセンブラでは、jmp命令やloop命令などには直接アドレスを指定していましたが、NASMではラベルを定義して、そのラベルをアドレスのように扱います。とりあえずこの行では定義しているだけですので、処理としては何もしません。PRINTラベル以下に進みます。まずDLレジスタには出力したい文字を指定します。BXレジスタにはHELLOのアドレスが格納されているので、その値を表示します。よって、最初は「H」が表示されることになります。dsはデータセグメントですが、comファイルだと、DSレジスタ、SSレジスタ、CSレジスタはすべて同じ値なので、どのセグメントでも関係ありません。結局「ds:bx」は「Hello World!」のアドレスを示すことになります。
AHレジスタには02Hが入っているので、システムコールが呼ばれたら1文字出力されます。最初は「H」が出力されることになります。そしてbxをインクリメントし「ds:bx」が「H」の次の「e」を示すようにして、PRINTラベルへジャンプします。「ds:bx」は「e」を示しているので今度は「e」が出力され、以後、同じ処理がDLレジスタが00Hになるまで続きます。DLレジスタが00HになるとPRINTENDへジャンプし、プログラムを終了します。では、コンパイルしてください。実行すると「Hello World!」という文字列が出力されます。

ではこのプログラムをバイナリエディタで開いてください。「Hello World!」という文字列は最後の00Hも合わせると、全部で13バイトです。hello.comのバイナリであるマシン語は、もちろんそれぞれのアセンブリ命令に対応しているわけですが、最後の13バイトをみてください。ASCIIコードと対応させると分かりますが、それぞれが「Hello World!」という文字列を表しています。

----- hello.comの最後の13バイト 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 H e l l o W o r l d ! -----
つまり、NASMでは「HELLO db 'Hello World!', 00H」というように変数を定義すると、実行ファイルの最後にその文字列が追加されます。ポイントは「section .data」であり、これは以下のプログラムはデータセクションであるということを意味しています。この部分に書かれるプログラムは、実行すべきマシン語ではなく、データとして扱われるマシン語であるということです。そして、データであるその文字列をプログラムが参照することになるわけです。プログラム中では「HELLO」は文字列のアドレスを表します。次の「db」は1バイトの羅列であることを表します。つまり確保されるメモリ領域の単位が1バイトであるということです。そしてそのメモリへ「'Hello World!', 00H」が連続して格納されることになります。これはHELLOという名前のchar型(1バイト)配列を用意したと考えれば分かりやすいと思います。
ここまでのテキストは「アセンブリ言語の教科書」の90ページ以降の原稿をWEB用にリライトしたものです。
GNUとは「Gnu's Not Unix」の略であり、Richard Stallman氏を中心としたFSF(Free Software Foundation)によって作られているUNIX互換ソフトウェアシステムの名称です。そして、Stallman氏の「優れたソフトウェアはフリーであるべき」という思想に基づき、GNUに含まれるソフトウェアはすべてフリーソフトとなっています。
この思想に基づいて作られたアセンブラが「The GNU Assembler」であり、通称GASと呼ばれています。GASはDean Elsner氏、Jay Fenlason氏を中心に開発されたアセンブラであり、AT&T形式に従うアセンブラです。NASMやMASM、TASMといったアセンブラはすべてIntel形式の記述方法であるのに対し、GASはAT&T形式の記述法であるため、最初はすこし戸惑うかもしれませんが、それぞれの記述法に慣れればどちらの記述法になっても気にはなりません。
Linux環境でもNASMを使用してプログラミングを行うことは可能です。しかし、gccがLinuxの主流コンパイラであるので、GASを覚えておいた方が何かと便利です。よって、この章では、GASを利用したプログラミングを学んでいくことにします。
Linuxでアセンブラプログラミングを行う前に、まずGDBの使い方を解説します。
GDBは「The GNU Source-Level Debugger」の略でありGNUが提供する有名なデバッガです。GDBはフリーソフトであり、著作権はGCCと同様にGNU GPLにより規定されています。世の中にはプログラム開発を手助けする様々なデバッガがありますが、GDBはUNIX系OSではもっともよく使われているデバッガです。しかし、GDBはCUI環境でのコマンド形式のデバッガであるため、GUI環境に慣れている人にとっては少々使いにくいものになっています。よって、そのGDBの簡単な解説を行います。
まずは以下のプログラムをみてください。
----- ex1.s
.text
.globl main
main:
movl $1, %eax
movw $0xFFFF, %ax
movb $0b01010101, %ah
ret
-----
eaxレジスタに1を転送し、そのあとaxレジスタに0xFFFFを転送し、そしてahレジスタに0b01010101を転送するだけのプログラムです。「$」から始まるのは既値であり、最初に「0x」がついたら16進数、「0b」がついたら2進数を意味します。では、この処理の1命令ずつみていくためにgdbを使ってex1を実行することにします。
----- terminal $ gcc -Wall ex1.s -o ex1 $ gdb ex1 GNU gdb 5.0 Copyright 2000 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... (gdb) -----
まずはex1.sをコンパイルします。そしてgdbの引数にそのプログラムを渡してgdbを実行します。すると上記のような文字列が表示されます。gdbはフリーソフトウェアであり、GNU GPLライセンスに基づくといったことが書かれてあります。
では、さっそくgdbを使ってプログラムex1を解析していきます。まずはmain関数にブレイクポイントを仕掛けます。
----- terminal (gdb) break main Breakpoint 1 at 0x80483c0 -----
「break main」と入力することでブレイクポイントを仕掛けることができます。するとアドレス0x80483c0にブレイクポイントを設置したむねが表示されます。では、プログラムを実行します。gdb上でプログラムを実行するためには「run」と入力します。
----- terminal (gdb) run Starting program: /home/kenji/book2/li_gcc/ex1 Breakpoint 1, 0x080483c0 in main () -----
「run」と入力すると実行されたプログラムのパスが表示されます。そして、あらかじめ仕掛けておいたブレイクポイントで処理が停止したことが分かります。では、main関数から1命令だけ進めてみます。つまり「movl $1, %eax」を実行させます。1命令だけ実行させるためには「stepi」を使います。
----- terminal (gdb) stepi 0x080483c5 in main () (gdb) display/x $eax 1: /x $eax = 0x1 -----
「stepi」と入力すると、どの部分が実行されたのかを示すアドレスが表示されます。上記の例では、0x080483c5のアドレスにある命令が実行されたようです。eaxレジスタの値を参照するためには、「display/x $eax」と入力します。すると、eaxレジスタの値が0x1であることが分かります。つまり、「movl $1, %eax」が実行されていることが確認できます。では、次の命令も実行させます。さらに「stepi」と入力してください。
----- terminal (gdb) stepi 0x080483c9 in main () 1: /x $eax = 0xffff -----
次は「display/x $eax」を入力しなくてもレジスタの値が表示されます。みての通り、「movw $0xFFFF, %ax」が実行され、eaxレジスタの値が0xffffになっています。では、さらに処理を進めて下さい。
----- terminal (gdb) stepi 0x080483cb in main () 1: /x $eax = 0x55ff -----
同じく次の命令の「movb $0b01010101, %ah」が実行され、eaxレジスタの値が0x55ffとなっています。2進数の01010101は16進数では0x55です。そして、ahレジスタはaxレジスタの上位8ビットなので、結果的にeaxは0x55ffとなるわけです。当たり前ですが、正確にプログラムの処理が実行されているのが分かります。最後にgdbを終了するためには「quit」もしくは「q」と入力します。
----- terminal (gdb) quit The program is running. Exit anyway? (y or n) y $ -----
「現在プログラムが実行されていますが、本当に終了しますか?」といった確認メッセージが表示されますが、終了するので、「y」と入力してください。これでデバッグは終了です。
一連のデバッグの流れをみていきましたが、その中で使ったコマンドがいくつかありました。それを以下に列挙します。

breakはブレイクポイントを設置します。フォーマットは「break ラベル名」となります。runはプログラムを始めから実行するコマンドです。ブレイクポイントを設置していなければ、プログラムの終了、もしくはセグメント違反などの何かしらのエラーまで処理を進めます。displayコマンドは現在のレジスタの値を表示します。フォーマットは「display/FMT DATA」です。FMTには「x」「t」「o」などを入れることができ、それぞれレジスタの値を16進数で表示させるか、2進数で表示させるか、8進数で表示させるかを決めることができます。FMTを指定しなかったら、10進数で表示されます。stepiコマンドは、1命令のみ実行させるコマンドです。quitはgdb自体を終了します。
以上が、ex1のデバッグ時に使用したコマンドです。ただし他にも重要なコマンドがいくつかありますので、それを説明します。


まず、breakコマンドによって設置したブレイクポイントを解除するclearコマンドです。フォーマットはbreakコマンドと同じで「clear LABEL」となります。nextiコマンドは、ネクスト実行となります。stepiと同じで1命令を実行するコマンドですが、nextiはcall命令もひとつの命令として実行します。しかし、stepiはcall命令により呼ばれた先のアドレスまで進みます。そこがstepiとnextiの違いです。

つまり、stepiは別の関数にある命令にも進みますが、nextiは現在の関数にある命令しか実行しません。よって、call命令がひとつの命令として処理されるわけです。
continueは現在位置から次のブレイクポイントまで一気に実行します。もしブレイクポイントがなければプログラムは終了します。xはADDRで指定されたアドレスの値を表示します。USIZEは何バイト分表示させるかを指定します。そしてprint命令は、レジスタ、変数、配列の内容を表示します。FMTオプション、DATAオプションはそれぞれdisplayコマンドと同じように使用することができます。
では、いよいよGASを使ってのアセンブラプログラミングを行っていきます。まずは、お馴染の「Hello World!」を表示するプログラムを作成します。以下のプログラムをみてください。
----- ex2.s
.data
hello: .string "Hello World!\n"
.text
.globl main
main:
pushl %ebp
movl %esp, %ebp
pushl $hello
call printf
leave
ret
-----
----- terminal $ gcc -Wall ex2.s -o ex2 $ ./ex2 Hello World! $ -----
みての通り「Hello World!」と表示するプログラムです。といってもやっていることはC言語のプログラムとほとんど同じです。スタックに「Hello World!」という文字列のアドレスをpushしてprintf関数をcallすることで文字列が出力されます。.data部分はデータセクションです。そして.text部分がコードセクションです。データセクションではhelloというラベルを作り、そこから文字列(.string)として「Hello World!」というデータを作っています。よって、helloというラベルが文字列のアドレスとして機能することになります。それをスタックにpushすることによってprintf関数に渡し、文字列を出力しています。そして、最後のleave命令とret命令ですが、leave命令は大抵ret命令の前に置かれます。leave命令が行うことは以下のプログラムと同じです。
----- movl %ebp, %esp popl %ebp -----
つまり、mainラベル以降の最初の2行である「pushl %ebp」「movl %esp, %ebp」と同じことをやっているわけです。そして、espレジスタが指しているアドレス、つまりスタックのトップには呼び出し元のアドレスがあるので、そこにret命令によってジャンプしているということです。
今度は数値をカウントするプログラムを作成します。ここでは、実際にC言語でいうローカル変数のためのメモリ確保をアセンブリ言語でやってみます。
----- ex3.s
.data
count: .string "count: %d\n"
.text
.globl main
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
xor %eax, %eax
count_loop:
movl %eax, -4(%ebp)
pushl %eax
pushl $count
call printf
movl -4(%ebp), %eax
inc %eax
cmpb $5, %al
jnz count_loop
leave
ret
-----
まず最初にお決まりのebpレジスタをスタックにpushして、espレジスタの値をebpに転送しています。そして、espを4減算しています。この状態のメモリ領域は以下のように変化します。

まずは上記のような状態です。%espにある08a00001は呼び出し元のアドレスです。call命令はスタックに呼び出し元のアドレスをpushして関数にジャンプするため、関数実行の直前は必ずスタックトップに呼び出し元のアドレスが格納されていることになります。そして、ebpレジスタをスタックにpushして、espレジスタの値をebpに転送すると以下のように変化します。

ffffc000は前の関数が使っていたebpレジスタの値です。これをスタックに保存した状態で、espレジスタとebpレジスタが同じ値となります。この状態で、今度はespレジスタから4減算します。すると以下のように変化します。

さて、espレジスタはスタックのトップを示します。つまり、push命令が実行されたら、espレジスタが指しているアドレスの次(低位)のアドレスにデータが格納されることになり、pop命令が実行されたら、espレジスタが指しているアドレスのデータがpopされるわけです。もちろんそれぞれの命令によりespレジスタの値も変化します。ここで、もしpush命令が実行されたら、espレジスタの次(低位)のアドレスであるfffffb0cにデータが格納されることになります。つまり以下のようになります。

仮にpushされたデータを10としていますが、何でも構いません。問題はスタックの状態です。以後スタックはfffffb08、fffffb04というようにアドレス低位の方向に伸びていくわけですが、ここで、espを4減算したためにアドレスfffffb10にフリーのメモリ領域ができました。
さて、ex3.sのcount_loopラベル以降をみてください。「movl %eax, -4(%ebp)」という命令があります。これはeaxレジスタの値をebpレジスタの値から4バイト減算したメモリアドレスへ転送することを意味しています。つまりfffffb10にeaxのデータを格納するということです。当然プログラムex3.sの最初のeaxは0ですので、0を格納すると以下のようになります。

スタックはメモリ低位の方向へ伸びていくため、アドレスfffffb10の値がスタックに影響されることはありません。つまり自由に使用できるメモリ領域ができたことになります。さらに、この関数が終了するとleave命令により、現在のebpの値がespとなり、あらかじめスタックにpushしていたffffc000という値が%ebpへpopされるため、自動的に呼び出し前の状態に戻ります。


この時点でespレジスタが指しているアドレスはfffffb18となり、ret命令が実行されます。ret命令が実行されたら、スタックトップの値である呼び出し先アドレス08a00001へ処理がジャンプして、esp、ebpレジスタが共に元の状態に戻ってmain関数終了となります。
さて、プログラムの解説に戻ります。count_loopラベル以降の処理は以下のようになっています。
-----
count_loop:
movl %eax, -4(%ebp)
pushl %eax
pushl $count
call printf
movl -4(%ebp), %eax
inc %eax
cmpb $5, %al
jnz count_loop
-----
このプログラムは、ebxレジスタやecxレジスタなどの他のレジスタを使っても実現できるので、わざわざメモリを使う必要はないのですが、メモリ領域のアクセスの仕方を学ぶためにも、確保したメモリを参照しています。まず最初に、メモリ領域に0を格納して、printf関数を呼び出すために、引数を逆順にスタックへpushしています。関数をcallするとその戻り値がeaxレジスタに格納されるため、eaxレジスタの値は変更されています。よって、メモリからeaxレジスタにデータを転送して、現在のカウント値を復元します。そしてそのデータをインクリメントし、cmp命令によって5であるかどうかを判別します。もし5ならば通過し、5じゃないならばjnz命令によりcount_loopへとジャンプします。このようにして0から4までの数値がカウントされます。では、プログラムを実行してください。
----- terminal $ gcc -Wall ex3.s -o ex3 $ ./ex3 count: 0 count: 1 count: 2 count: 3 count: 4 $ -----
このように表示されます。数値をカウントしながら、標準出力へ表示しています。
ここまでのテキストは「アセンブリ言語の教科書」の222ページ以降の原稿をWEB用にリライトしたものです。
GUI(Graphical User Interface)とは、ユーザに対する情報の表示にグラフィックを多用し、大半の基礎的な操作をマウスなどのポインティングデバイスによって行なうことができるユーザインターフェースのことです。現在では、GUIを利用するための基本的なプログラムをOSが提供することにより、開発負担の軽減などが図られています。Microsoft社のWindowsももちろんGUIを利用するためのAPI(Application Program Interface)が提供されています。
さて、これまではNASMでCUI(Character-based User Interface)プログラムを作成してきました。しかし、せっかくWindowsでプログラミングを行っているので、ここからはGUIプログラムの作成に挑戦することにします。C/C++言語をはじめとした多くの言語で書かれたプログラムも、最終的にはマシン語に変換されているわけですので、アセンブラ(NASM)でGUIプログラムを作成することももちろん可能です。ただし、Windowsプログラミングは一般的にWindowsAPIと呼ばれるインターフェースを利用して行っていきます。よって、アセンブリ言語の知識ももちろんですが、それよりもWindowsAPIの知識が必要となります。WindowsAPIの解説ももちろん行っていきますが、それを詳細に説明していくと本来のアセンブラという内容から大きく外れてしまうので、この章では、とりあえずある程度のWindowsプログラミングの経験がある方を対象とさせていただきます。
Windowsプログラミングを行うためにはNASMの他にリンカが必要になります。「リンカとは?」と思われるかもしれませんが、その前にまずコンパイルの定義を示します。コンパイルとは、アセンブリ言語やC言語によって記述された複数のソースファイルをオブジェクトファイルに変換することです。そしてこれを行うソフトをコンパイラと呼びます。コンパイルすることによって作成されたオブジェクトファイルは、もちろん実行ファイルではないため実行できません。このオブジェクトファイルを実行可能なプログラムの形式に変換することをリンクと呼びます。そして、リンクを行うソフトをリンカと呼びます。
オブジェクトファイルは大きく分けて2種類あります。ひとつは単独のソースファイルに対応する形式でオブジェクトファイルと呼ばれます。このファイルの拡張子は、標準で.objです。もうひとつは、複数のオブジェクトファイルをまとめて一つのファイルにしたもので、ライブラリファイルと呼ばれます。このファイルの拡張子は標準で.libです。大規模なプログラムを作成する場合、複数のソースファイルを作成して、ひとつのプログラムを作ります。そのため、ソフトウェアの開発時は複数のソースファイルをそれぞれがコンパイルしておき、最後にリンカを使って、すべてのオブジェクトファイル、もしくはライブラリファイルを結合して実行ファイルを作成することになります。つまり、プログラム(実行ファイル)を作成する全体的な流れは以下のようになります。

コンパイルは、これまで通りnasmw.exeが行ってくれますが、リンクは残念ながらできません。そのため新しくリンカをダウンロードしなければなりません。NASMと相性のよいリンカにalinkがあります。本書ではこれを使うことにします。以下のページからダウンロードすることができます。

「Downloads」というリンクをクリックするとダウンロードページへ進めます。

いくつかリンクがありますが、ダウンロードすべきなのは2つです。まず「Download ALINK (Win32 version)」というリンクをクリックしてください。これがWindows用のリンカです。「al_al.zip」というファイルを解凍したら、フォルダの中に「ALINK.EXE」というファイルが存在することを確認してください。他にもいろいろとファイルがありますが、これらはサンプルプログラムですので気にしないでください。重要なのは「ALINK.EXE」だけですので、このファイルをnasmw.exeと同じフォルダにコピーしてください。
次に同じページから「Download my Win32 Import library (win32.lib)」をクリックしてください。ダウンロードすると「win32lib.zip」というファイルが作成されますので、これを解凍してください。すると、「WIN32.LIB」というファイルが出来上がります。これも、nasmw.exeやALINK.EXEと同じフォルダへコピーしてください。これで準備完了です。
やはり最初はお約束の「Hello World!」を出力するプログラムを作成することにします。もちろん、出力といってもGUIプログラムなので、コマンドプロンプトの標準出力へ表示するわけではなく、メッセージボックスを使って表示させることにします。では、以下にプログラムを示します。
----- hello32.asm
extern MessageBoxA
section .text
global main
main:
push dword 0
push dword title
push dword text
push dword 0
call MessageBoxA
ret
section .data
title: db 'Message', 0
text: db 'Hello World!', 0
-----
まずはコンパイルしてください。コンパイルするにはnasmw.exeとalink.exeを以下のように実行します。
----- コマンドプロンプト C:\nasm>nasmw -fwin32 hello32.asm C:\nasm>alink -oPE hello32 win32.lib -entry main ALINK v1.6 (C) Copyright 1998-9 Anthony A.J. Williams. All Rights Reserved Loading file hello32.obj Loading file win32.lib matched Externs matched ComDefs Generating PE file hello32.exe C:\nasm> -----

実行すると「Hello World!」という文字列がメッセージボックスに表示されます。コマンドプロンプト上で作成していたプログラムとは性質が違っています。まず、MessageBoxAはWindowsのAPI(Application Programming Interface)のひとつであり、これを呼び出すことによってメッセージボックスを表示しています。
Windowsプログラミングにおいて、APIを呼び出すためには、そのAPIの引数をスタックに逆順に詰め込んでからAPI自身をcallすることによって実行させます。つまりhello32.asmの場合は、push命令によってスタックにデータを格納し、MessageBoxAを呼び出しています。MSDN(http://msdn.microsoft.com/)では、MessageBox関数は次のように定義されています。ちなみにMessageBox関数はWindows内部でASCIIコード用のMessageBoxAと、UNICODE用のMessageBoxWに分けられています。
----- MessageBox
int MessageBox(HWND hWnd, // ウィンドウハンドル
LPCTSTR lpText, // 本文として表示するテキスト
LPCTSTR lpCaption, // タイトルに表示するテキスト
UINT uType // メッセージボックスのタイプ
);
-----
MessageBox関数の定義とhello32.asmのスタックへ格納した順番を比べてください。
----- push dword 0 // メッセージボックスのタイプ push dword title // タイトルに表示するテキスト push dword text // 本文として表示するテキスト push dword 0 // ウィンドウハンドル -----
タイトルと本文をみることによって定義されている順番とは逆にスタックに詰め込まれているのが分かります。Windows上でのアセンブラプログラミングはこのようにして作成していくことになります。
ここまでのテキストは「アセンブリ言語の教科書」の320ページ以降の原稿をWEB用にリライトしたものです。