比較とジャンプ

ジャンプ
> 汎用命令 (3)

ジャンプ命令には、無条件ジャンプ (JMP) と、条件ジャンプ (Jcc) があります。

無条件ジャンプは、相対位置または絶対位置を指定して、移動します。

条件ジャンプは、ステータスフラグの状態によって、ジャンプするか、後続の命令をそのまま実行します。
条件ジャンプは、相対位置での移動しか行えません。

ジャンプは、rIP の値を変更することで、次の命令の実行位置を変更します。
サンプルコード1
以下は、JMP 命令のエンコードを確認するためだけのコードです。実際には実行しないでください。

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 命令で、そのメモリ位置の値を参照し、絶対位置にジャンプします。

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 レジスタに格納します。

「mov rax, label1」では、絶対アドレスを格納することはできません。
この場合、RAX には、.text セクション先頭からの label1 の相対位置が、値として入ります。
ラベルの絶対位置を取得したい場合は、LEA 命令でメモリのアドレス指定をして、そのメモリ位置のアドレスを、値として格納します。

RAX に label1 の絶対位置を取得したら、その値を、addr のメモリ位置に書き込みます (64bit)。
その後、JMP 命令で、addr のメモリ位置から、ジャンプ先の絶対アドレス位置を読み込み、ジャンプします。

jmp [addr] は、実際には、jmp [RIP + 0x2EBC] となります。
(データセクションのメモリ位置を指定しているため、オフセット値は、その位置を示す値になります)

JMP 命令は、レジスタまたはメモリで移動先が指定された場合、絶対アドレス位置として、その値を rIP にセットします。
この場合、addr のメモリ位置から 64bit の値を読み込み、その絶対アドレスの位置へ移動します。
総括
ジャンプを行う場合、基本的には、ラベルを直接指定して、相対位置のオフセットを指定します。
ただし、値は符号付き 32bit の範囲までです。

絶対アドレス位置に移動したい場合は、レジスタまたはメモリ位置を指定します。

なお、64bit モードの場合、デフォルトのオペランドサイズは 64bit となります。
よって、オペランドには、64bit レジスタ、または、64bit 値が格納されたメモリ位置を指定する必要があります。
比較命令
2つの値を比較したい場合、CMP 命令や TEST 命令を使います。

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 と比べてどうであるかを示します。

フラグの値
JOOF = 1
JNOOF = 0
JCCF = 1
JNCCF = 0
JZZF = 1
JNZZF = 0
JSSF = 1符号が負
JNSSF = 0符号が正
JP
JPE
PF = 1
JNP
JPO
PF = 0
等しい/等しくない
JEZF = 1A = B。等しい
JNEZF = 0A != B。等しくない
符号なし
JBCF = 1A < B。符号なしで小さい
JNAECF = 1!(A >= B)。符号なしで、大きくも等しくもない
JNBCF = 0!(A < B)。符号なしで小さくない
JAECF = 0A >= B。符号なしで、大きいか等しい
JBECF = 1 or ZF = 1A <= B。符号なしで、小さいか等しい
JNACF = 1 or ZF = 1!(A > B)。符号なしで、大きくない
JACF = 0 and ZF = 0A > B。符号なしで、大きい
JNBECF = 0 and ZF = 0!(A <= B)。符号なしで、小さくも等しくもない
符号付き
JLSF != OFA < B。符号付きで小さい
JNGESF != OF!(A >= B)。符号付きで大きくも等しくもない
JGESF = OFA >= B。符号付きで大きいか等しい
JNLSF = OF!(A < B)。符号付きで小さくない
JLEZF = 1 or SF != OFA <= B。符号付きで小さいか等しい
JNGZF = 1 or SF != OF!(A > B)。符号付きで大きくない
JGZF = 0 and SF = OFA > B。符号付きで大きい
JNLEZF = 0 and SF = OF!(A <= B)。符号付きで小さくも等しくもない
符号付きの値の比較
以下は、符号付き 8bit 整数で、A - B を行った場合の、ステータスフラグの結果です。

1 - 2 = -1OF = 0, SF = 1, ZF = 0 (A < B)
2 - 1 = 1OF = 0, SF = 0, ZF = 0 (A > B)
-128 - 1 = -129(0x7F) = 127OF = 1, SF = 0, ZF = 0 (A < B)
127 - -1 = 128(0x80) = -128OF = 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 かどうかで、<= や >= を判断することができます。