MASM32によるアセンブラ入門:パート2お読みになる前に本稿の内容は、読者の環境に MASM32がインストールされていることを前提にしています。まだインストールしていない場合は、http://www.masm32.com/から入手してください。 はじめにシリーズ第1回では、Microsoft Assemblerを使用してアセンブラファイルをコンパイルできるようVisual Studioをセットアップする方法を説明しました。第2回では、アセンブラという言語そのものと、アセンブラに含まれているいくつかの命令について解説したいと思います。 変数アセンブラには変数はありません。少なくとも、C++で使われているような意味での変数はありません。アセンブラでは、レジスタとメモリアドレスを使用します。つまり、アセンブラを書くということは、プロセッサと同じ言語を話すということです。この点はぜひ頭に入れておいてください。 たとえば、プロセッサは では、レジスタとは一体何なのでしょうか。そして、どうやったらメモリにアクセスできるのでしょうか。 レジスタレジスタは変数に似ていますが、プロセッサのみが使用するという点が異なります。先ほど「高等言語のような意味での変数は存在しない」と言ったのはそういうわけです。プロセッサチップ上には一定数のレジスタのみが存在します。レジスタは「プロセッサチップ上に物理的に存在し、プロセッサ用にハードコーディングされた変数である」と考えるとわかりやすいでしょう。 これらのレジスタは、決まったサイズの数値を表すことができます。このサイズはプロセッサのビット数によって決まります。つまり、32ビットプロセッサの場合は32ビットになります。C++用語では、この一定サイズの数値のことを この数値には符号は付いていません。負数を表さなければならないときは、「0x100000000+負数」で表現します。したがって、-1は0xFFFFFFFF、-2は0xFFFFFFFEで表現されます(以下同様です)。 最新のIntelプロセッサには非常に多くのレジスタがありますが、アプリケーション内で使用するレジスタは次の6つだけです。
レジスタ
![]() これらの名前は、プロセッサの発展経緯に拠ります。レジスタ名の先頭の「e」は「拡張(extended)レジスタ」を表しています。つまり、16ビットプロセッサから32ビットプロセッサに移行したときの、それぞれのレジスタの32ビット版という意味です。16ビットプロセッサで使用できるレジスタは mov(移動)命令 まず、最も単純な mov eax, 100 この命令では、100という値を mov (destination), (source)
mov al, bl ; move the lower byte of ebx into the lower byte
; of eax
mov al, 0ffh ; move 0xFF into the lower byte of eax
mov ah, 0ffh ; move 0xFF into the high byte of the low word
; (2-bytes) of eax
mov ax, 0ffffh ; move 0xFFFF into the low word of eax
mov eax, 0ffffh ; move 0xFFFF into eax
メモリの内容をレジスタに移動したり、その逆を行ったりすることもできます。その場合は、「メモリの内容」を表すために大カッコを使用します。移動されるバイト数はレジスタ名によって決まります。
mov al, [esi] ; move the byte contained in the memory address
; in register esi into the lower byte of eax
mov [edi], bl ; move the byte value in the lowest byte of ebx
; into the memory address in register edi
mov cx, [esi] ; move the word (2-byte) value contained in the
; memory address of register esi into the lower
; word of ecx
mov [edi], edx ; move the dword (4-byte) value contained in edx
; into the memory address contained in register edi
また、大カッコの演算子を使用するときにオフセットを含めることもできます。
mov al, [esi + 3] ; move the byte contained in the memory address
; in register esi + 3 into the lower byte of eax
mov [edi + 2], dx ; move the lower word (2-bytes) contained in
; edx into the memory address contained in the
; register edi + 2
関数関数は次の形式で宣言します。 TestProc proc dwValue1:DWORD, wValue2:WORD, bValue3:BYTE ret TestProc endp このコード例は空の関数ですが、基本的な形式はどの関数でも同じです。まず関数名を指定し、その後に 関数の終わりには、関数名と この関数をC++から呼び出す場合は、関数から戻る前に、レジスタ 関数の戻り値は TestProc proc dwValue1:DWORD, dwValue2:DWORD mov eax, dwValue1 add eax, dwValue2 ret TestProc endp この関数では、 C++からアセンブラ関数にアクセスするには、同じ名前とパラメータを持つ関数を宣言する必要があります。C++でのパラメータのサイズは、アセンブラコード内で定義したパラメータのサイズに等しくなければなりません。さらに、 extern "C" unsigned int __stdcall TestProc(unsigned int dwValue1, unsigned int dwValue2); ポインタを渡す場合は、そのポインタをアセンブラ関数の アセンブラ関数を静的DLLからエクスポートする設計にした場合は、C++内で関数を宣言する必要はありません。DLLの.defファイルに関数名を含めるだけで、この方法で宣言した他のC++関数と同じように使用することができます。 スタックとpush命令、pop命令 プロセッサには「スタック」と呼ばれる機構があります。 スタックは、使用可能なレジスタの数の少なさを補うために用意された機構です。スタックを使用すると、レジスタの内容を効率的かつ迅速な方法で保存/取得することができます。 簡単に言えば、スタックはファーストイン-ラストアウト方式の値キューです。
TestFunction proc
mov eax, 100
push eax ; Stack now contains { 100 }
mov eax, 200
push eax ; Stack now contains { 200, 100 }
mov eax, 300
push eax ; Stack now contains ( 300, 200, 100 }
pop eax ; eax = 300, stack = { 200, 100 }
pop eax ; eax = 200, stack = { 100 }
pop eax ; eax = 100, stack = { }
ret
TestFunction endp
スタックの一般的な用途は、関数を終了する前にレジスタebx、esi、ediの値を復元することです。次に例を示します。 TestFunction proc push ebx push esi push edi ; code goes in here pop edi pop esi pop ebx ret TestFunction endp 本当ならば、使用する予定のレジスタの値だけを保存しておけばよいのですが、ここでは ここで注目してほしいのは、関数を終了するときのスタックの状態が、関数に入った時点のものと同じでなければならないということです。言い換えると、 フラグとフラグに関連する命令 フラグとは、プロセッサ内で たとえば、減分を行う TestFunction proc mov eax, 2 dec eax ; eax == 1 dec eax ; eax == 0, zero flag is set ret TestFunction endp その他に、特定のフラグの状態に応じて動作が変わる処理もあります。そうした処理の一例がジャンプです。最も基本的な これらの命令とゼロフラグに関する知識を使用すれば、ループを記述することができます。 LoopFunction proc xor eax, eax ; efficient way of saying eax = 0 mov ecx, 5 ; ecx is the register generally used for counters LoopStart: ; this is a label, used for labelling code positions inc eax dec ecx jnz LoopStart ; eax now equals 5 ret LoopFunction endp まとめ本稿では、アセンブラの基本的な命令をいくつか紹介し、その使い方を説明しました。また、レジスタとは何かと、アセンブラ内に存在するレジスタについても解説しました。さらに、アセンブラでパラメータ付きの関数を定義する方法と、アセンブラ関数をC++内で宣言する方法を示しました。 次の回では、算術演算と、アセンブラコードの開発を容易にするMASMのマクロについて説明したいと思います。 著者紹介darwen(darwen)
Windows 3.11のVisual C++/MFC 1.5の時代からWindowsプログラミングに取り組み始める。現在は、多言語展開を図っているとあるソフトウェア会社のベテラン開発者としてあらゆるプロジェクトに従事。暇さえあればプログラミングをしているのではないかという噂あり。
|