アンティーク・アセンブラ~Antique Assembler
第3回 もしも“if”なら
たとえば,C言語でif/elseやswitch,for,while/do-while,条件演算("cond ? v1 : v2" 形式の記述)が使用できないとしたら,有用なプログラムを書くことはできるでしょうか?
実行時に与えられるデータに関わらず,常に一定の手順でしか処理できないとしたら,ソフトウェアの有用性は非常に限定されたものになるでしょう。
今回は,アセンブラにおいて“if”を実現するための,条件判定に関して説明します。
状態フラグ
一般的な CPU アーキテクチャでは,演算等の命令を実行した際に,実行結果に応じて状態が変化するフラグを備えています。
この「実行結果に応じて状態が変化するフラグ」(以下「状態フラグ」)は,特殊なレジスタ上の一部のビットが割り当てられていることが多く,このレジスタは多くの場合,“Condition Code Register”(CCR)や“State Register”(SR)等と呼ばれますが,Intel 80x86アーキテクチャの場合はそのものズバリのEFLAGS という名前のレジスタを持っています(“E”は“Extended”から由来するもので,80286以前の16ビット幅のFLAGレジスタに対して32ビット拡張されていることを意味します)。
なお,実行される命令によっては,更新されるフラグが限定される,つまりそれ以外のフラグには影響を及ぼさない,といった挙動もありますので注意が必要です。
- ※ Intel 80x86アーキテクチャでは,ここで紹介するフラグ以外にもPF(Parity Flag)およびAF(Adjust Flag)が提供されていますが,あまり一般的ではないのでここでの説明は割愛します。
「ゼロ」フラグ
実行結果がゼロか否かを保持するフラグビットを一般にゼロフラグ(Zero Flag)と呼びます。Intel 80x86アーキテクチャでは略称のZFで呼びます。
リスト1 ゼロフラグの挙動
.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
実際にプログラムを動かしてみましょう。
図1 ゼロフラグの挙動確認
(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)
「キャリー」フラグ
符号無し演算における値域超えの有無を保持するフラグビットを一般にキャリーフラグ(Carry Flag)と呼びます。Intel 80x86アーキテクチャでは略称のCFで呼びます。
リスト2 キャリーフラグの挙動
.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
実際にプログラムを動かしてみましょう。
図2 キャリーフラグの挙動確認
(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とは別に,演算結果に応じてZFも変化しています。


