Light Macro Assembler ホームページへ

スタンダードな EXE プログラム

EXE プログラムのソースコード例です。簡略化セグメントを使わないスタンダードな方法で記述したものです。


        assume  cs:cseg,ds:dseg,ss:sseg ; セグメント レジスタの値を宣言

cseg    segment                         ; コード セグメント cseg の開始宣言
start:                                  ; プログラム開始ラベル
        mov     ax, dseg                ; ax = ラベル msg のセグメント値
        mov     ds, ax                  ; ds = ax
        mov     dx, offset msg          ; dx = ラベル msg のオフセット値
        mov     ah, 09h                 ; int 21h のファンクション番号
        int     21h                     ; 文字列の表示
        mov     ax, 4C00h               ; ah = 4Ch, al = OS 戻り値 = 0
        int     21h                     ; DOS へ戻る
cseg    ends                            ; コードセグメント cseg の終了

dseg    segment byte                    ; データ セグメント dseg の開始
msg     db      'Hello !',0Dh,0Ah,'$'   ; 表示する文字列。'$'は終わりを示す記号
dseg    ends                            ; データ セグメント dseg の終了

sseg    segment stack                   ; スタック セグメント sseg の開始
        db      100h dup(?)             ; スタックの大きさは 256 バイトもあれば十分
sseg    ends                            ; スタック セグメント sseg の終了

        end     start                   ; プログラムの終了。start は開始ラベル

このコードが書かれているテキスト ファイルの名前を仮に「hello.asm」とすると、LASM でアセンブルするには、コマンドラインで次のように入力します。

lasm hello        hello.obj が生成されます
lil hello         hello.exe が生成されます
hello            「Hello !」と表示されます

このプログラムについて

では、この hello.asm について、少し詳しく見ていきましょう。アセンブラに慣れていない方はよくわからないかもしれませんが、雰囲気をつかんでいただくだけでもよいと思います。これだけのコードにも多くのポイントがあります。


プログラムの概要

このプログラムは MS-DOS のファンクション 09h によって「Hello !」と表示し、ファンクション 4Ch によって終了します。この簡単な EXE 形式プログラムは、「cseg」「dseg」「sseg」の3つのセグメントで構成されています。


assume 文によるセグメント レジスタの宣言

    assume cs:cseg,ds:dseg,ss:sseg
先頭の assume 文は、各セグメント レジスタがどのセグメントを示しているか宣言します。assume 文それ自身は機械語コードを作成するものではなく、セグメント レジスタの現在の値をアセンブラに通知するだけです。この情報は、たとえば次のようなコードをアセンブルするときに必要になります。

    mov     al, [msg]
これは「ラベル msg が指すメモリの内容を al レジスタに入れなさい」という命令です。8086 系のプロセッサでは、このようにメモリを参照する命令はセグメント レジスタを使用しなければなりませんが、4 つ (80386 以上では 6 つ) あるセグメント レジスタのうちどれを使えばよいのでしょうか。

assume 文の情報は、この選択に使用されます。このプログラムでは、msg は dseg 内にあり、assume 文で dseg に結び付けられているのは ds だけです。したがって ds が使用され、「mov al, ds:[msg]」と同等のコードが生成されます。もし assume 文で dseg に結び付けられているのが es だったなら、アセンブラは「mov al, es:[msg]」と同等のコードを生成します。

assume 文の宣言内容が正しいかどうかは、アセンブラにはわかりません。実態と異なる宣言を行い、アセンブラがその不正な情報を利用した場合は、プログラムが正しく動かなくなります。ただ、assume 文の宣言内容が必要な命令は限られているので、この文を省略できる場合もあります。実際、hello.asm の場合は assume 文がなくても大丈夫です。

assume 文の 最初の「cs:cseg」というパラメータは、コード セグメント レジスタ cs がセグメント cseg の先頭を指していることを宣言しています。cs は常にコードが置かれているセグメントの先頭を指しているので、そのことをアセンブラに教えています。

assume 文の 2 番目のパラメータは「ds:dseg」となっていますが、この時点でこう宣言するのは厳密には正しくありません。なぜなら、ds は少し後の「mov ds, ax」というコードが実行されて初めて dseg に位置付けられるからです。したがって、「assume ds:dseg」という宣言は、本当は「mov ds, ax」の後に書くのが正しいのです。ただ、このプログラムではそれまでの間に assume 文に影響されるコードがないので、assume 文を 1 つで済ませるために少し先回りして宣言しています。こういう書き方は少し不正確なのですが、よく行われています。

assume 文の 3 番目の「ss:sseg」は、スタック セグメント レジスタ ss の値を宣言しています。ss は「stack」パラメータ付きで宣言されたセグメントの先頭を指しています。ss の値は EXE ファイルのロード時に自動的に行われます。


コード セグメントの開始

    cseg  segment
