アンティーク・アセンブラ~Antique Assembler

第3回 もしも“if”なら

この記事を読むのに必要な時間:およそ 10.5 分

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

一般的なソフトウェアでは,符号付きの数値を表現する際に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クリア)なら,下限を下回った演算による値域超え

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。