アセンブラ構文
ここでは、アセンブラ構文の要素について、説明していきます。
文
アセンブリ文の構文は次のとおりです:
- Label: Prefix Opcode Operand1, Operand2
ここで、Label はラベル、Prefix はアセンブリ プレフィックス オペコード(オペレーション コード)、Opcode はアセンブリ命令オペコードまたは指令、Operand はアセンブリ式です。 Label と Prefix は省略可能です。 オペコードの中には、オペランドを 1 つしか取らないものもあれば、まったく取らないものもあります。
コメントはアセンブリ言語文間には挿入できますが、アセンブリ言語文内には挿入できません。 例:
MOV AX,1 {Initial value} { OK }
MOV CX,100 {Count} { OK }
MOV {Initial value} AX,1; { Error! }
MOV CX, {Count} 100 { Error! }
ラベル
組み込みアセンブリ言語文でのラベルの使い方は Delphi 言語の場合と同様で、ステートメントの前にラベルとコロンを付けます。 ラベルの長さには制限はありません。 Delphi の場合と同様に、ラベルは、asm 文が含まれているブロック内の label 宣言部で宣言しておく必要があります。 ただし、ローカル ラベルにはこの規則は当てはまりません。
ローカル ラベルはアット マーク(@)で始まるラベルです。 アット マークの後に、英字、数字、アンダースコア、アット マークのいずれかが 1 つ以上含まれます。 ローカル ラベルの使用は asm 文に限られます。ローカル ラベルのスコープは、予約語 asm から、ラベルが含まれている asm 文の終わりまでです。 ローカル ラベルは、宣言する必要はありません。
命令オペコード
組み込みアセンブラでは、アプリケーションで一般的な用途に使用できるオペコードとして Intel が規定しているものをすべてサポートしています。 ただし、オペレーティング システムの特権命令はサポートされない場合があることに注意してください。 具体的には、以下のプロセッサ ファミリの命令がサポートされています。
- IA-32
- Pentium ファミリ
- Pentium Pro および Pentium II
- Pentium III
- Pentium 4
- Intel 64
さらに、以下の拡張命令セットもサポートされています。
- Intel SSE(SSE4.2 含む)
- AMD 3DNow! (AMD K6 以降)
- AMD Enhanced 3DNow! (AMD Athlon 以降)
各命令の詳細は、ご使用のマイクロプロセッサのマニュアルを参照してください。
自動ジャンプ サイズ決定
特に指示しない限り、組み込みアセンブラでは、最も短い(したがって最も効率的な)ジャンプ命令形式を自動的に選択することで、ジャンプ命令を最適化します。 ターゲットがラベルの場合(手続きや関数でない場合)、この自動ジャンプ サイズ決定は無条件ジャンプ命令(JMP)とすべての条件ジャンプ命令に適用されます。
無条件ジャンプ命令(JMP)については、ターゲット ラベルまでの距離が -128 ~ 127 バイトまでの場合、組み込みアセンブラは short ジャンプ(1 バイトのオペコードの後に 1 バイトの変位が続く)を生成します。 それ以外の場合は、near ジャンプ(1 バイトのオペコードの後に 2 バイトの変位が続く)を生成します。
条件ジャンプ命令については、ターゲット ラベルまでの距離が -128 ~ 127 バイトまでの場合、short ジャンプ(1 バイトのオペコードの後に 1 バイトの変位が続く)が生成されます。 そうでなければ、埋め込みアセンブラが、ターゲット ラベルへの near ジャンプを生成します。
手続きや関数のエントリ ポイントへのジャンプは常に near 形式です。
指令
組み込みアセンブラでは、DB(バイト定義)、DW(ワード定義)、DD(ダブル ワード定義)の 3 つの定義アセンブラ指令(疑似命令)をサポートしています。 各指令は、指令の後にコンマで区切って指定されたオペランドに対応するデータを生成します。
指令 | 説明 |
---|---|
DB |
バイト定義: バイト列を生成します。 各オペランドには、128 ~ 255 の範囲の値を持つ定数式か、任意の長さの文字列を指定できます。 定数式の場合は 1 バイトのコードが生成され、文字列の場合は各文字の ASCII コードに対応する値を持つバイトの列が生成されます。 |
DW |
ワード定義: ワード列を生成します。 各オペランドには、32,768 ~ 65,535 の範囲の値を持つ定数式か、アドレス式を指定できます。 アドレス式の場合、組み込みアセンブラは near ポインタ(アドレスのオフセット部が含まれているワード)を生成します。 |
DD |
ダブル ワード定義: ダブル ワード列を生成します。 各オペランドには、2,147,483,648 ~ 4,294,967,295 の範囲の値を持つ定数式か、アドレス式を指定できます。 アドレス式の場合、組み込みアセンブラは far ポインタ(アドレスのオフセット部が含まれているワードの後に、アドレスのセグメント部が含まれているワードが続くもの)を生成します。 |
DQ |
クワッド ワード定義: Int64 値のクワッド ワード(8 バイト)を定義します。 |
DB、DW、DD の各指令によって生成されるデータは、他の組み込みアセンブリ言語文で生成されたコードと同様に、常にコード セグメントに格納されます。 未初期化または初期化済みのデータをデータ セグメントに生成するには、Delphi の var 宣言または const 宣言を使用しなければなりません。
DB、DW、DD の各指令の例を以下に示します:
asm
DB FFH { One byte }
DB 0.99 { Two bytes }
DB 'A' { Ord('A') }
DB 'Hello world...',0DH,0AH { String followed by CR/LF }
DB 12,'string' { {{Delphi}} style string }
DW 0FFFFH { One word }
DW 0,9999 { Two words }
DW 'A' { Same as DB 'A',0 }
DW 'BA' { Same as DB 'A','B' }
DW MyVar { Offset of MyVar }
DW MyProc { Offset of MyProc }
DD 0FFFFFFFFH { One double-word }
DD 0,999999999 { Two double-words }
DD 'A' { Same as DB 'A',0,0,0 }
DD 'DCBA' { Same as DB 'A','B','C','D' }
DD MyVar { Pointer to MyVar }
DD MyProc { Pointer to MyProc }
end;
識別子の後ろに DB、DW、DD のいずれかの指令がある場合、その指令の位置にそれぞれバイト、ワード、ダブル ワードのサイズの変数が宣言されます。 たとえば、アセンブラでは次のような宣言が可能です:
ByteVar DB ?
WordVar DW ?
IntVar DD ?
// …
MOV AL,ByteVar
MOV BX,WordVar
MOV ECX,IntVar
組み込みアセンブラでは、このような変数宣言をサポートしていません。 インライン アセンブリ言語文で定義可能なシンボルの種類はラベルだけです。 変数はすべて Delphi 構文を使って宣言する必要があります。したがって、上記のコードは次のように記述します。
var
ByteVar: Byte;
WordVar: Word;
IntVar: Integer;
// …
asm
MOV AL,ByteVar
MOV BX,WordVar
MOV ECX,IntVar
end;
変位のサイズを指定するために、以下のように SMALL や LARGE を使用できます。
MOV EAX, [LARGE $1234]
この命令は、32 ビット変位($00001234)の "通常" の移動を生成しますが、
MOV EAX, [SMALL $1234]
この命令は、アドレス サイズ オーバーライド プレフィックスと 16 ビット変位($1234)を持つ移動を生成します。
SMALL は、領域を節約するために使用できます。 以下の例では、アドレス サイズ オーバーライドと 2 バイト アドレス(合計 3 バイト)が生成されます。
MOV EAX, [SMALL 123]
これに対して、
MOV EAX, [123]
この例では、アドレス サイズ オーバーライドなしで 4 バイト アドレス(合計 4 バイト)が生成されます。
さらに、VMTOFFSET と DMTINDEX の 2 つの追加指令を使用すれば、アセンブリ言語コードから仮想メソッドと動的メソッドにアクセスすることができます。
VMTOFFSET は、仮想メソッド引数の、仮想メソッド テーブル(VMT)の先頭からのテーブル エントリ オフセットをバイト単位で取得します。 この指令では、完全修飾クラス名付きのメソッド名(たとえば TExample.VirtualMethod)や、インターフェイス名付きのインターフェイス メソッド名がパラメータとして必要です。
DMTINDEX は、渡された動的メソッドの動的メソッド テーブル インデックスを取得します。 この指令でも、完全修飾クラス名付きのメソッド名がパラメータとして必要です(例、TExample.DynamicMethod)。 動的メソッドを呼び出すには、DMTINDEX で取得した値が入っている (E)SI レジスタで、System.@CallDynaInst を呼び出します。
メモ: message 指令の付いたメソッドは動的メソッドとして実装され、これも DMTINDEX を使って呼び出すことができます。 例:
TMyClass = class
procedure x; message MYMESSAGE;
end;
以下の例では、DMTINDEX と VMTOFFSET を両方使用して、動的メソッドと仮想メソッドにアクセスします。
program Project2;
type
TExample = class
procedure DynamicMethod; dynamic;
procedure VirtualMethod; virtual;
end;
procedure TExample.DynamicMethod;
begin
end;
procedure TExample.VirtualMethod;
begin
end;
procedure CallDynamicMethod(e: TExample);
asm
// Save ESI register
PUSH ESI
// Instance pointer needs to be in EAX
MOV EAX, e
// DMT entry index needs to be in (E)SI
MOV ESI, DMTINDEX TExample.DynamicMethod
// Now call the method
CALL System.@CallDynaInst
// Restore ESI register
POP ESI
end;
procedure CallVirtualMethod(e: TExample);
asm
// Instance pointer needs to be in EAX
MOV EAX, e
// Retrieve VMT table entry
MOV EDX, [EAX]
// Now call the method at offset VMTOFFSET
CALL DWORD PTR [EDX + VMTOFFSET TExample.VirtualMethod]
end;
var
e: TExample;
begin
e := TExample.Create;
try
CallDynamicMethod(e);
CallVirtualMethod(e);
finally
e.Free;
end;
end.
演算子
インライン アセンブリ言語のオペランドは、定数、レジスタ、シンボル、演算子から成る式です。
オペランド内では、以下の予約語は既に意味が定義されています。
組み込みアセンブリ言語の予約語
- CPU レジスタ
カテゴリ |
識別子 |
---|---|
8 ビット CPU レジスタ |
AH、AL、BH、BL、CH、CL、DH、DL(汎用レジスタ) |
16 ビット CPU レジスタ |
AX、BX、CX、DX(汎用レジスタ)、DI、SI、SP、BP(インデックス レジスタ)、CS、DS、SS、ES(セグメント レジスタ)、IP(命令ポインタ) |
32 ビット CPU レジスタ |
EAX、EBX、ECX、EDX(汎用レジスタ)、EDI、ESI、ESP、EBP(インデックス レジスタ)、FS、GS(セグメント レジスタ)、EIP |
FPU |
ST(0)、...、ST(7) |
MMX FPU レジスタ |
mm0、...、mm7 |
XMM レジスタ |
xmm0、...、xmm7(x64 の場合は、xmm15 までの 16 個) |
Intel 64 レジスタ |
RAX、RBX、... |
データと演算子
カテゴリ |
識別子 |
---|---|
データ |
BYTE、WORD、DWORD、QWORD、TBYTE |
演算子 |
NOT、AND、OR、XOR、SHL、SHR、MOD、LOW、HIGH、OFFSET、PTR、TYPE |
VMTOFFSET、DMTINDEX | |
SMALL、LARGE |
予約語は常にユーザー定義の識別子よりも優先されます。 例:
var
Ch: Char;
// …
asm
MOV CH, 1
end;
このコードでは、Ch 変数ではなく CH レジスタに 1 がロードされます。 組み込みアセンブリ言語の予約語と同じ名前のユーザー定義シンボルにアクセスするには、次のようにオーバーライド演算子(&)を使用する必要があります。
MOV&Ch, 1
組み込みアセンブリ言語の予約語と同じ名前のユーザー定義識別子は、できるだけ使用しないようにしてください。