この segment 文は、cseg という名前のセグメントの開始を宣言しています。コードやデータを作成するのは、常にセグメントの中でなければなりません。


プログラム開始ラベル

    start
プログラムの開始地点を指定するために、ラベルを定義します。プログラム最後の end 文では、プログラムの開始位置としてこのラベルの名前を指定しています。


ds と dx のセット

    mov   ax, dseg
    mov   ds, ax
    mov   dx, offset msg
ds:dx がメッセージ文字列の先頭を指すようにセットします。つまり、ds の指すセグメントのオフセット dx が msg を指すようにします。

セグメント レジスタ ds への代入では、プロセッサの命令セットの制約で直接「mov ds, dseg」と書くことができないため、ax レジスタを経由して値をセットしています。


int 20h ファンクション 09h 番による文字列の表示

    mov   ah, 09h
    int   21h
int 21h、つまり 21h 番のソフトウエア割り込みでは、MS-DOS が各種のサービスを提供しています。この割り込みはファンクション リクエストと呼ばれる MS-DOS の API サービスです。21h 番以外の番号でもいくつかのサービスが提供されていますが、21h 番が全体の大部分を占めています。

21h 番で提供されるサービスは多数あるので、その種類を ah レジスタの値で指定します。ah の値によっては、さらに al の値も使用されます。今回使用する 09h 番は、「ds:dx で示される文字列を '$' があるまで標準出力に表示する」というサービスです。

文字列を表示する方法としては、文字コードを VRAM (Video RAM) に直接書き込むとか、ハードウエアの BIOS を利用するなどの方法もあります。しかし、MS-DOS のファンクション リクエストを使う方法がもっとも簡単であり、また汎用性があります (各ハードウエアで共通に使えます)。ですから、高速に表示する必要があるといった理由がない限り、ファンクション リクエストを使うのが一番よい方法です。


ファンクション4Chによるプログラムの終了

    mov   ax, 4C00h
    int   21h
ファンクション リクエスト 4Ch 番は、プログラムを終了して MS-DOS に戻ります。このとき、al レジスタの値がプログラムの OS 戻り値になります。ah と al の値を別々の命令でセットしてもよいのですが、上のように ah と al で構成される ax レジスタに代入すれば、1 つの命令で ah と al の両方をセットできます。

OS 戻り値としては、慣習的に「正常終了」を意味する 0 を返しています。OS 戻り値は表示されることもなく、通常は無視されますが、IF ERRORLEVEL コマンドを使用しているバッチ ファイルやメイク ユーティリティ NMAKE.EXE のように OS 戻り値を利用するプログラムもあります。

この int 21h 命令はプログラムを終了させるので、戻ってきません。


コード セグメントの終了

    cseg  ends
今まで開いていたセグメント cseg の終了を宣言します。


データの定義

    dseg  segment byte
    msg   db   'Hello !',0Dh,0Ah,'$'
    dseg  ends
データ用のセグメントの中でメッセージ データを作成します。セグメントの開始方法と終了方法は、先の cseg の場合と同様です。パラメータの「byte」は、セグメントのアラインメントと呼ばれる属性です。「byte」はバイト単位のアラインメント、つまりこのセグメントを常に前のセグメントの直後に配置するように指定します。この指定がないとセグメントはパラグラフ単位のアラインメントになり、16 で割り切れるオフセットに配置されます。そのため、プログラム サイズが最大で 15 バイト大きくなります。

メッセージ データの 0Dh と 0Ah は、2 つ合わせて改行を意味します。このように、db 文ではデータを数値で指定することもできます。たとえば 'H' は 48h または 72 と指定することもできます。

最後の「$」は、メッセージ文字列の終点を示す印です。これはファンクション 09h の規約です。


スタックの定義

    sseg  segment stack
    db   100h dup(?)
    sseg  ends
プログラムにはスタックが必要です。たとえば、このプログラムは int 命令を使用していますが、この割り込み命令は戻り先を記憶するためにスタックを利用します。

上のコードでは、スタック用のセグメントを属性 stack で宣言しています。こうすると、プログラムの開始前に、ss:sp がこのセグメントの末尾を指すようにセットされるので、作成するプログラム コードで ss:sp を初期化する必要がなくなります。

スタック領域の大きさは、db 文で決定しています。ここでは 256 バイト確保していますが、これだけあればちょっとしたアセンブラ プログラムには十分です。


ソース ファイルの終了

    end    start
ソース ファイルの最後には、必ず end 文が必要です。同じソース ファイル内にプログラムの実行開始地点がある場合は、end 文にそのラベルを指定します。つまり、この例のように 1 つのソース ファイルだけで 1 つの実行ファイルを作成する場合は、必ず end 文にパラメータを指定することになります。hello.asm では、先に定義しておいた「start」を指定しています。

1997/5/29 created