命令
ここからは、x64 の汎用命令の説明を行っていきます。
命令のオペランドは、主に左側が格納先 (変更先)、右側がソース値となります。
命令の一覧は、汎用命令 (1) / 汎用命令 (2) / 汎用命令 (3) の方でご覧ください。
命令のオペランドは、主に左側が格納先 (変更先)、右側がソース値となります。
命令の一覧は、汎用命令 (1) / 汎用命令 (2) / 汎用命令 (3) の方でご覧ください。
記号の意味
オペランド | |
reg<N> | ModRM.reg でレジスタを指定します。 レジスタのサイズは N bit。 |
---|---|
reg/mem<N> | ModRM.r/m でレジスタまたはメモリを指定します。 |
imm<N> | 即値 (N bit) |
エンコーディング | |
/r | ModRM バイトで、2つのオペランドを指定します。 |
+rb +rw +rd +rq | 左にある値に、オペランドレジスタを指定するための値を追加し、それを1バイトのオペコードとします。 b, w, d, q は、それぞれ 8bit, 16bit, 32bit, 64bit のオペランドサイズを意味します。 [REX なし] 0 = rAX, 1 = rCX, 2 = rDX, 3 = rBX, 4 = rSP/AH, 5 = rBP/CH, 6 = rSI/DH, 7 = rDI/BH [REX.B = 0 (8bit)] 4 = SPL, 5 = BPL, 6 = SIL, 7 = DIL [REX.B = 1] 0〜7 = r8〜r15 左の値が B0 なら、実際のオペコードは B0〜B7 の範囲になります。 |
/<数字> | ModRM.r/m で、1つのオペランドのみを指定します。 また、数字は、ModRM.reg の値になります。これはオペコード拡張として使用されます。 |
ib iw id iq | 即値オペランド値 (8bit, 16bit, 32bit, 64bit) |
cb cw cd cp | オペコードの後に、ジャンプ先の相対オフセット値、または、新しいコードセグメントレジスタ値を指定します。 cb = 8bit、cw = 16bit、cd = 32bit、cp = 6 byte (16:32bit) |
プレフィックス
エンコーディングには、実際には、オペランドのサイズ指定などによって、適切なプレフィックスが付きます。
ただし、一部の命令では、各プレフィックスや ModRM のフィールド値が、オペコードの拡張として使用される場合があります。
(オペコードのバイト値が同じでも、それらの値によって、別の命令に置き換わる)
その場合は、明示的に以下が記述されています。
ただし、一部の命令では、各プレフィックスや ModRM のフィールド値が、オペコードの拡張として使用される場合があります。
(オペコードのバイト値が同じでも、それらの値によって、別の命令に置き換わる)
その場合は、明示的に以下が記述されています。
- オペランドサイズ・プレフィックス (66h)
- REP/REPE/REPZ/REPNE/REPNZ プレフィックス (F2h, F3h)
- ModRM.reg (/<数字> による表記)
LOCK プレフィックスについて
マルチプロセッサの場合、同じ場所のメモリアクセスが同時に起こらないようにする必要があります。
LOCK プレフィックスの使用がサポートされている命令で、LOCK プレフィックスが付いているか、暗黙的にロックが行われる命令の場合は、同時に同じ場所への読み書きのアクセスが行われることはありません。
LOCK がサポートされている命令はごく一部なので、通常のアプリケーションでは、OS の機能を使って、スレッドなどの処理を行う必要があります。
LOCK プレフィックスの使用がサポートされている命令で、LOCK プレフィックスが付いているか、暗黙的にロックが行われる命令の場合は、同時に同じ場所への読み書きのアクセスが行われることはありません。
LOCK がサポートされている命令はごく一部なので、通常のアプリケーションでは、OS の機能を使って、スレッドなどの処理を行う必要があります。
CPUID について
CPUID は、プロセッサに関する情報を取得するための命令です。
一部の命令は、プロセッサによって実装されている場合と、実装されていない場合があるので、そういった命令を使う場合は、事前に CPUID を使って、その命令がサポートされているかどうかを確認する必要があります。
CPUID は、実行する前に、EAX レジスタに、取得したい情報の機能番号を指定します。
EAX, EBX, ECX, EDX レジスタに結果の値が返ってくるので、その値から判断します。
詳細については、のちほど説明します。
一部の命令は、プロセッサによって実装されている場合と、実装されていない場合があるので、そういった命令を使う場合は、事前に CPUID を使って、その命令がサポートされているかどうかを確認する必要があります。
CPUID は、実行する前に、EAX レジスタに、取得したい情報の機能番号を指定します。
EAX, EBX, ECX, EDX レジスタに結果の値が返ってくるので、その値から判断します。
詳細については、のちほど説明します。
MOV 命令と XOR 命令
レジスタに 0 の値を入れたいことは、多々あります。
MOV 命令を使う場合は、mov eax, 0 というように、レジスタに即値を代入する形になりますが、レジスタの値を 0 にしたいだけなら、XOR 命令を使った方が効率的です。
XOR 命令で、2つのオペランドを同じレジスタに指定すると、結果的にレジスタの値は 0 になります。
xor eax, eax というように使います。
論理 XOR の場合、「0 XOR 0 = 0」「1 XOR 1 = 0」となるので、同じ値同士で XOR をすると、常に結果は 0 となります。
さらに、64bit モードの場合は、32bit レジスタが宛先として指定されている場合、レジスタの上位 32bit が 0 になるので、xor rax, rax とせずに、xor eax, eax のように、32bit レジスタを XOR することで、64bit レジスタの値を 0 にすることができます。
エンコーディングの値を見ればわかるように、xor eax, eax が一番サイズが小さくなっています。
なお、mov rax, 0 は、本来であれば mov reg64, imm64 の形式になるはずですが、NASM の最適化により、オペランド形式が置き換えられているようです。
-O0 オプションを付けて、最適化をなしにすると、結果は「48 B8 01 00 00 00 00 00 00」となり、本来の形式によるエンコーディングが行われました。
MOV 命令を使う場合は、mov eax, 0 というように、レジスタに即値を代入する形になりますが、レジスタの値を 0 にしたいだけなら、XOR 命令を使った方が効率的です。
XOR 命令で、2つのオペランドを同じレジスタに指定すると、結果的にレジスタの値は 0 になります。
xor eax, eax というように使います。
論理 XOR の場合、「0 XOR 0 = 0」「1 XOR 1 = 0」となるので、同じ値同士で XOR をすると、常に結果は 0 となります。
さらに、64bit モードの場合は、32bit レジスタが宛先として指定されている場合、レジスタの上位 32bit が 0 になるので、xor rax, rax とせずに、xor eax, eax のように、32bit レジスタを XOR することで、64bit レジスタの値を 0 にすることができます。
エンコーディング
mov rax, 0 ; B8 00 00 00 00 (mov reg32, imm32) xor rax, rax ; 48 31 C0 (REX) xor eax, eax ; 31 C0
エンコーディングの値を見ればわかるように、xor eax, eax が一番サイズが小さくなっています。
なお、mov rax, 0 は、本来であれば mov reg64, imm64 の形式になるはずですが、NASM の最適化により、オペランド形式が置き換えられているようです。
-O0 オプションを付けて、最適化をなしにすると、結果は「48 B8 01 00 00 00 00 00 00」となり、本来の形式によるエンコーディングが行われました。
R8〜R15
ただし、R8〜R15 レジスタの場合は、常に REX プレフィックスが必要になるため、32bit/64bit どちらのサイズで XOR しても、サイズは変わりません。XOR reg/mem32, reg32 | 31 /r XOR reg/mem64, reg64 | 31 /r xor r8, r8 ; 4D 31 C0 (REX.W/R/B) xor r8d, r8d ; 45 31 C0 (REX.R/B)
LEA 命令
LEA reg16, mem | 8D /r LEA reg32, mem | 8D /r LEA reg64, mem | 8D /r
LEA 命令を使うと、指定されたメモリ位置の絶対アドレスの値を、レジスタに格納することができます。
ソースオペランドでは、メモリのアドレス指定を行います。
MOV 命令の場合、この形式では、メモリ位置の値を読み込んで、レジスタに格納することになりますが、LEA 命令では、メモリ位置の値を読み込まずに、そのアドレスの値をレジスタに格納します。
lea rax, [label] lea rax, [rbx+rcx*4]
この命令は、アドレス指定の計算を行った上で、結果のアドレス値を取得したい場合や、RIP 相対アドレスから絶対アドレスを取得したい場合などに使います。
相対アドレスで参照されているコードの場合、メモリの絶対アドレスは、実際にコードが実行されている状況でないとわからないので、LEA 命令を使うことで、実行時に現在の値を取得することができるようになります。
lea rdx, [rel data] ; data のラベルの絶対アドレスを取得 (実行時) mov eax, [rdx] ; rdx のアドレス位置の値を読み込み data: dd 1234
NOP と XCHG
NOP 命令は、何もしない命令です。
XCHG 命令は、レジスタとレジスタ、またはレジスタとメモリの値を入れ替えます。
XCHG で AX, EAX, RAX レジスタを入れ替える場合、オペコードは 90h となるため、NOP 命令と同じオペコードになります。
つまり、NOP は XCHG rAX, rAX と同じです。
XCHG 命令で、同じレジスタの値を入れ替えたとしても、レジスタの値は変わらないので、何もしない動作と同じになります。
ただし、NASM で XCHG EAX, EAX を直接記述した場合は、オペコードは 90h ではなく、XCHG reg32, reg/mem32 の形式となります。
オペコードが 90h ではないので、これは NOP 命令と同じではありません。
XCHG 命令は、レジスタとレジスタ、またはレジスタとメモリの値を入れ替えます。
NOP | 90 XCHG AX, reg16 | 90 +rw XCHG EAX, reg32 | 90 +rd XCHG RAX, reg64 | 90 +rq XCHG reg16, AX | 90 +rw XCHG reg32, EAX | 90 +rd XCHG reg64, RAX | 90 +rq XCHG reg8, reg/mem8 | 86 /r XCHG reg16, reg/mem16 | 87 /r XCHG reg32, reg/mem32 | 87 /r XCHG reg64, reg/mem64 | 87 /r XCHG reg/mem8, reg8 | 86 /r XCHG reg/mem16, reg16 | 87 /r XCHG reg/mem32, reg32 | 87 /r XCHG reg/mem64, reg64 | 87 /r
XCHG で AX, EAX, RAX レジスタを入れ替える場合、オペコードは 90h となるため、NOP 命令と同じオペコードになります。
つまり、NOP は XCHG rAX, rAX と同じです。
XCHG 命令で、同じレジスタの値を入れ替えたとしても、レジスタの値は変わらないので、何もしない動作と同じになります。
nop ; 90 xchg ax, ax ; 66 90 (66h prefix) xchg eax, eax ; 87 C0 (XCHG reg32, reg/mem32) xchg rax, rax ; 48 90 (REX prefix)
ただし、NASM で XCHG EAX, EAX を直接記述した場合は、オペコードは 90h ではなく、XCHG reg32, reg/mem32 の形式となります。
オペコードが 90h ではないので、これは NOP 命令と同じではありません。
NOP と XCHG EAX, EAX の違い
NOP と XCHG EAX, EAX の違いは、レジスタの上位 32bit が 0 になるか、ならないかの違いです。
64bit モードでは、宛先に 32bit レジスタが指定されている場合、そのレジスタの上位 32bit は常に 0 になります。
そのため、XCHG EAX, EAX の場合、RAX の上位 32bit は 0 になり、NOP 命令では、0 になりません。
もしも NOP 命令が、XCHG EAX, EAX と全く同じ動作をするのであれば、RAX レジスタの上位 32bit は 0 になってしまい、値が変わってしまうので、何もしない動作ではなくなってしまいます。
そのため、オペコードが 90h で、EAX レジスタの値を入れ替える場合は、特殊なケースとして、レジスタの上位 32bit はクリアされません。
(AX, RAX を入れ替える場合は、そもそも結果の値は変わりません。ただし、各プレフィックスは付きます)
64bit モードでは、宛先に 32bit レジスタが指定されている場合、そのレジスタの上位 32bit は常に 0 になります。
そのため、XCHG EAX, EAX の場合、RAX の上位 32bit は 0 になり、NOP 命令では、0 になりません。
もしも NOP 命令が、XCHG EAX, EAX と全く同じ動作をするのであれば、RAX レジスタの上位 32bit は 0 になってしまい、値が変わってしまうので、何もしない動作ではなくなってしまいます。
そのため、オペコードが 90h で、EAX レジスタの値を入れ替える場合は、特殊なケースとして、レジスタの上位 32bit はクリアされません。
(AX, RAX を入れ替える場合は、そもそも結果の値は変わりません。ただし、各プレフィックスは付きます)