アンティーク・アセンブラ~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も変化しています。

「オーバーフロー」フラグ

一般的なソフトウェアでは、符号付きの数値を表現する際に2の補数と呼ばれる方法を用います。

符号有り演算における値域越えの有無を保持するフラグビットを一般にオーバーフローフラグ(Overflow Flag)と呼びます。Intel 80x86アーキテクチャでは略称のOFで呼びます。

リスト3 オーバーフローフラグの挙動
    .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

実際にプログラムを動かしてみましょう。

図3 オーバーフローフラグの挙動確認
(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ビット数の演算とみなした場合には、値域越えとみなされる「0x80000000 - 0x01 = 0x7fffffff」の演算も、符号無し32ビット数の演算とみなした場合では十分に値域の範囲であるため、桁上がりないし桁借りでセットされるCFは変化しません。

「符号」フラグ

演算結果を2の補数とみなした際に、最上位ビットを符号ビット(sign bit)と呼びます。

このビット値と同じ値を保持するフラグビットは符号フラグ(Sign Flag⁠⁠、あるいは「負値の場合に1となる」ことから負値フラグ(Negative Flag)と呼びます。Intel 80x86アーキテクチャでは符号フラグの略称のSFで呼びます。

リスト4 符号フラグの挙動
    .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

実際にプログラムを動かしてみましょう。

図4 符号フラグの挙動確認
(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) 

符号付き演算でのオーバーフローの際には、元の数値と演算結果では符号が反転します。

そこで、先述した OF がセットされている=値域超えが発生した際に、SFの設定状況=演算結果の符合ビットを確認することで、演算結果の値域超えがどの方向で発生したのかを判定することができます。

  • 結果が負値(=SFセット)なら、上限を超えた演算による値域超え
  • 結果が正値(=SFクリア)なら、下限を下回った演算による値域超え

条件分岐

一般的な CPU アーキテクチャは、現在実行中の命令が格納されているアドレスを保持するレジスタを持っています。

このレジスタは通常プログラムカウンタ(program counter)と呼ばれます。32ビットIntel 80x86アーキテクチャの場合はEIP(Extended Instruction Pointer)レジスタが相当します(⁠⁠extended⁠は、16ビット幅しか扱えなかった80286以前のIPレジスタに対して、32ビット化拡張がされている、という意味です⁠⁠。

EIPレジスタは、命令の実行ごとに次の命令位置、次の命令位置へと順次書き換えられますが、この「次に実行する命令の位置」を強制的に変化させる命令を制御転送(Controll Transfer)命令と呼びます。

制御転送命令には幾つか種類がありますが、通常のプログラムで使用するものは、手続き呼び出し(Procedure Call)に関する命令と、それ以外の分岐(Branch)に関する命令に大別できます。

※)
 連載次々回で説明予定

特に分岐命令には、制御転送の実施の有無を状態フラグの状況に応じて変化させる条件分岐(Conditional Branch)命令と、常に分岐を実施する無条件分岐命令があります。

Intel 80x86アーキテクチャの条件分岐命令はJccという形式で表記され、cc 部分には分岐を実施する際の条件名称が指定されます(無条件分岐命令はJMPと記述します⁠⁠。

主要な条件名称を以下に示します。

 フラグ状態欄は、C言語的な表記を使用します。
表1 条件名称
名称条件フラグ状態
e
Equal)
等しいZF
ne
Not Equal)
等しくない!ZF
g
Grater)
比較値より大きい!ZF && (SF == OF)
le
Less or Equal)
比較値以下ZF || (SF != OF)
ge
Grater or Equal)
比較値以上SF == OF
l
Less)
比較値未満SF != OF
a
Above)
(符号無し)比較値より大きい!CF && !ZF
na
Not Above)
(符号無し)比較値以下CF || ZF
cc
Carry Clear)
(符号無し)比較値以上!CF
cs
Carry Set)
(符号無し)比較値未満CF

「フラグ状態」は、⁠条件」欄における大小関係が成立する際に、⁠検証対象の値」から「比較値」減算した際のフラグ状態を示しています。

符号無し数の比較や、⁠等しい」⁠等しくない」を理解するのは比較的簡単だと思いますが、符号付き数の比較に関するSF/OFの条件は少々面倒かもしれません。

“SF == OF⁠が成立するのは

  • 減算結果が 0 ないし正の値(SF == 0 && OF == 0)
  • 演算結果が符号付き数の上限超え(SF == 1 && OF == 1)

のいずれかの場合です(⁠⁠上限超え」と判断する理由は、SFでの説明をご覧ください⁠⁠。

減算の際に後者の条件が成立するのは、⁠検証対象の値」が正の値で、かつ「比較値」が負の値の場合に限定されますから、以上を総合すると、⁠SF == OF⁠ということは「検証対象の値」「比較値」ということに他ならないことがわかります(⁠⁠SF != OF⁠ならその逆⁠⁠。

なお、Intel 80x86アーキテクチャの状態フラグは、加減算のような(整数)演算命令の実行以外にも、比較(compare)命令CMPによって更新されます。

結果をレジスタなりメモリに格納する演算命令と違い、比較命令は状態フラグ更新以外の改変副作用が無いため、条件分岐命令のための状態フラグ更新には、比較命令を使用するケースの方が一般的と思われます。

リスト5 比較命令の挙動
    .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
図5 比較命令の挙動確認
(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) 

論理積/論理和

実際の条件分岐で指定される条件は、必ずしも前節で述べたような単純な形式ではありません。

たとえば、以下のような条件判定の実装は珍しいものではありません:

リスト6 論理積
if(a && b && c){
    x = 1;
}

上記のような論理積(logical multiplication)を実現する場合、Cコンパイラが生成するアセンブラプログラムは以下のようになります。

リスト7 論理積の実装
    # 変数 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 文以後の処理が記述される

また

リスト8 論理和
if(a || b || c){
    x = 1;
}

上記の様な論理和(logical addition)を実現する場合、Cコンパイラが生成するアセンブラプログラムは以下のようになります。

リスト9 論理和の実装
    # 変数 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の場合、加減算は32ビット単位で実施されます。

しかし、64ビットや128ビットといった、より大きな値域での演算が必要な場合はどうすれば良いのでしょうか?

ありがたいことに、このような状況で使用するための拡張加減算とでも言うべき命令が、多くの CPU アーキテクチャでサポートされています。

Intel 80x86アーキテクチャではADC(ADd with Carry)およびSBB(SuBtract with Borrow)命令がそれに相当します。

以下は、領域aおよびbに格納された4倍長(128ビット)データの加算を行なうプログラムです(結果は領域bに格納⁠⁠。

リスト10 4倍長加算
    .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倍長の加算を行ってみましょう。

まずは桁上がりが発生しないケースとして、0x12345678.12345678.12345678.12345678 同士の加算を見てみましょう(見やすいように、32ビットごとに⁠.⁠で区切りました⁠⁠。

図6 桁上がりのない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ビット=4バイト)ごとの加算が実施されるだけです。このケースは簡単ですね。

次に32ビット幅間での桁上がりが発生する0x00000001.fffffffd.fffffffe.ffffffffと0x00000004.00000003.00000002.00000001の加算を見てみましょう。

以下の実行例では、初期値の設定手順は省略しています。また、複数の命令を一括して実行するために、実行命令数を指定するための引数をstepiコマンドに与えています。

図7 桁上がりのある4倍長加算
(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) 

桁上がりが次々と伝播していく様子がわかるのではないでしょうか?

おすすめ記事

記事・ニュース一覧