たとえば、
実行時に与えられるデータに関わらず、
今回は、
状態フラグ
一般的な CPU アーキテクチャでは、
この
なお、
- ※ Intel 80x86アーキテクチャでは、
ここで紹介するフラグ以外にもPF (Parity Flag) およびAF (Adjust Flag) が提供されていますが、 あまり一般的ではないのでここでの説明は割愛します。
「ゼロ」フラグ
実行結果がゼロか否かを保持するフラグビットを一般にゼロフラグ
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
movl $2, %eax
# 初期値の 2 から 1 を引くので、eax の値は 1
subl $1, %eax
# eax の現在値 1 から 1 を引くので、eax の値は 0
subl $1, %eax
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
実際にプログラムを動かしてみましょう。
(gdb) disassemble entry_point Dump of assembler code for function entry_point: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: mov $0x2,%eax 0x00401006 <entry_point+6>: sub $0x1,%eax 0x00401009 <entry_point+9>: sub $0x1,%eax End of assembler dump. (gdb) run .... 0x00401001 in entry_point () (gdb) stepi 0x00401006 in entry_point () ※ "mov $0x02, %eax" の実行 (gdb) info register eax eflags eax 0x2 2 eflags 0x246 [ PF ZF IF ] (gdb) stepi 0x00401009 in entry_point () ※ 1つ目の "sub $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x1 1 eflags 0x202 [ IF ] ※ ZF のクリア (gdb) stepi 0x0040100c in end_of_program () ※ 2 つ目の "sub $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x0 0 eflags 0x246 [ PF ZF IF ] ※ ZF のセット (gdb)
「キャリー」フラグ
符号無し演算における値域超えの有無を保持するフラグビットを一般にキャリーフラグ
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
# 符号無し 32 ビットの上限値
movl $0xffffffff, %eax
# 符号無し 32 ビットの上限を超えた加算
addl $1, %eax
# 0 ~ 31 ビットの範囲の演算
addl $1, %eax
# 符号無し 32 ビットの下限(0)を下回る減算
subl $2, %eax
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
実際にプログラムを動かしてみましょう。
(gdb) disassemble entry_point Dump of assembler code for function entry_point: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: mov $0xffffffff,%eax 0x00401006 <entry_point+6>: add $0x1,%eax 0x00401009 <entry_point+9>: add $0x1,%eax 0x0040100c <entry_point+12>: sub $0x2,%eax End of assembler dump. (gdb) run .... 0x00401001 in entry_point () (gdb) stepi 0x00401006 in entry_point () ※ "mov $0xffffffff, %eax" の実行 (gdb) info register eax eflags eax 0xffffffff -1 eflags 0x246 [ PF ZF IF ] (gdb) stepi 0x00401009 in entry_point () ※ 1 つ目の "add $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x0 0 eflags 0x257 [ CF PF AF ZF IF ] ※ CF のセット (gdb) stepi 0x0040100c in entry_point () ※ 2 つ目の "add $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x1 1 eflags 0x202 [ IF ] ※ CF のクリア (gdb) stepi 0x0040100f in end_of_program () ※ "sub $0x01, %eax" の実行 (gdb) info register eax eflags eax 0xffffffff -1 eflags 0x297 [ CF PF AF SF IF ] ※ CF のセット (gdb)
上記の実行例ではCFとは別に、
「オーバーフロー」フラグ
一般的なソフトウェアでは、
符号有り演算における値域越えの有無を保持するフラグビットを一般にオーバーフローフラグ
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
# 符号付き 32 ビットの上限値
movl $0x7fffffff, %eax
# 符号付き 32 ビットの正値上限を超えた加算
addl $1, %eax
# 符号付き 32 ビットの値域内の加算
addl $1, %eax
# 符号付き 32 ビットの下限値
movl $0x80000000, %eax
# 符号付き 32 ビットの負値下限を下回る減算
subl $1, %eax
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
実際にプログラムを動かしてみましょう。
(gdb) disassemble entry_point Dump of assembler code for function entry_point: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: mov $0x7fffffff,%eax 0x00401006 <entry_point+6>: add $0x1,%eax 0x00401009 <entry_point+9>: add $0x1,%eax 0x0040100c <entry_point+12>: mov $0x80000000,%eax 0x00401011 <entry_point+17>: sub $0x1,%eax End of assembler dump. (gdb) run .... 0x00401001 in entry_point () (gdb) stepi 0x00401006 in entry_point () (gdb) info register eax eflags eax 0x7fffffff 2147483647 eflags 0x246 [ PF ZF IF ] (gdb) stepi 0x00401009 in entry_point () ※1つ目の "add $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x80000000 -2147483648 eflags 0xa96 [ PF AF SF IF OF ] ※ OF のセット (gdb) stepi 0x0040100c in entry_point () ※ 2 つ目の "add $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x80000001 -2147483647 eflags 0x282 [ SF IF ] ※ OF のクリア (gdb) stepi 0x00401011 in entry_point () ※ "mov $0x80000000, %eax" の実行 (gdb) info register eax eflags eax 0x80000000 -2147483648 eflags 0x282 [ SF IF ] (gdb) stepi 0x00401014 in end_of_program () ※ "sub $0x01, %eax" の実行 (gdb) info register eax eflags eax 0x7fffffff 2147483647 eflags 0xa16 [ PF AF IF OF ] ※ OF のセット (gdb)
符号付き32ビット数の演算とみなした場合には、
「符号」フラグ
演算結果を2の補数とみなした際に、
このビット値と同じ値を保持するフラグビットは符号フラグ
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
movl $0, %eax
# 初期値の 0 から 1 を引くので、eax の値は -1 = 負値
subl $1, %eax
# eax の現在値に 2 を足すので、eax の値は 1 = 正値
addl $2, %eax
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
実際にプログラムを動かしてみましょう。
(gdb) disassemble entry_point Dump of assembler code for function entry_point: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: mov $0x0,%eax 0x00401006 <entry_point+6>: sub $0x1,%eax 0x00401009 <entry_point+9>: add $0x2,%eax End of assembler dump. (gdb) run .... 0x00401001 in entry_point () (gdb) stepi 0x00401006 in entry_point () (gdb) info register eax eflags eax 0x0 0 eflags 0x246 [ PF ZF IF ] (gdb) stepi 0x00401009 in entry_point () ※ "sub $0x01, %eax" の実行 (gdb) info register eax eflags eax 0xffffffff -1 eflags 0x297 [ CF PF AF SF IF ] ※ SF のセット (gdb) stepi 0x0040100c in end_of_program () ※ "add $0x02, %eax" の実行 (gdb) info register eax eflags eax 0x1 1 eflags 0x213 [ CF AF IF ] ※ SF のクリア (gdb)
符号付き演算でのオーバーフローの際には、
そこで、
- 結果が負値
(=SFセット) なら、 上限を超えた演算による値域超え - 結果が正値
(=SFクリア) なら、 下限を下回った演算による値域超え
条件分岐
一般的な CPU アーキテクチャは、
このレジスタは通常プログラムカウンタ
EIPレジスタは、
制御転送命令には幾つか種類がありますが、
- ※)
- 連載次々回で説明予定
特に分岐命令には、
Intel 80x86アーキテクチャの条件分岐命令はJcc
という形式で表記され、cc
部分には分岐を実施する際の条件名称が指定されますJMP
と記述します)。
主要な条件名称を以下に示します。
- ※ フラグ状態欄は、
C言語的な表記を使用します。
名称 | 条件 | フラグ状態 |
---|---|---|
e | 等しい | ZF |
ne | 等しくない | !ZF |
g | 比較値より大きい | !ZF && (SF == OF) |
le | 比較値以下 | ZF || (SF != OF) |
ge | 比較値以上 | SF == OF |
l | 比較値未満 | SF != OF |
a | (符号無し) | !CF && !ZF |
na | (符号無し) | CF || ZF |
cc | (符号無し) | !CF |
cs | (符号無し) | CF |
「フラグ状態」
符号無し数の比較や、
“SF == OF”
- 減算結果が 0 ないし正の値
(SF == 0 && OF == 0) - 演算結果が符号付き数の上限超え
(SF == 1 && OF == 1)
のいずれかの場合です
減算の際に後者の条件が成立するのは、
なお、CMP
によって更新されます。
結果をレジスタなりメモリに格納する演算命令と違い、
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
movl $0, %eax
# 演算結果は 0 - 1 ⇒ 0xffffffff(-1)
# 演算結果に応じて EFLAGS を更新
# eax の値を - 1 で更新
subl $1, %eax
# 演算結果は 0xffffffff - 0xffffffff ⇒ 0
# 演算結果に応じて EFLAGS を更新
# eax は 0xffffffff のまま
cmpl $0xffffffff, %eax
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
(gdb) disassemble entry_point Dump of assembler code for function entry_point: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: mov $0x0,%eax 0x00401006 <entry_point+6>: sub $0x1,%eax 0x00401009 <entry_point+9>: cmp $0xffffffff,%eax End of assembler dump. (gdb) run .... 0x00401001 in entry_point () (gdb) stepi 0x00401006 in entry_point () (gdb) info register eax eflags eax 0x0 0 eflags 0x246 [ PF ZF IF ] (gdb) stepi 0x00401009 in entry_point () ※ "sub $0x01, %eax" の実行 (gdb) info register eax eflags eax 0xffffffff -1 ※ 値は更新 eflags 0x297 [ CF PF AF SF IF ] (gdb) stepi 0x0040100c in end_of_program () ※ "cmp $0xffffffff, %eax" の実行 (gdb) info register eax eflags eax 0xffffffff -1 ※ 値はそのまま eflags 0x246 [ PF ZF IF ] ※ 演算結果による更新 (gdb)
論理積/論理和
実際の条件分岐で指定される条件は、
たとえば、
if(a && b && c){
x = 1;
}
上記のような論理積
# 変数 a 領域の格納値から 0 を引く
cmpl $0, a
# 演算結果が 0 であったら notmatched に遷移
# ⇒ 変数 a が 0 なら notmatched に遷移
je notmatched
# 変数 b 領域の格納値から 0 を引く
cmpl $0, b
# 演算結果が 0 であったら notmatched に遷移
je notmatched
# 変数 c 領域の格納値から 0 を引く
cmpl $0, c
# 演算結果が 0 であったら notmatched に遷移
je notmatched
# a && b && c だったので、"x = 1" を実施
movl $1, x
notmatched:
# 以下、if 文以後の処理が記述される
また
if(a || b || c){
x = 1;
}
上記の様な論理和
# 変数 a 領域の格納値から 0 を引く
cmpl $0, a
# 演算結果が非 0 であったら matched に遷移
# ⇒ 変数 a が非 0 であったら matched に遷移
jne matched
# 変数 b 領域の格納値から 0 を引く
cmpl $0, b
# 演算結果が非 0 であったら matched に遷移
jne matched
# 変数 c 領域の格納値から 0 を引く
cmpl $0, c
# 演算結果が非 0 であったら matched に遷移
jne matched
# a || b || c が成立しないので notmatched に遷移
jmp notmatched
matched:
# a || b || c が成立したので、"x = 1" を実施
movl $1, x
notmatched:
# 以下、if 文以後の処理が記述される
以上のことを踏まえた上でC言語の言語仕様を読めば
- 論理積/
論理和による結合は、 常に全ての条件が確認されるわけではない - 論理積による結合は、
成立しない条件があった時点で中断され得る - 論理積による結合は、
成立する条件があった時点で中断され得る
という仕様も理解しやすいのではないでしょうか?
分岐以外の状態フラグ参照
条件分岐命令以外の状態フラグ使用の例として、
たとえば32ビットアーキテクチャのCPUの場合、
しかし、
ありがたいことに、
Intel 80x86アーキテクチャではADC
SBB
以下は、
.data
.align 4
.global a
a:
.long 0 # 最下位桁(LSB first)
.long 0
.long 0
.long 0
.global b
b:
.long 0
.long 0
.long 0
.long 0
.text
.align 4
.global entry_point
entry_point:
int3 # プログラム実行の一時停止
leal a, %eax # eax には a 領域のアドレス
leal b, %ebx # ebx には b 領域のアドレス
# a+0x00 + b+0x00 を b+0x00 に格納
movl 0x00(%eax), %edx
addl %edx, 0x00(%ebx)
# a+0x04 + b+0x04 + CF を b+0x04 に格納
# ⇒ 直前の加算での桁上がり分の取り込み
movl 0x04(%eax), %edx
adcl %edx, 0x04(%ebx)
# a+0x08 + b+0x08 + CF を b+0x08 に格納
movl 0x08(%eax), %edx
adcl %edx, 0x08(%ebx)
# a+0x0c + b+0x0c + CF を b+0x0c に格納
movl 0x0c(%eax), %edx
adcl %edx, 0x0c(%ebx)
.global end_of_program
end_of_program:
int3 # プログラム実行の一時停止
nop
それでは実際に4倍長の加算を行ってみましょう。
まずは桁上がりが発生しないケースとして、
(gdb) run .... 0x00401001 in entry_point () (gdb) x/4x &a 0x402000 <a>: 0x00000000 0x00000000 0x00000000 0x00000000 ※ a 領域の初期状態 (gdb) set var *0x402000=0x12345678 (gdb) set var *0x402004=0x12345678 (gdb) set var *0x402008=0x12345678 (gdb) set var *0x40200c=0x12345678 ※ アドレスを指定して強制書き換え (gdb) x/4x &a 0x402000 <a>: 0x12345678 0x12345678 0x12345678 0x12345678 ※ 強制書き換え後の a 領域の状態 (gdb) x/4x &b 0x402010 <b>: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) set var *0x402010=0x12345678 (gdb) set var *0x402014=0x12345678 (gdb) set var *0x402018=0x12345678 (gdb) set var *0x40201c=0x12345678 (gdb) x/4x &b 0x402010 <b>: 0x12345678 0x12345678 0x12345678 0x12345678 ※ 同様に強制書き換えした後の b 領域の状態 (gdb) continue ※ 4倍長加算の実施 .... 0x00401024 in end_of_program () (gdb) x/4x &b 0x402010 <b>: 0x2468acf0 0x2468acf0 0x2468acf0 0x2468acf0 ※ 4倍長加算結果 (gdb)
この実行例で行っているようなGDBのset var
”run
)
桁上がりが無い場合は、
次に32ビット幅間での桁上がりが発生する0x00000001.
以下の実行例では、stepi
コマンドに与えています。
(gdb) disassemble entry_point Dump of assembler code for function entry_point: 0x00401000 <entry_point+0>: int3 0x00401001 <entry_point+1>: lea 0x402000,%ebx 0x00401007 <entry_point+7>: lea 0x402010,%eax 0x0040100d <entry_point+13>: mov (%ebx),%edx 0x0040100f <entry_point+15>: add %edx,(%eax) 0x00401011 <entry_point+17>: mov 0x4(%ebx),%edx 0x00401014 <entry_point+20>: adc %edx,0x4(%eax) 0x00401017 <entry_point+23>: mov 0x8(%ebx),%edx 0x0040101a <entry_point+26>: adc %edx,0x8(%eax) 0x0040101d <entry_point+29>: mov 0xc(%ebx),%edx 0x00401020 <entry_point+32>: adc %edx,0xc(%eax) End of assembler dump. (gdb) run .... 0x00401001 in entry_point () (gdb) .... (gdb) x/4x &a 0x402000 <a>: 0xffffffff 0xfffffffe 0xfffffffd 0x00000001 (gdb) x/4x &b 0x402010 <b>: 0x00000001 0x00000002 0x00000003 0x00000004 (gdb) stepi 4 0x00401011 in entry_point () ※ 4 命令分 = 最初の add までを一気に実行 (gdb) info register eflags eflags 0x257 [ CF PF AF ZF IF ] ※ 0xffffffff + 0x00000001 により CF セット (gdb) x/4x &b 0x402010 <b>: 0x00000000 0x00000002 0x00000003 0x00000004 ※ b+0x00 には 0xffffffff + 0x00000001 の結果が格納 (gdb) stepi 2 0x00401017 in entry_point () ※ 最初の adc までを一気に実行 (gdb) info register eflags eflags 0x213 [ CF AF IF ] ※ 0xfffffffe + 0x00000002 + CF により CF セット (gdb) x/4x &b 0x402010 <b>: 0x00000000 0x00000001 0x00000003 0x00000004 ※ b+0x04 には 0xfffffffe + 0x00000002 + CF の結果が格納 (gdb) stepi 2 0x0040101d in entry_point () ※ 2つ目の adc までを一気に実行 (gdb) info register eflags eflags 0x213 [ CF AF IF ] ※ 0xfffffffd + 0x00000003 + CF により CF セット (gdb) x/4x &b 0x402010 <b>: 0x00000000 0x00000001 0x00000001 0x00000004 ※ b+0x08 には 0xfffffffd + 0x00000003 + CF の結果が格納 (gdb) stepi 2 0x00401023 in end_of_program () ※ 3つ目の adc までを一気に実行 (gdb) info register eflags eflags 0x206 [ PF IF ] ※ 0x00000001 + 0x00000004 + CF により CF クリア (gdb) x/4x &b 0x402010 <b>: 0x00000000 0x00000001 0x00000001 0x00000006 ※ b+0x0c には 0x00000001 + 0x00000004 + CF の結果が格納 (gdb)
桁上がりが次々と伝播していく様子がわかるのではないでしょうか?