Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

インラインアセンブラで学ぶアセンブリ言語 第3回

アセンブリ言語による分岐や繰り返し

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2006/08/29 00:00

前回は、インラインアセンブラを用いた演算を紹介しました。本稿では、さらにステップアップし、アセンブリ言語による条件分岐や繰り返しなど、プログラムの流れを制御する方法を解説します。

目次

はじめに

 これまで、『インラインアセンブラで学ぶアセンブリ言語』の第1回第2回を通じて、Microsoft Visual C++のインラインアセンブラを用い、アセンブリ言語の基本について説明してきました。

 第3回となる本稿では、インラインアセンブラを用いた流れ制御を解説します。アセンブリ言語は高水準言語における文の概念がなく、すべての命令が単純なオペコードと引数(オペランド)だけで構成されています。この純粋な構造のために、流れ制御を行う場合も、if文やfor文のように記述することはできません。基本的にプログラムの流れ制御はすべてC言語で言うところのgoto文だけで記述し、比較命令や条件ジャンプも個別の命令で行います。

 本来のマクロ機能を持たない生のアセンブリ言語ならば、ジャンプ先のプログラムの位置すらも、すべてメモリアドレスで指定しなければなりません。実際に、機械語にアセンブルされた後は、ラベルという情報は持たず、すべてのジャンプ処理は、プログラムがロードされているメモリアドレスを計算して指定します。

 しかしながら、こうした作業をすべて手計算で行うのは困難です。変数のメモリサイズの計算とは異なり、アセンブリ言語が機械語にアセンブルされた後のオペコードとオペランドのサイズから、目的のコードが配置されているアドレスを計算する作業は煩雑であり、間違いも許されません。こうした作業は人間ではなく、やはりマクロやコンパイラに委ねるべき仕事です。

 幸いインラインアセンブラでは、C言語のラベルを用いてgoto文のように目的のコードに移動することができます。本稿で、機械語に最も近いアセンブリ言語がどのような命令でプログラムの流れを制御するのか、体験することができます。

単純なジャンプ

 条件を指定せずに、無条件に指定した場所に移動するにはJMP命令を使います。

jmp dest

 前述したとおり、本来のJMP命令は移動先のメモリアドレスを指定するものですが、インラインアセンブラではC言語のラベルを指定することができます。JMP命令のディスティネーション・オペランドには、C言語のラベルを指定してください。インラインアセンブラのブロック内にラベルを指定することもできるので、C言語の任意のコードから、インラインアセンブラのコードに移動することもできます。

 機械語の世界では、「IP」という特殊なレジスタに、実行するべきプログラムのアドレスが格納されています。CPUはプログラムを実行するごとにIPレジスタの値を次の命令に移動させます。JMP命令は、性質的にIPレジスタの値を変更する命令であると解釈することもできます。

sample01
#include <stdio.h>

int main() {
    int value = 0;

    __asm {
        jmp label        ;label ラベルまでジャンプする
        add value, 100    ;この命令が飛ばされる
label:
        add value, 1000;
    }

    printf("value=%d\n", value);

    return 0;
}
実行結果
value=1000

 「sample01」は、値0で初期化した整数型の変数valueを用意しています。__asmブロックでvalueに100を加算するコードと、1000を加算するコードを用意していますが、JMP命令が実行された時点でlabelタグに無条件にジャンプします。そのためadd value, 100が実行されず、プログラムの結果は1000となります。JMP命令をコメント化して実行しないようにすれば、直後のADD命令が実行されて1100という実行結果になることが分かります。

条件分岐

 特定の値を調べ、その結果によってプログラムの流れを分岐させるには、最初にCMP命令を実行します。CMP命令はオペランドを比較してその結果をレジスタに保存します。

cmp src1 , src2

 この命令は、2つのソース・オペランドを持ちます。src1に第1ソース・オペランドを、src2に第2ソース・オペランドを指定します。それぞれのソース・オペランドには、レジスタ、メモリアドレス、または即値のいずれかを指定します。

 CMP命令は、指定したソース・オペランドを比較し、EFLAGSと呼ばれるレジスタに結果を保存します。EFLAGSは、命令を実行した結果の副作用的な情報をフラグとして保存するレジスタです。このレジスタの値は、直接読み書きをして利用するものではなく、命令の結果として設定されたり、命令の条件として利用されます。

 CMP命令が実行されるとEFLAGSレジスタのZFと呼ばれるフラグが設定されます。ZFとは、ゼロフラグのことで、演算の結果が0であればセットされ、そうでなければ解除されるという性質を持っています。CMP命令は、一種の減算処理を内部で行いますが、その結果は破棄されます。条件制御は、この演算によって得られた結果を使ってジャンプをすることで実現できます。CMP命令は一種の減算なので、双方に指定したオペランドの値が一致していれば、結果が0となりZFフラグがセットされるという仕組みになります。

 ZFフラグの状態によってジャンプするかどうかを決定する命令はJZ命令と、JNZ命令があります。

jz dest
jnz dest

 JZ命令は、ZFフラグがセットされている場合に、ディスティネーション・オペランドに指定された場所にジャンプします。逆にJNZ命令は、ZFフラグがセットされていない場合に、ディスティネーション・オペランドに指定された場所にジャンプします。

 単純に説明するならば、ある変数AとBが等しいかどうかを調べて、その結果に応じて実行するコードを変更したい場合、CMP命令でAとBを比較し、その後にJZ命令を用いる形になるでしょう。

