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

第3回 もしも“if”なら

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

分岐以外の状態フラグ参照

条件分岐命令以外の状態フラグ使用の例として,多倍長演算の例を説明したいと思います。

たとえば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) 

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

著者プロフィール

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

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