ジャンプ
> 汎用命令 (3)
ジャンプ命令には、無条件ジャンプ (JMP) と、条件ジャンプ (Jcc) があります。
無条件ジャンプは、相対位置または絶対位置を指定して、移動します。
条件ジャンプは、ステータスフラグの状態によって、ジャンプするか、後続の命令をそのまま実行します。
条件ジャンプは、相対位置での移動しか行えません。
ジャンプは、rIP の値を変更することで、次の命令の実行位置を変更します。
ジャンプ命令には、無条件ジャンプ (JMP) と、条件ジャンプ (Jcc) があります。
無条件ジャンプは、相対位置または絶対位置を指定して、移動します。
条件ジャンプは、ステータスフラグの状態によって、ジャンプするか、後続の命令をそのまま実行します。
条件ジャンプは、相対位置での移動しか行えません。
ジャンプは、rIP の値を変更することで、次の命令の実行位置を変更します。
サンプルコード1
以下は、JMP 命令のエンコードを確認するためだけのコードです。実際には実行しないでください。
nasm の -l オプションで、出力とソースコードを並べた形式のテキストを出力できます。
※ジャンプ命令で、移動先にラベルを指定した場合は、常に相対位置で出力されます。
nasm の -l オプションで、出力とソースコードを並べた形式のテキストを出力できます。
section .text testfunc: nop ; 90 label1: jmp testfunc ; EB FD jmp label2 ; EB 0A times 10 db 0 ; 0(1byte) x 10 label2: ret
$ nasm -f elf64 test.asm -l test.txt
解説
JMP rel8off | EB cb JMP rel32off | E9 cd
- jmp testfunc (EB FD)
testfunc の位置にジャンプします。
EB はオペコード、移動先の相対オフセット値は (0xFD = -3) です。
次の命令位置から見た時の testfunc の位置は、-3 となります。
- jmp label2 (EB 0A)
次の命令位置から見た時の label2 の位置は、間に 0 (1byte) が 10個出力されているので、+10 のオフセット位置になります。
※ジャンプ命令で、移動先にラベルを指定した場合は、常に相対位置で出力されます。
サンプルコード2
以下のサンプルでは、LEA 命令で移動先の絶対アドレスを取得し、一度メモリに書き込んだ後、JMP 命令で、そのメモリ位置の値を参照し、絶対位置にジャンプします。
上記の場合、jmp rax としても、結果は同じです。
※このコードの場合、default rel により、RIP 相対アドレスでアドレス指定されるため、[<ラベル>] は、[RIP + offset] に置き換わります。
default rel global testfunc section .data addr: dq 0 section .text testfunc: lea rax, [label1] ; label1 の絶対アドレスを RAX にセット mov [addr], rax ; addr のメモリ位置に値を書き込む jmp [addr] ; FF 25 BC 2E 00 00 nop label1: ret
JMP reg/mem64 | FF /4
上記の場合、jmp rax としても、結果は同じです。
※このコードの場合、default rel により、RIP 相対アドレスでアドレス指定されるため、[<ラベル>] は、[RIP + offset] に置き換わります。
解説
まず、LEA 命令で、label1 ([RIP + 0x0E]) の絶対アドレスを、RAX レジスタに格納します。
RAX に label1 の絶対位置を取得したら、その値を、addr のメモリ位置に書き込みます (64bit)。
その後、JMP 命令で、addr のメモリ位置から、ジャンプ先の絶対アドレス位置を読み込み、ジャンプします。
jmp [addr] は、実際には、jmp [RIP + 0x2EBC] となります。
(データセクションのメモリ位置を指定しているため、オフセット値は、その位置を示す値になります)
JMP 命令は、レジスタまたはメモリで移動先が指定された場合、絶対アドレス位置として、その値を rIP にセットします。
この場合、addr のメモリ位置から 64bit の値を読み込み、その絶対アドレスの位置へ移動します。
「mov rax, label1」では、絶対アドレスを格納することはできません。
この場合、RAX には、.text セクション先頭からの label1 の相対位置が、値として入ります。
ラベルの絶対位置を取得したい場合は、LEA 命令でメモリのアドレス指定をして、そのメモリ位置のアドレスを、値として格納します。
この場合、RAX には、.text セクション先頭からの label1 の相対位置が、値として入ります。
ラベルの絶対位置を取得したい場合は、LEA 命令でメモリのアドレス指定をして、そのメモリ位置のアドレスを、値として格納します。
RAX に label1 の絶対位置を取得したら、その値を、addr のメモリ位置に書き込みます (64bit)。
その後、JMP 命令で、addr のメモリ位置から、ジャンプ先の絶対アドレス位置を読み込み、ジャンプします。
jmp [addr] は、実際には、jmp [RIP + 0x2EBC] となります。
(データセクションのメモリ位置を指定しているため、オフセット値は、その位置を示す値になります)
JMP 命令は、レジスタまたはメモリで移動先が指定された場合、絶対アドレス位置として、その値を rIP にセットします。
この場合、addr のメモリ位置から 64bit の値を読み込み、その絶対アドレスの位置へ移動します。
総括
ジャンプを行う場合、基本的には、ラベルを直接指定して、相対位置のオフセットを指定します。
ただし、値は符号付き 32bit の範囲までです。
絶対アドレス位置に移動したい場合は、レジスタまたはメモリ位置を指定します。
なお、64bit モードの場合、デフォルトのオペランドサイズは 64bit となります。
よって、オペランドには、64bit レジスタ、または、64bit 値が格納されたメモリ位置を指定する必要があります。
ただし、値は符号付き 32bit の範囲までです。
絶対アドレス位置に移動したい場合は、レジスタまたはメモリ位置を指定します。
なお、64bit モードの場合、デフォルトのオペランドサイズは 64bit となります。
よって、オペランドには、64bit レジスタ、または、64bit 値が格納されたメモリ位置を指定する必要があります。
比較命令
2つの値を比較したい場合、CMP 命令や TEST 命令を使います。
CMP 命令は、A - B による減算を行い、結果をフラグにセットします。
TEST 命令は、論理 AND を行って、結果をフラグにセットします (PF, ZF, SF)。
値が 0 かどうかを比較したい場合は、以下のようにして TEST 命令を使うことができます。
同じ値で AND を行った場合、両方のビットが 1 の場合のみ 1 になるので、ビットが一つでも 1 になっていれば、0 以外の値になります。
すべてのビットが 0 (値が 0) の場合のみ、結果の値が 0 になり、ZF が 1 になります。
CMP 命令は、A - B による減算を行い、結果をフラグにセットします。
TEST 命令は、論理 AND を行って、結果をフラグにセットします (PF, ZF, SF)。
値が 0 かどうかを比較したい場合は、以下のようにして TEST 命令を使うことができます。
test eax, eax ; 同じ値で AND jne no_zero ; 結果が 0 でないならジャンプ ; zero no_zero:
同じ値で AND を行った場合、両方のビットが 1 の場合のみ 1 になるので、ビットが一つでも 1 になっていれば、0 以外の値になります。
すべてのビットが 0 (値が 0) の場合のみ、結果の値が 0 になり、ZF が 1 になります。
条件ジャンプ (Jcc)
Jcc (J の後、条件によって異なる名前が続く) 命令は、CMP などの比較命令や演算命令の後に使います。
ステータスフラグが条件に一致している場合は、ジャンプします。条件が一致しない場合は、そのまま後続の命令を実行します。
ステータスフラグが条件に一致している場合は、ジャンプします。条件が一致しない場合は、そのまま後続の命令を実行します。
各 Jcc 命令の意味
Jcc 命令 (および、その他の条件付き命令) は、ニーモニック (命令の名前) が条件の意味を示しています。
'N' は否定、'E' は equal (等しい)。
'A' は符号なしで above (上)、'B' は符号なしで below (下)。
'G' は符号付きで greater (大きい)、'L' は符号付きで less (小さい) を意味します。
他の文字は、フラグの名前を示します。
以下は、CMP A, B の時の、A の値が、B と比べてどうであるかを示します。
'N' は否定、'E' は equal (等しい)。
'A' は符号なしで above (上)、'B' は符号なしで below (下)。
'G' は符号付きで greater (大きい)、'L' は符号付きで less (小さい) を意味します。
他の文字は、フラグの名前を示します。
以下は、CMP A, B の時の、A の値が、B と比べてどうであるかを示します。
フラグの値 | ||
JO | OF = 1 | |
---|---|---|
JNO | OF = 0 | |
JC | CF = 1 | |
JNC | CF = 0 | |
JZ | ZF = 1 | |
JNZ | ZF = 0 | |
JS | SF = 1 | 符号が負 |
JNS | SF = 0 | 符号が正 |
JP JPE | PF = 1 | |
JNP JPO | PF = 0 | |
等しい/等しくない | ||
JE | ZF = 1 | A = B。等しい |
JNE | ZF = 0 | A != B。等しくない |
符号なし | ||
JB | CF = 1 | A < B。符号なしで小さい |
JNAE | CF = 1 | !(A >= B)。符号なしで、大きくも等しくもない |
JNB | CF = 0 | !(A < B)。符号なしで小さくない |
JAE | CF = 0 | A >= B。符号なしで、大きいか等しい |
JBE | CF = 1 or ZF = 1 | A <= B。符号なしで、小さいか等しい |
JNA | CF = 1 or ZF = 1 | !(A > B)。符号なしで、大きくない |
JA | CF = 0 and ZF = 0 | A > B。符号なしで、大きい |
JNBE | CF = 0 and ZF = 0 | !(A <= B)。符号なしで、小さくも等しくもない |
符号付き | ||
JL | SF != OF | A < B。符号付きで小さい |
JNGE | SF != OF | !(A >= B)。符号付きで大きくも等しくもない |
JGE | SF = OF | A >= B。符号付きで大きいか等しい |
JNL | SF = OF | !(A < B)。符号付きで小さくない |
JLE | ZF = 1 or SF != OF | A <= B。符号付きで小さいか等しい |
JNG | ZF = 1 or SF != OF | !(A > B)。符号付きで大きくない |
JG | ZF = 0 and SF = OF | A > B。符号付きで大きい |
JNLE | ZF = 0 and SF = OF | !(A <= B)。符号付きで小さくも等しくもない |
符号付きの値の比較
以下は、符号付き 8bit 整数で、A - B を行った場合の、ステータスフラグの結果です。
上の2つのように、オーバーフローが発生しなかった場合 (OF = 0 時)、
結果が負 [OF = 0 and SF = 1] であれば、「A - B = 負」ということなので、つまり、A < B という条件に当てはまります。
結果が正 [OF = 0 and SF = 0] であれば、「A - B = 正」ということなので、つまり、A > B という条件に当てはまります。
下の2つのように、オーバーフローが発生した場合 (OF = 1 時)、結果の符号が、正か負か (SF = 0 or 1) で、どの形式によるオーバーフローなのかが判断できます。
結果が正 [OF = 1 and SF = 0] であれば、「負−正=正」ということなので、A が負で B が正であるなら、A < B という条件に当てはまります。
結果が負 [OF = 1 and SF = 1] であれば、「正−負=負」ということなので、A が正で B が負であるなら、A > B という条件に当てはまります。
上記をまとめると、OF != SF であれば、A < B となり、OF = SF であれば、A > B ということになります。
あとは、ZF が 1 かどうかで、<= や >= を判断することができます。
1 - 2 = -1 | OF = 0, SF = 1, ZF = 0 (A < B) |
---|---|
2 - 1 = 1 | OF = 0, SF = 0, ZF = 0 (A > B) |
-128 - 1 = -129(0x7F) = 127 | OF = 1, SF = 0, ZF = 0 (A < B) |
127 - -1 = 128(0x80) = -128 | OF = 1, SF = 1, ZF = 0 (A > B) |
上の2つのように、オーバーフローが発生しなかった場合 (OF = 0 時)、
結果が負 [OF = 0 and SF = 1] であれば、「A - B = 負」ということなので、つまり、A < B という条件に当てはまります。
結果が正 [OF = 0 and SF = 0] であれば、「A - B = 正」ということなので、つまり、A > B という条件に当てはまります。
下の2つのように、オーバーフローが発生した場合 (OF = 1 時)、結果の符号が、正か負か (SF = 0 or 1) で、どの形式によるオーバーフローなのかが判断できます。
結果が正 [OF = 1 and SF = 0] であれば、「負−正=正」ということなので、A が負で B が正であるなら、A < B という条件に当てはまります。
結果が負 [OF = 1 and SF = 1] であれば、「正−負=負」ということなので、A が正で B が負であるなら、A > B という条件に当てはまります。
上記をまとめると、OF != SF であれば、A < B となり、OF = SF であれば、A > B ということになります。
あとは、ZF が 1 かどうかで、<= や >= を判断することができます。