sample02
#include <stdio.h>

int main() {
    int value;
    char * tc = "value は 100 でした\n";
    char * fc = "value は 100 ではありませんでした\n";
    char * result;

    printf("value 変数に代入する任意の値を入力してください>");
    scanf("%d", &value);

    __asm {
        cmp value, 100    ;value が 100 と等しいかどうかを調べる
        jz cmp_true    ;value が 100 ならジャンプする

        mov ebx, fc;
        mov result, ebx;
        jmp end        ;end にジャンプする

cmp_true:
        mov ebx, tc;
        mov result, ebx;
end:
    }

    printf(result);

    return 0;
}
実行結果
C:\...>sample02
value 変数に代入する任意の値を入力してください>10
value は 100 ではありませんでした

C:\...>sample02
value 変数に代入する任意の値を入力してください>100
value は 100 でした

 「sample02」は、最初にscanf()関数を用いてユーザーにvalue変数に代入する値を入力してもらいます。その後__asmブロック内でvalue変数の値を調べ、入力された値が100と等しいかどうかで、結果として出力する変数を設定します。value変数に入力された値が100と等しければJZ命令によってcmp_trueラベルまで移動し、tc変数がresult変数に設定されます。そうでなければ、fc変数がresult変数に設定され、下部のtcresultに設定するプログラムがそのまま実行されないようにするためにJMP命令でendラベルまで移動しています。これらの作業は、C言語のif-else文と同じです。

 JZ命令やJNZ命令では、値が等しいかどうかを調べてジャンプしていましたが、当然、値がより小さいか、またはより大きいかを調べることも可能です。こうした判定処理もJZ命令と同様にCMP命令による結果がEFLAGSレジスタに保存され、EFLAGSレジスタ内の各種フラグの値を調べて実行されます。ジャンプ命令は非常に数が多いので詳細は割愛しますが、使い方はJZ命令と同じです。

条件ジャンプ命令
命令ジャンプ条件
JAより上(CF = 0 & ZF = 0)
JAEより上か等しい(CF = 0)
JBより下(CF = 1)
JBEより下か等しい(CF = 1 | ZF = 1)
JCキャリーがある(CF = 1)
JCXZCXレジスタが0
JE等しい(ZF = 1)
JGより大きい(ZF = 0 & SF = OF)
JGEより大きいか等しい(SD = OF)
JLより小さい(SF ! OF)
JLEより小さいか等しい(ZF = 1 | SF ! OF)
JNAより上でない(CF = 1 | ZF = 1)
JNAEより上でなく等しい(CF = 1)
JNBより下でない(CF = 0)
JNBEより下でなく等しい(CF = 0 & ZF = 0)
JNCキャリーがない(CF = 0)
JNE等しくない(ZF = 0)
JNGより大きくない(ZF = 1 | SF ! OF)
JNGEより大きくなく等しくない(SF ! OF)
JNLより小さくない(SF = OF)
JNLEより小さくなく等しくない(ZF = 0 & SF = OF)
JNOオーバーフローがない(OF = 0)
JNPパリティがない(PF = 0)
JNS符号がない(SF = 0)
JNZゼロではない(ZF = 0)
JOオーバーフローがある(PF = 1)
JPパリティがある(PF = 1)
JPEパリティが偶数(PF = 1)
JPOパリティが基数(PF = 0)
JS符合がある(SF = 1)
JZゼロである(ZF = 1)

 上の表に示した各種ジャンプ命令は、演算の結果によって設定されるEFLAGSレジスタのフラグを調べて、その結果が真であればジャンプするという性質を持ちます。通常は、CMP命令で値を比較した後、ジャンプ命令で条件に従ってジャンプするかしないかを決定するという使い方になります。

sample03
#include <stdio.h>

int main() {
    int a, b;
    char * r1 = "a は b より大きい\n";
    char * r2 = "a は b より小さい\n";
    char * r3 = "a は b と等しい\n";
    char * result;

    printf("a の値を入力してください>");
    scanf("%d", &a);

    printf("b の値を入力してください>");
    scanf("%d", &b);

    __asm {
        mov eax, a;
        cmp eax, b;
        ja label1;
        jb label2;

        mov ebx, r3;
        mov result, ebx;
        jmp end;
label1:
        mov ebx, r1;
        mov result, ebx;
        jmp end;
label2:
        mov ebx, r2;
        mov result, ebx;
end:
    }

    printf(result);

    return 0;
}
実行結果
C:\...>sample03
a の値を入力してください>10
b の値を入力してください>100
a は b より小さい

a の値を入力してください>100
b の値を入力してください>10
a は b より大きい

a の値を入力してください>100
b の値を入力してください>100
a は b と等しい

 「sample03」は、scanf()関数からユーザーに変数abの値を入力してもらい、abの値を比較して、その結果に応じた文字列を出力するというプログラムです。CMP命令でabを比較し、その後JA命令、またはJB命令で、目的のコードにジャンプします。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 赤坂 玲音(アカサカ レオン)

    平成13年度「全国高校生・専門学校生プログラミングコンテスト 高校生プログラミングの部」にて最優秀賞を受賞。 2005 年度~ Microsoft Most Variable Professional Visual Developer - Visual C++。 プログラミング入門サイト Wis...

バックナンバー

連載:インラインアセンブラで学ぶアセンブリ言語
All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5