MS-DOS MASM プログラミング入門

Written by けんいち

Updated : 2004/08/20 - 03:22:24 , Ver 0.04


0 はじめに MASMプログラミングをはじめるにあたって
+ MASMの手に入れ方
+ MS-DOSコマンド
+ バッチファイル
+ リダイレクト
+ パイプ
+ Makeをつかう
+ MASMの参考書
1 MASMプログラムの例 MS-DOSでのMASMプログラム例
+ MS-DOSプログラム例
+ ファイルを分割する
+ 簡略化しないプログラム
2 MASM擬似命令 MASMの擬似命令
+ 擬似命令の種類
+ 擬似命令を使った例
+ 別の擬似命令を使った例
+ MASMのデータタイプ
3 CPUの仕組み CPUの仕組み
+ レジスタとメモリ
+ マシン語命令
+ プログラムの例1
+ プログラムの例2
4 INT21H MS-DOSファンクションコール DOSのソフトウェア割り込みについて
+ システムコール
+ 入出力ファンクションコール
+ 入出力を使ったプログラム1
+ 入出力を使ったプログラム2
+ ディレクティブ
+ メモリ管理
+ ファイル管理
+ その他ファンクションコール
+ エラーコード表
5 C言語とアセンブラの協調 C言語とアセンブラコードを共生する場合
+ Cからアセンブラコード呼び出し
+ C言語のデータ型
+ アセンブラからC言語コードの呼び出し
6 LSICでリンクするには LSICとMASMコードの共生
+ はじめに
+ LSICのインストール
+ LSIC問題点
+ LSICでのクロスリンク
7 COMファイルについて COMファイルについて
+ COMファイルについて
+ COMファイル作成
+ メモリモデル

Chapter 0   はじめに

Abstract : MASMプログラミングをはじめるにあたって

Chapter 0 - 1   MASMの手に入れ方 : Updated on 2004/06/25

ここでは、8086 CPUのアセンブラを紹介します。アセンブラには、MASMを使いますので もろもろのサイトからダウンロードしてきてください。一応、下に示しておきましょう。

リンク 解説
MASM32 Site MASM のみを手に入れるなら、ここから入手できます。
Intel CPU マニュアル PDF ファイルで上・中・下の三冊あります。
16ビットリンカ 16ビットのリンカです。

必要な物を落としてきてください。リンクについては2004/06/25現在、存在を確認しているものです。

(注意!!!)ここで解説するのは、Dosのリアルモードで動く16ビットアセンブラを解説していきます。 MS-DOS5.0/MS-DOS6.2/Windows95/98など では実行できますが、Windows2000/XPなどではこの解説のプログラムを作成したとしても実行は出来ません。 Windows2000/XPでアセンブラをしてみたい方は、Win32の方のアセンブラの解説を見てみてください。

さてアセンブラソースファイルのファイルには拡張子として ASM を使います。 ソースファイルをアセンブルするには、MASM 本体の ml.exe を使います。

% ml /c first.asm

ファイル名が first.asm の場合は、上のようにします。そうすると、first.obj というオブジェクトファイルが作成されます。オブジェクトファイルとは、 ソースファイルを実行コードに書き直したものです。しかし、オブジェクトファイルでは実行はできません。 このオブジェクトファイルをリンカにかけて実行ファイルに変換します。

% link first;

これで実行ファイルを作成することができます。もちろんリンカは16bitコード用の リンカでなければなりません。

実行ファイル名を変えるには、次のように指定します。

% link first, main;

これで実行ファイル名は、main.exe になります。

ml.exe と link.exe を実行するためには、実行ファイルにパスを通しておかなければなりません。 パスを通すには、autoexec.bat を変更しなければなりません。例えば ml.exe と link.exe が c:\bin に存在したとすると、 次のように追加します。

SET PATH=C:\BIN;%PATH%

autoexec.bat の変更は、スタートメニューの「ファイル名を指定して実行」から、Windows98の場合 sysedit もしくは msconfig を指定すると、変更することができます。ほかの情報は絶対変更しないでください。 Windows の誤作動の原因になります。そうなった場合、私は責任を取れませんので。 また、パスが有効になるのは、windowsを再起動してからです。再起動が面倒くさいという型は、 うえのコマンドを直接入力することでもパスを設定することが出来ます。

アセンブラのプログラミングは、自己責任で行ってください。 ここに載っているプログラムを実行したことによる損害などが生じても、一切責任はもてません。

Chapter 0 - 2   MS-DOSコマンド : Updated on 2004/06/25

MS-DOSプロンプトの使い方を良く知らない人もいると思うので、ここで紹介しておきましょう。 MS-DOSでは、元はプログラム開発用のOSなので、いろいろと便利な機能がついています。 一部ですが、ここでいろいろ示しておきます。

MS-DOS開発でよく使う一般コマンド

コマンド 説明
COPY ファイルのコピー
CD カレントディレクトリの変更
DEL ファイルの削除
DIR ファイルの表示
MKDIR ディレクトリの作成
MORE ファイルの内容表示(一画面ずつ)
REN

ファイル名の変更

RMDIR ディレクトリの削除
SET 環境変数のセット
START Windows上でファイルを実行
TYPE テキストファイルの内容表示

一つ一つのコマンドのヘルプを見るには、/? オプションをつけることで、見ることが出来ます。

また特殊なファイル名として、CON、PRN、AUX などが予約されています。CON は直接コンソール入力、 PRNはプリンタ、AUXは補助出力となっています。

たとえば、直接コンソール入力からファイルを作成したい場合には、次のようにすることも出来ます。

% copy con test.txt
This is a test(Return)
^Z(Ctrl-Z)

% more test.txt
This is a test

簡単なバッチファイル(次で説明)を作成するときには非常に便利です。 また、DIRについては、環境変数DIRCMDでオプションをあらかじめ指定しておくことが出来ます。

Chapter 0 - 3   バッチファイル : Updated on 2004/06/25

バッチファイルとは、いくつかのコマンドをまとめて処理するファイルのことです。拡張子には BAT をつけます。例えば次のようなものです。

ml myfile.asm
link myfile;
del myfile.obj

というコマンド群を一つのファイル mymake.bat というファイルに書き込んでおいて実行することでいっぺんにコマンドを実行してくれます。make コマンドを使うまでもない簡単なプログラムを作成する場合には、バッチファイルは非常に便利です。バッチファイル内で使えるバッチコマンドもいくつかあるので、示しておきましょう。

REM コメント、注釈
ECHO

文字の出力、もしくはその制御

IF 条件分岐
GOTO バッチファイル内の指定したラベル位置へジャンプ

スクリプトを書いてみるとこんな感じになります。

@echo off
echo ファイルが存在した場合実行ファイルを作る

if exist myfile.asm goto MAKEFILE
exit

MAKEFILE:
ml myfile.asm
link myfile;
del myfile.obj

echo 実行ファイルを作成しました

最初の@は、自分自身を表示しないようにする命令です。

Chapter 0 - 4   リダイレクト : Updated on 2004/06/25

MS-DOSの機能の一つにパイプとリダイレクトがあります。まずリダイレクトについて説明しましょう。 リダイレクトはファイルのデータを直接プログラムの標準入力に流したり、 プログラムの出力を直接、ファイルに保存したりすることが出来ます。入力リダイレクトには < を、出力リダイレクトには > と >> を使います。 >> の方は、 アペンドモードで出力します。アペンドモードの場合、ファイルの最後に標準出力からの結果を 付け足します。 たとえば、次の例はDIRコマンドの出力を指定したファイルに流します。

% DIR > dir.txt

こうすることで、DIR の出力がdir.txtに保存されます。> を >> にすることで、dir.txt に出力をアペンド(結合)します。ようするにdir.txtのファイルの最後に出力されたテキストを結合することが出来ます。

Chapter 0 - 5   パイプ : Updated on 2004/06/25

パイプは、標準出力を他のコマンドにファイルを介することなく直接渡します。 パイプには | を使います。次の例は、出てくるメッセージを1ページごとに分割して表示します。

% mycmd.exe | more

mycmd.exe が出力するメッセージを1ページごとに区切って出力します。

Unix / Linux を使ったことがある人なら次のようなコマンドを使ったことがありますよね。

% nkf -e < result.txt | txt2ps | lpr -Ppr101

このコマンドの場合、まずnkfの標準入力にresult.txtの内容を流します。nkfは文字コードを変える プログラムで、この場合-eが指定されているのでEUCに変換されます。その変換されたものが、 標準出力で出力されます。この標準出力で出力されたものは、パイプでtxt2psの標準入力に結合されます。 txt2psは、テキスト文章をPostScript形式に変換するプログラムです。標準出力にPostScript形式の データが出力されます。またパイプをとおって、txt2psの標準出力がlprの標準入力に結合されます。 txt2psからはPostScriptのデータが出力されるので、標準入力にはPostScriptのデータが入ります。 lprは印刷を行うプログラムでpr101というプリンタに入ってきたデータの出力を行います。

というのがパイプの解説です。

Chapter 0 - 6   Makeをつかう : Updated on 2004/06/25

執筆中・・・

Chapter 0 - 7   MASMの参考書 : Updated on 2004/07/09

参考書としては、いくつかありますが、 有名なものをいくつか挙げておきましょう。

  • はじめて読む8086 (アスキー)
  • はじめて読むMASM (アスキー)
  • はじめて読む486 (アスキー)
  • 最近は、いろいろな雑誌でアセンブラ講座も開かれているようです。 そちらを参照してもいいと思います。


    Chapter 1   MASMプログラムの例

    Abstract : MS-DOSでのMASMプログラム例

    Chapter 1 - 1   MS-DOSプログラム例 : Updated on 2004/06/25

    DOS上でどんな感じのプログラムになるか、実際にプログラムを見てみましょう。

    ;******************************************************************************
    ; 一番最初のプログラム
    ;   このプログラムは簡略化セグメントを使っています。
    ;******************************************************************************
    
    .model small, c
    .486
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
    .data
    
    MSG db  'ハローワールド', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    .code
    
    .startup    ; スタートアップコード作成
    
        mov  ax, _DATA
        mov  ds, ax
    
        mov  dx, offset MSG
        mov  ah, 09h
        int  21h
    
    .exit       ; 終了コード作成
    
    end

    簡単なプログラムです。実行してみてください。「ハローワールド」と表示されたでしょうか。 このプログラムの.486 や db などは擬似命令と呼ばれます。 この命令は、アセンブルするときのMASMへの命令です。どのようにアセンブルすればいいかを擬似命令で MASMに指定します。またmov や int はマシン語命令(機械語命令)と呼ばれ、コンピュータに動作させる命令です。 このコードが実際に実行されます。アセンブルされるとバイナリコードに変換されますが、これでは 普通の人に読めません。そのため、バイナリコードに対応付けられた分かりやすいコード、movとかaddとかを ニーモニックといいます。

    とりあえずは、雰囲気をつかむことが重要でしょう

    /cスイッチは、オブジェクトファイルを作らせるスイッチです。このスイッチをつけないと、 実行ファイルまで作ろうとして、リンクまでしようとします。このコードは16ビット用のコードなので、 リンクをするときにエラーがでてしまいます。リンクをするときには16ビット用のリンカを使わなければ、 なりません。なので、/cスイッチを指定してリンクまでしないようにします。

    アセンブラのときに、/Fl スイッチを指定するとリストファイルが作成されます。 このファイルはどういうものかというと、どのようにアセンブラされたかという結果がファイルに保存されます。

    アセンブルするときに次のように指定してください。

    % ml /c /Fl first.asm

    これで拡張子が LST のリストファイルが作成されます。実際にどのようなものかというと、 次のようになります。

    Microsoft (R) Macro Assembler Version 6.14.8444	10/03/00 20:01:38
    .\first.asm	Page 1 - 1
    
    ;******************************************************************************
    ; 一番最初のプログラム
    ; このプログラムは簡略化セグメントを使っています。
    ;******************************************************************************
    
    .model small, c
    .486
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
    0000	.data
    
    0000 83 6E 83 8D 81 5B	MSG DB 'ハローワールド', 0dh, 0ah, '$'
         83 8F 81 5B 83 8B
         83 68 0D 0A 24
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    0000	.code
    
    .startup ; スタートアップコード作成
    
    0010 B8 ---- R	mov ax, _DATA
    0013 8E D8	mov ds, ax
    
    0015 BA 0000 R	mov dx, offset MSG
    0018 B4 09	mov ah, 09h
    001A CD 21	int 21h
    
    .exit ; 終了コード作成
    
    end
    
    Microsoft (R) Macro Assembler Version 6.14.8444	10/03/00 20:01:38
    .\main3.asm	Symbols 2 - 1
    
    
    
    
    Segments and Groups:
    
    Name                              Size    Length  Align  Combine Class
    
    DGROUP . . . . . . . . . . . . .	GROUP
    _DATA . . . . . . . . . . . . .	16 Bit	0011	Word	Public 'DATA'	
    _TEXT . . . . . . . . . . . . .	16 Bit	0020	Word	Public 'CODE'	
    
    
    Symbols:
    
    N a m e Type Value Attr
    
    @CodeSize . . . . . . . . . . . 	Number	0000h	
    @DataSize . . . . . . . . . . . 	Number	0000h	
    @Interface . . . . . . . . . . .	Number	0001h	
    @Model . . . . . . . . . . . . .	Number	0002h	
    @Startup . . . . . . . . . . . .	L Near	0000	_TEXT	
    @code . . . . . . . . . . . . . 	Text _TEXT
    @data . . . . . . . . . . . . . 	Text DGROUP
    @fardata? . . . . . . . . . . . 	Text FAR_BSS
    @fardata . . . . . . . . . . . .	Text FAR_DATA
    @stack . . . . . . . . . . . . .	Text DGROUP
    MSG . . . . . . . . . . . . . . 	Byte	0000	_DATA	
    
    0 Warnings
    0 Errors
    

    リストファイルを見ることで、どのようにアセンブルされたかわかるので、 必要に応じて使っていきましょう。

    Chapter 1 - 2   ファイルを分割する : Updated on 2004/06/25

    プログラムを書いていると、内容が長すぎるファイルを分割したい場合があります。 たとえば、アセンブルするときにちょこっとしか変更していないのに毎回アセンブルしていると 時間がかかってしまうことがあります。そういう場合に分割を行ったりするのです。そのような場合には、 分割アセンブルという方法を使います。ソースを分割してアセンブルして、 リンクでオブジェクトファイルを統合します。

    実際にmain.asm と sub.asm に分割されていた場合を考えてみましょう。 アセンブルするときにはそれぞれ別々にアセンブルしますが、リンクのときには次のように指定して リンクを行います。

    link main+sub;

    これで両方のオブジェクトファイルを統合したプログラムが作成できます。この場合、 実行ファイルは一番最初のオブジェクトファイルの名前 main.exe になります。

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

    ;******************************************************************************
    ; 分割プログラムの例 <main.asm>
    ;   こちらはメインです
    ;******************************************************************************
    
    .model small
    .486
    
    ;******************************************************************************
    ; 外部プロシージャのプロトタイプ
    ;******************************************************************************
    
    extern  sub_proc:near
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
    .data
    
    MSG db  'メインから表示しています', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    .code
    
    .startup
    
        mov  ax, _DATA
        mov  ds, ax
    
        mov  dx, offset MSG
        mov  ah, 09h
        int  21h
    
        call sub_proc  ; ほかのファイルにあるプロシージャを呼び出し
    
        mov  dx, offset MSG
        mov  ah, 09h
        int  21h
    
    .exit
    
    end

    もうひとつのプログラムは以下のようになっています。

    ;******************************************************************************
    ; サブファイル <subproc.asm>
    ;   このファイルにはメインプロシージャから呼び出される
    ;   sub_procが定義されています。
    ;******************************************************************************
    
    .model small
    .486
    
    ;******************************************************************************
    ; プロシージャの定義
    ;******************************************************************************
    
    public  sub_proc
    
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
    .data
    
    MSG2 db  'サブプロシージャから表示しています', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    .code
    
    sub_proc    proc
    
        mov  dx, offset MSG2
        mov  ah, 09h
        int  21h
    
        ret
    
    sub_proc    endp
    
    end

    これを分割アセンブルして、リンクを行うと文字列が表示されます。ちゃんと表示されましたよね。

    MASMの世界では、関数のことをプロシージャと呼びます。 プロシージャは、proc - endp 構造で定義されます。必要なときに使っていきましょう。

    このプロシージャを外部から参照したいときにはそれを明示しないといけません。 そのためこのプログラムでは、public 擬似命令を使っています。これによって、プロシージャは外部から参照(呼び出し可能)になります。

    ほかのソースファイルにあるプロシージャを呼び出すには、extern 擬似命令を使ってそのプロシージャ使用するソースの中で、あらかじめ定義しておかなければなりません。 例題2の場合では、sub_proc プロシージャを定義しています。near というのは、プロシージャの呼び出しが 同一セグメント間であることを示しています。

    メモリは、プログラムを実行するときにセグメントという単位に分割されます。データセグメント には基本的にデータがあります。コードセグメントには、実行コードがあります。上の プログラムの場合は、プロシージャが同一セグメント内にあるのでニアコール( 近距離呼び出し)を行っています。

    もしセグメントが違う場合には、ファーコール(遠距離呼び出し)を 行わなければなりません。ファーコール指定には far を使います

    Chapter 1 - 3   簡略化しないプログラム : Updated on 2004/06/25

    以上の例では、簡略化セグメントという方式を使ってきました。簡略化することで、 簡単にMASMのプログラムを書くことが出来ました。本当はMASMを使う場合、 もう少し面倒な定義をしなければなりません。一番最初のプログラムを書き直して見ましょう。

    ;******************************************************************************
    ; 一番最初のプログラム
    ;   このプログラムは簡略化セグメントを使っています。
    ;******************************************************************************
    
    .486
    
        assume cs:CODE, DS:CODE
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    CODE  segment
    
        org 100h
    
    MSG db  'ハローワールド', 0dh, 0ah, '$'
    
    start:
        mov  ax, CODE
        mov  ds, ax
    
        mov  dx, offset MSG
        mov  ah, 09h
        int  21h
    
        mov  ah, 4ch
        mov  al, 00h
        int  21h
    
    CODE  ends
    
    end  start
    
    	

    Chapter 2   MASM擬似命令

    Abstract : MASMの擬似命令

    Chapter 2 - 1   擬似命令の種類 : Updated on 2004/07/09

    それでは、擬似命令について説明していきましょう。擬似命令とは、 アセンブルをするときにMASMに対して出す命令のことです。この種類には色々あります。 たとえば、セグメントの定義だったり、使用するCPUの定義だったり…。 マクロの指定などもできます。どのような、擬似命令があるか見てみましょう。

    基本的な擬似命令 解説
    DB DW DD DQ DT 変数を定義する DB : 1byte DW : 2byte DD : 4byte DQ : 8byte DT : 10byte
    END プログラムを終了し、スタートアドレスを割り当てる
    EQU シンボルに値を割り当てる
    EXTERN ほかのモジュール内にあるシンボルを定義する
    PUBLIC シンボルがほかのモジュールから参照可能であるように定義する
    INCLUDE ほかのアセンブラソースファイルを挿入する
    PROC - ENDP プロシージャを定義する
    STRUC - ENDS 構造体を定義する
    MACRO - ENDM マクロを定義する
    LOCAL 局所変数を定義する
    PROTO プロシージャのプロトタイプ宣言
    INVOKE 引数つきプロシージャの呼び出し

    とりあえず、これだけ知っておけば何とかなるでしょう。 シンボルとは、プロシージャ名や変数名のことです。

    プログラムの先頭で、プロセッサ指定もすることができます。 指定したマイクロプロセッサの範囲内の命令が使われます。

    マイクロプロセッサ指定文 解説
    .8086 8086と8087の命令
    .80186 80186と8087の命令
    .286 80286と80287の命令
    .386 80386と80387の命令
    .486 80486と80387の命令
    .586 Pentium の命令
    .NO87 数値演算プロセッサ使用不可

    必要なものを使ってください。次は簡略化セグメント定義のための命令です。 きちんとしたセグメントの記述の仕方があるのですが、それは少しめんどくさいので、 簡略化されたセグメントの記述の仕方があります。

    簡略化セグメントを使うには、.model 文を使います。ここでは、メモリモデルとして small のみを使います。ほかにも、tiny, medium, compact, large, huge, flat などがありますが、 ここでは説明しません。メモリモデルの指定には、次のように指定します。

    .model small
    .486

    これを記述したら、簡略化セグメントが記述可能です。次の命令を使ってセグメントを記述していきます。

    簡略化セグメント命令 解説
    .startup スタートアップコードを作成
    .exit プログラム終了コードを作成
    .code コードセグメントを定義(セグメント名は_TEXT)
    .data データセグメントを定義(セグメント名は_DATA)
    .stack スタックセグメントを定義(セグメント名はSTACK)

    通常は、コードセグメントに実行コードがあり、 データセグメントにデータが定義されています。スタックセグメントはスタックの退避用に使われます。

    Chapter 2 - 2   擬似命令を使った例 : Updated on 2004/07/09

    とりあえず、プログラムを実際に見てみましょう。

    ;******************************************************************************
    ; 擬似命令の例
    ;******************************************************************************
    
    .model small
    .486
    
    ;******************************************************************************
    ; 定数定義
    ;******************************************************************************
    
    MIN     equ    '0'
    MAX     equ    '9'
    
    TRUE    equ    1
    FALSE   equ    0
    
    ;******************************************************************************
    ; マクロ定義
    ;******************************************************************************
    ; キーボードから一文字入力
    ; AL に入力された文字が代入
    ;******************************************************************************
    
    fc_read_kbd  macro 
    
        mov     ah, 08h
        int     21h
    
                 endm
    
    ;******************************************************************************
    ; ディスプレイに文字列を表示
    ;******************************************************************************
    
    fc_display   macro string
    
        mov     dx, offset string
        mov     ah, 09h
        int     21h
    
                 endm
    
    ;******************************************************************************
    ; 変数定義(データセグメント)
    ;******************************************************************************
    
    .data
    
    InChar    db  0
    MSG       db  '数字を入力してください', 0dh, 0ah, '$'
    ERROR1    db  '数字ではありません!!', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; スタックセグメント
    ;******************************************************************************
    
    .stack    ; スタックの大きさを指定しない場合はデフォルトで1Kバイトになる
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    .code
    
    isNumber   proc
    
        cmp     InChar, MIN
        jb      NOT_NUM       ; InChar < MIN ならジャンプ
        cmp     InChar, MAX
        ja      NOT_NUM       ; InChar > MAX ならジャンプ
    
        mov     ax, TRUE
        ret
    
    NOT_NUM:
        mov     ax, FALSE
        ret
    
    isNumber   endp
    
    ;******************************************************************************
    
    ; ここからプログラムはスタート
    
    ;******************************************************************************
    
    	.startup
    
        fc_display  MSG  ; メッセージを表示
    
    MAINLOOP:
    
        fc_read_kbd      ; キーボードから1文字入力
    
        mov     InChar, al
        call    isNumber ; 入力されたキーが数字かどうか評価
    
        cmp     ax, FALSE
        je      ERRORMSG
    
    	.exit
    
    ERRORMSG:
    
        fc_display  ERROR1
        jmp         MAINLOOP
    
    end

    このプログラムは、数字の入力を求めるプログラムです。数字を入力するとプログラムは終了します。 数字の評価チェックにグローバル変数を使うというかなりあくどい事をやっていますが、 一応擬似命令の説明なので、 良しとしました。じつはプロシージャには引数を指定できます。引数といっても、 スタックを利用するだけです。

    Chapter 2 - 3   別の擬似命令を使った例 : Updated on 2004/07/09

    次のプログラムは前のプログラムに対して、引数を使ったプログラムです。 プログラムが何をやっているかわからなくても、擬似命令のだいたいの役割はわかるでしょう。 一応、引数を使った呼び出しをするプログラムも載せておきます。赤い部分が変更した部分です。

    ; データセグメントより上は変更していないので、下の部分のみを変えてください。
    
    ;******************************************************************************
    ; 変数定義(データセグメント)
    ;******************************************************************************
    
    .data
    
    ; InChar    db  0
    MSG       db  '数字を入力してください', 0dh, 0ah, '$'
    ERROR1    db  '数字ではありません!!', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; スタックセグメント
    ;******************************************************************************
    
    .stack    ; スタックの大きさを指定しない場合はデフォルトで1Kバイトになる
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    .code
    
    
    ; プロシージャの引数の引渡しには、スタックを用いている
    ; プロシージャの呼び出しの方法にはC言語の呼び出し規約を用いている
    
    isNumber   proc c InChar:byte
    
        cmp     InChar, MIN
        jb      NOT_NUM       ; InChar < MIN ならジャンプ
        cmp     InChar, MAX
        ja      NOT_NUM       ; InChar > MAX ならジャンプ
    
        mov     ax, TRUE
        ret
    
    NOT_NUM:
        mov     ax, FALSE
        ret
    
    isNumber   endp
    
    ;******************************************************************************
    ; ここからプログラムはスタート
    ;******************************************************************************
    
    	.startup
    
        fc_display  MSG  ; メッセージを表示
    
    MAINLOOP:
    
        fc_read_kbd      ; キーボードから1文字入力
    
        invoke  isNumber, al ; 引数を伴ったプロシージャの呼び出し
    
        cmp     ax, FALSE
        je      ERRORMSG
    
    	.exit
    
    ERRORMSG:
    
        fc_display  ERROR1
        jmp         MAINLOOP
    
    end

    ものすごく簡単でしょ?案外こっちのほうが見やすいかもしれません。 同じプログラムをC言語で作ろうと思ったら、ライブラリやなんかを積むので 結構実行ファイルは大きくなってしまいます。けれども、MASMで作成してみるとなんと かなり軽いプログラムが出来ます。

    詳しい擬似命令の説明はしてないけど、それらについては、 オンラインヘルプを読むか、参考書を参考にしてください。

    Chapter 2 - 4   MASMのデータタイプ : Updated on 2004/07/09

    マシン語命令をやる前に、簡単にMASMと8086の規則について説明しておきましょう。

    その前に、アセンブルでの数値表現を示しておきます。MASM では、普通の数字は10進数として扱われます。16進数を表現したい場合には数字の後に H をつけます。また実数も扱うことができます。

    NUM1   EQU   123  ; 10進数
    NUM2   EQU   43h  ; 16進数
    REAL1  EQU   1.00 ; 実数
    PI     EQU   3.14 ; 実数

    次にシンボルについてですが、シンボルとはユーザーが定義した値や プロシージャ名のことを指します。シンボルの名前付け規則にはいくつかのルールがあります。 シンボルには<アルファベット、@、$、_、?、数字>を使うことができますが、先頭に数字を 使ってはいけません。また擬似命令の名前をシンボルとして定義することはできません。

    次にデータ型を示しますが、データ型には、次の8つがあります。

    MASMで使うデータ型 解説 バイト数
    BYTE 整数型 1 byte
    WORD 2 byte
    DWORD 4 byte
    QWORD 8 byte
    TBYTE 10 byte
    REAL4 単精度浮動小数点型:実数 4 byte
    REAL8 倍精度浮動小数点型:実数 8 byte
    REAL10 テンポラリ実数型  

    Chapter 3   CPUの仕組み

    Abstract : CPUの仕組み

    Chapter 3 - 1   レジスタとメモリ : Updated on 2004/07/09

    マシン語のプログラムを実行するときに計算値を保存するのがレジスタです。 レジスタには次の種類があります。

    汎用レジスタ 名称 解説
    AX アキュムレータ 累加算
    BX ベースレジスタ 補助計算
    CX カウントレジスタ 補助計算/ループカウント
    DX データレジスタ 補助計算/データ
    SI ソースインデックス 補助計算/データ領域操作
    DI

    デスティネーションインデックス

    補助計算/データ領域操作
    汎用レジスタ(特殊) 名称 解説
    BP ベースポインタ スタック操作
    SP スタックポインタ スタック操作
    IP インストラクションポインタ 命令フェッチ
    セグメントレジスタ 名称 解説
    CS コードセグメント 実行コードエリア
    DS データセグメント データエリア(転送の場合はソース)
    ES エキストラセグメント データエリア(転送の場合はデスティネーション)
    SS スタックセグメント スタックエリア
    フラグレジスタ 名称 解説
    FLAGS フラグレジスタ CPU状態記述

    レジスタにはこれだけあります。これは8086のレジスタの数であって、 上位CPUになるともっと増えます。すべて16ビットレジスタです。AX、BX、CX、DXレジスタは、 上位8ビットと下位8ビットを分けて使うことができます。AXレジスタの上位8ビットはAHレジスタ(AX Higher)、下位レジスタはALレジスタ(AX Lower)です。BXレジスタ以下も同じように命名されます。 (BH、BL、CH、CL、DH、DLなど)

    汎用レジスタは、なんでもかんでも好き勝手に使っていいわけではありません。ある程度は利用法が決まっているので、できるだけ用途に応じた使い方をしましょう。そのほうが他のプログラマのためにも、見やすいコードがかけます。

    特殊汎用レジスタは、BP、SP、IPの3つのレジスタがあります。BPとSPはスタック操作に使われます。IPは現在実行中のコードのアドレスの位置です。

    セグメントレジスタは、用途に応じて4つあります。プログラム実行中には、 メモリは「セグメント」という単位に分割されます。これによって、メモリを独立した非線形 メモリとして扱うことができます。それぞれのセグメントの用途は表にある通りです。 特定のセグメント内のメモリにアクセスする場合は、次のようにメモリを指定します。

    CS:DX  ←  10h ; コードセグメントにあるDX番地に10Hを書き込むという命令
    SS:SP          ; スタックはSS:SP番地につまれていく

    フラグレジスタとは、CPUの状態を保存しているレジスタです。次の種類があります。

    フラグの種類 名称 解説
    OF オーバーフローフラグ 符号付演算の桁上がりが起こるとセット
    DF ディレクションフラグ メモリ転送命令の転送方向を示す
    SF サインフラグ 演算結果の符号を示す
    ZF ゼロフラグ 演算結果が0であればセット
    AF 補助キャリーフラグ BCD演算用キャリーフラグ
    PF パリティフラグ 演算結果が偶数だとセット
    CF キャリーフラグ 演算結果が桁上がりが起こるとセット

    ほかにもいくつかありますが、割愛します。インテルのマニュアルを参照してください。

    メモリは、前にも書いてあるように実行中にセグメントに分割されます。本当のところを言うと、 16ビットアセンブラではセグメントは最初にすでに分割してあります。OSはそのセグメントにデータを割り当てるだけなのですね。 8086CPUでは、1M(FFFFFH)までしかメモリを使うことができません。メモリはフラットな線形空間ですが、これをどのようなセグメントに分けて、メモリを管理しているかということについてはここでは割愛させてもらいます。

    とりあえず、「セグメントとは、 メモリを分割してその分割したメモリを独立して扱うもの」と覚えておいてください。 8086に特有のセグメント計算を覚えたところで、上位機種に移行する場合、その知識は大して役に立ちません。 それでも知りたいという方は、関連書籍を参考にしていただければいいと思います。

    Chapter 3 - 2   マシン語命令 : Updated on 2004/07/09

    さて次は、マシン語命令(ニーモニック)について説明します。実際のマシン語は16進数でかかれています。それをアルファベットで表した表現をニーモニックといいます。アセンブラを記述するときには16進数で記述していくのは大変なのでニーモニックをこつこつと入力していくことになります。それでは実際にどのようなニーモニックがあるか紹介していきましょう。

    まずは、代入命令です。レジスタに値を代入します。mov と書きます。MOVE の略ですね。

    mov  ax, 0dh  ; ax レジスタに 0dh を代入
    mov  dx, ax   ; dx レジスタに ax レジスタの値を代入
    mov  MEMORY , ax  ; メモリ上に ax レジスタの値を代入
    英語 解説 データ転送命令
    mov Move data 代入命令
    xchg Exchange レジスタ交換命令

    足し算です。足し算には、2つあります。add と adc です。 add は普通に足し算を行います。adc は Add with Carry の略で、キャリーフラグの値もいっしょに足します。 adc は桁上がりを考慮した足し算というわけです。

    add  ax, bx  ; ax レジスタと bx レジスタを足す。結果は ax レジスタに…

    その他算術演算命令には以下のものがあります。

    算術演算命令 英語 解説
    add Add 加算
    adc Add with carry キャリー付き加算
    cmp Compare 比較演算
    dec Decrement 1減算
    inc Increment 1加算
    div Divine 除算
    idiv Divine with sign 符号付割り算
    mul Multiply 乗算
    imul Multiply with sign 符号付割り算
    sub Substract 減算
    sbb Substract with carry キャリー付き減算

    詳しい使い方については、インテルのアーキテクチャマニュアルでも見てください。 できることなら、英語と関連させて覚えたほうが覚えやすいですよ。

    次はジャンプ命令です。以下のものがあります。

    ジャンプ命令のニーモニックは絶対英語で覚えたほうがいいです。 でなければ絶対覚えられるものではありません。

    単純ジャンプ命令 英語 解説
    jmp Jump 無条件ジャンプ
    jc Jump if carry flag is set キャリーフラグがセットされていたらジャンプします
    jnc Jump if carry flag is not set キャリーフラグがセットされていなかったらジャンプします
    jcxz Jump if CX is zero CXレジスタがゼロならジャンプします
    je / jz Jump if equal / Jump if zero flag is set ゼロフラグがセットされていたらジャンプします
    jne / jnz Jump if not equal / Jump if zero flag is not set ゼロフラグがセットされていなかったらジャンプします
    符号なし整数比較結果ジャンプ命令 英語 解説
    ja / jnbe Jump if above / Jump if neither below or equal cmp op1 , op2 という命令で、op1 > op2 (above) ならばジャンプします
    jb / jnae Jump if below / Jump if neither above or equal cmp op1, op2 という命令で、op1 < op2 (below) ならばジャンプします
    jae / jnb Jump if above or equal / Jump if not below cmp op1, op2 という命令で、op1 >= op2 (above or equal) ならばジャンプします
    jbe / jna Jump if below or equal / Jump if not above cmp op1, op2 という命令で、op1 <= op2 (below or equal) ならばジャンプします
    符号あり整数比各結果ジャンプ命令 英語 解説
    jg / jnle Jump if greater / Jump if neither lower or equal cmp op1, op2 という命令で、op1 > op2 (greater) ならばジャンプします
    jl / jnge Jump if lower / Jump if neither greater or equal cmp op1, op2 という命令で、op1 < op2 (lower) ならばジャンプします
    jge / jnl Jump if greater or equal / Jump if not lower cmp op1, op2 という命令で、op1 >= op2 (greater or equal) ならばジャンプします
    jle / jng Jump if lower or equal / Jump if not greater cmp op1, op2 という命令で、op1 <= op2 (lower or equal) ならばジャンプします
    ループ命令 英語 解説
    loop Loop while cx is not zero cx レジスタの値を1減じて、0になるまで繰り返す
    loope / loopz Loop while cx is not zero and zero flag is set cx レジスタの値を1減じて、ゼロフラグがセットされている間繰り返す
    loopne / loopnz Loop while cx is not zero and zero flag is not set cx レジスタの値を1減じて、ゼロフラグがセットされていない間繰り返す
    コール命令 英語 解説
    call Call procedure プロシージャ呼び出し
    ret Return プロシージャから復帰

    それでは次は論理演算命令です。

    論理演算命令 英語 解説
    and Logical and 論理積命令
    or Logical or 論理和命令
    not Logical not 論理否定命令
    neg Negation 符号反転命令
    xor Logical exclusive or 排他的論理和命令
    test Logical compare 論理和積テスト命令 / 実際にはレジスタの値に変化なし

    次にスタック命令とフラグ命令についてみていきます。

    スタック命令 英語 解説
    push Push into the stack スタックへデータ退避
    pop Pop out of the stack スタックからデータ取り出し
    pushf Push flags into the stack スタックへフラグを退避
    popf Pop flags out of the stack スタックからデータ取り出し
    フラグ操作 英語 解説
    std Set direction flag ディレクションフラグをセット
    cld Clear direction flag ディレクションフラグをクリア
    stc Set carry flag キャリーフラグをセット
    clc Clear carry flag キャリーフラグをクリア

    次はシフト・ローテート命令です。ビット演算子ですね。

    シフト命令 英語 解説
    SHL Shift left 論理左シフト
    SHR Shift right 論理右シフト
    SAL Shift arithmetic left 算術左シフト
    SAR Shift arithmetic right 算術右シフト
    ローテート命令 英語 解説
    ROL Rotate left 左ローテート
    ROR Rotate right 右ローテート
    RCL Rotate thru carry left 左キャリーローテート
    RCR Rotate thru carry right 右キャリーローテート

    とりあえず、英語と関連付けて覚えたほうが覚えやすいですよ。ここでだらだらと、 表を見せても何にもなりませんね。実際にプログラムを見たほうが早いかもしれません。 次では、実際にプログラムを見ていきましょう。

    Chapter 3 - 3   プログラムの例1 : Updated on 2004/07/09

    それでは、前のものを踏まえたうえで簡単なプログラムを見ていきましょう。

    ;******************************************************************************
    ; 簡単なプログラム
    ;******************************************************************************
    
    .model small
    .486
    
    ;******************************************************************************
    ; 定数
    ;******************************************************************************
    
    ANSWER  equ  's'
    
    ;******************************************************************************
    ; マクロ
    ;   文字列の表示
    ;******************************************************************************
    
    fc_display   macro string
        mov  dx, offset string
        mov  ah, 09h
        int  21h
                 endm
    
    ;******************************************************************************
    ; マクロ
    ;   文字入力(エコーあり)
    ;   al : 入力された文字
    ;******************************************************************************
    
    fc_kbd_input macro
        mov  ah, 01h
        int  21h
                 endm
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MSG0    db      'パスコードを入力してください > ', '$'
    MSG1    db      'エラー:パスコードが違います', 0dh, 0ah, 0dh, 0ah, '$'
    MSG2    db      '成功 :正常に入力されました', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
        .code
        .startup
    
    MAINLOOP:
    
        fc_display MSG0
        fc_kbd_input
    
        cmp  al, ANSWER
        je   CORRECT
    
        fc_display MSG1
        jmp  MAINLOOP
    
    CORRECT:
    
        fc_display MSG2
        .exit
    
    end

    今までと同じようなプログラムです。でも読めるようにはなったでしょう。 fc_display が文字列の表示、fc_kbd_input がキーボードから一文字入力のマクロです。 それを考えれば、ものすごく簡単なプログラムでしょう?

    キーボードからの入力は AL レジスタに代入されますが、cmp で答えと比べています。 もし等しいなら CORRECT に飛びます。 je の e は equal の e ですよ。ジャンプ命令は、 英語と関連付けて覚えたほうが早く覚えられます。

    プログラムの概要については、わかりますよね簡単ですから。このプログラムを応用すれば、 パスワード入力用のプログラムを作ることができます。

    Chapter 3 - 4   プログラムの例2 : Updated on 2004/07/26

    プログラミングを勉強するのなら、どんどんプログラムを見ていったほうがよいでしょう。 今回のプログラムは、足し算をして10進数表示するプログラムです。

    
    ;******************************************************************************
    ; 10進表示プログラム
    ;******************************************************************************
    
    .model small
    .486
    
    ;******************************************************************************
    ; マクロ
    ;   文字列の表示
    ;******************************************************************************
    
    fc_display   macro string
         mov  dx, offset string
         mov  ah, 09h
         int  21h
                 endm
    
    ;******************************************************************************
    ; マクロ
    ;   文字入力(エコーなし)
    ;   al : 入力された文字
    ;******************************************************************************
    
    fc_kbd_input macro
         mov  ah, 08h
         int  21h
                 endm
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MSG0    db      '整数9と5の計算を行います', 0dh, 0ah, '$'
    MSG1    db      '答えは'
    NUM     dw      0
            db      'です', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
        .code
        .startup
    
    MAINLOOP:
    
        fc_display MSG0
        fc_kbd_input
    
        xor     ax, ax   ; ax := 0
    
        mov     al, '9'  ; アスキーコードで9と5の足し算を行います
        add     al, '5'
        aaa              ; アスキー補正:2進数をBCD値に変換
    
        rol     ax, 08h  ; ah と al を入れ替える
        add     ax, '00' ; アスキーコードに変換
        mov     NUM, ax  ; 変数に代入
    
        fc_display MSG1
        .exit
    
    end

    そういえば BCD 値とか説明していませんでした。 BCD値については、インテルのアーキテクチャマニュアルを参考にしてください。

    メモリに値を代入するときに、AH と AL の値が入れ替わります。つまりメモリに 代入された時点でバイトの並び方が、 AL AH の順になっています。それだと数字が逆になってしまうので、 ローテート命令を用いて、AL と AH の値を入れ替えています。

    このように、メモリに代入するときにレジスタの並び順が逆になることを、 リトルエンディアン方式といいます。

    このプログラムを改造すれば、キーボードから入力を求めて足し算して 結果を表示するようなプログラムの作成が可能ですね。


    Chapter 4   INT21H MS-DOSファンクションコール

    Abstract : DOSのソフトウェア割り込みについて

    Chapter 4 - 1   システムコール : Updated on 2004/08/16

    システムコールとは、DOSの内部プロシージャ(関数)を呼び出すことを指します。 呼び出しには、内部割込みという手法を使います。内部割込みには、int というマシン語命令を使います。0hから1FhまではBIOS割り込みとして指定されているので、 ソフトウェアの割り込みの場合20hから使用することが出来ます。よって、DOSの場合も20hから割り当てられています。 通常DOSの機能を使うには、システムコール内の 21h というファンクションコールを使います。 このシステムコールを使うことで、DOSのいろいろのシステムを使うことが可能になります。

    ここでは、21h 以外のシステムコールについては説明はしません。 市販の参考書などを参考にしてください。一応どのようなものがあるか示しておきましょう。

    システムコール 解説
    20H プログラムの終了
    21H ファンクションリクエスト
    22H 終了アドレス
    23H <CTRL-C>の抜け出しアドレス
    24H 致命的エラーによる中断アドレス
    25H アブソリュートディスクリード
    26H アブソリュートディスクライト

    ファンクションコールは次のように行います。

    mov     ah, xxh   ; xx には、ファンクションリクエスト番号
    int     21h       ; 内部割り込み

    ファンクションコールが失敗した場合、多くの場合はキャリーフラグが立ちます。 これは覚えておいたほうがいいでしょう。キャリーを使って、エラー処理をすることになります。 エラーコードはAXレジスタに保管されます。

    つぎから、ファンクションコールを説明していきましょう。

    Chapter 4 - 2   入出力ファンクションコール : Updated on 2004/08/16

    入出力を行うファンクションコールには、次のようなものがあります。

    番号 説明
    01H キーボード入力から1文字受け取り、その文字を出力デバイスに出力する
    02H 出力に1文字出力する
    03H 補助入力装置から1文字受け取る
    04H 補助出力装置に1文字出力する
    05H プリンタに1文字出力する
    06H 入出力から1文字入出力をおこなう
    07H 入力から1文字受け取る
    08H 入力から1文字受け取る。受け取った文字の出力はしない
    09H 出力に文字列を出力する
    0AH 入力から文字列を受け取る
    0BH 入力のバッファの状態を返す
    0CH 入力のバッファを空にして、入力から1文字受け取る

    順番に説明していきましょう。

    01H <文字入力:エコーあり>
    入力 AH = 01H
    出力 AL = 入力された文字
    02H <文字出力>
    入力 AH = 02H : DL = 出力する文字コード
    03H <補助入力>
    入力 AH = 03H
    出力 AL = 補助入力から入力された文字
    04H <補助出力>
    入力 AH = 04H : DL = 出力する文字
    05H <プリンタ出力>
    入力 AH = 05H : DL = 出力する文字コード
    06H <直接コンソール入出力> CTRL-C のチェックなし
    入力 AH = 06H: : DL = FF の場合はコンソールから入力 / DL /= FF の場合は DL を出力
    出力 AL = 入力された文字 (DL = FF の場合)
    07H <直接コンソール文字入力> CTRL-C のチェックなし
    入力 AH = 07H
    出力 AL = 入力された文字
    08H <文字入力:エコーなし>
    入力 AH = 08H
    出力 AL = 入力された文字
    09H <文字列表示>
    入力 AH = 09H : DS:DX = 画面に表示する文字列 / 文字列の終わりは $ で表現
    0AH <文字列入力>
    入力 AH = 0AH : DS:DX = 文字列バッファの先頭アドレス / バッファの1バイト目にはバッファのサイズをユーザーが設定 / 2バイト目には実際に入力されたバイト数 / 3バイト目からデータが入力される
    0BH <キーボードバッファのチェック>
    入力 AH = 0BH
    出力 AL = FFH バッファに文字あり / AL = 00H バッファに文字なし
    0CH <バッファを空にしてキーボード入力
    入力 AH = 0CH / AL = 01H / 06H / 07H / 08H / 0AH 対応した文字入力を行う AL = 上記以外 バッファを空にするのみ
    出力 AL = 入力された文字 / AL = 00H 入力処理を行わない場合

    Chapter 4 - 3   入出力を使ったプログラム1 : Updated on 2004/08/16

    それでは、プログラムを見てみましょう。まずは、別ファイルにマクロを設定してしてみましょう。 まとめておいたので実際に中身を見てみてください。→CONIO.INC

    これからは、このマクロをバシバシつかっていきます。マクロの先頭の fc は function call の略です。 多言語経由の方にはもうしわけないですが、 名前もできるだけC言語に近いもので命名してみました。それではこれを使って、 早速プログラムを作ってみましょう。

    ;******************************************************************************
    ; 入出力ファンクションコール usemacro.asm
    ;******************************************************************************
    
    .model small
    .486
    
            include <conio.inc>
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MSG0    db      'マクロを実行してみます', 0dh, 0ah, '$'
    MSG1    db      0dh, 0ah, 'こんな感じでマクロ使用', 0dh, 0ah, '$'
    MSG2    db      'なにかキーを押してください…$'
    MSG3    db      0dh, 0ah, 'わかったかな??', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
            .startup
    
            fc_put_string   MSG0
            fc_get_char_necho
    
            mov   cx, 10
    
    MAINLOOP:
            fc_put_string   MSG1
            fc_put_string   MSG2
            fc_get_char_echo
    
            loop MAINLOOP
    
            fc_put_string   MSG3
            .exit
    
    end

    かなり短くなりましたね。別ファイルに分けることで、だいぶ見やすくなりました。

    Chapter 4 - 4   入出力を使ったプログラム2 : Updated on 2004/08/16

    それでは次は、ちょっとした文字列入力プログラムを作ってみましょう。

    ;******************************************************************************
    ; 入出力プログラム string.asm
    ;******************************************************************************
    
    .model small
    .486
    
            include <conio.inc>
    
    STRLEN  equ     128
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MENU    db      '---------- MENU ----------', 0dh, 0ah
            db      '1: 文字列入力', 0dh, 0ah
            db      '2: 入力した文字列表示', 0dh, 0ah
            db      '3: メニューの表示', 0dh, 0ah
            db      '9: 終了', 0dh, 0ah, 0dh, 0ah, '$'
    
    COM     db      'COMMAND > $'
    COMSTR  db      'STRING > $'
    ERR1    db      'コマンドが違います... MENUは 3 を押してください', 0dh, 0ah, '$'
    
    BUFFER  label   byte
    MAXLEN  db      ?
    INPUTED db      ?
    STRING  db      128 dup(20h)
    
    RETURN  db      0dh, 0ah, '$'
    
    ;******************************************************************************
    ; スタックセグメント
    ;******************************************************************************
    
            .stack
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
            .startup
    
    DISPMENU:
            fc_put_string MENU
    
    MAINLOOP:
            fc_put_string COM
            fc_get_char_echo
    
            push  ax
            fc_put_string RETURN
            pop   ax
    
            cmp   al, '1'
            je    INPUTSTR
    
            cmp   al, '2'
            je    DISPSTR
    
            cmp   al, '3'
            je    DISPMENU
    
            cmp   al, '9'
            je    EXIT
    
            fc_put_string ERR1
    
            jmp   MAINLOOP
    
    DISPSTR:
            fc_put_string STRING
            fc_put_string RETURN
            jmp   MAINLOOP
    
    INPUTSTR:
            fc_put_string COMSTR
            fc_get_string STRLEN, BUFFER
    
            xor   bx, bx
            mov   bl, INPUTED
            mov   buffer[bx + 2], '$'
    
            fc_put_string RETURN
    
            jmp   MAINLOOP
    
    EXIT:
            .exit
    
    end

    文字列を入力するプログラムです。label についてだけ説明しておきます。 label はデータの番地にシンボルを指定します。C言語で言うポインタですね。label 以下の byte はラベル以下がバイト単位で数えられることを意味します。例えば、 label 以下が word となっていたとしましょう。すると BUFFER[4] は 2×4バイト先のメモリをあらわすことになります。

    プログラムについては簡単なので、見ればわかるでしょう。そんなに難しいプログラムでないしね。

    xor    bx, bx  ; mov   bx, 0h と同じ

    となっている部分は mov bx, 0 と同じ意味です。8ビットCPUのころの時代の名残ですね。 昔のパソコンはメモリの量がとても少なかったそうです。それゆえそのころのプログラマは、 プログラム量を削るために血のにじむような努力をしました。mov bx, 0 とするよりも、xor bx, bx とするほうがバイト数が少ないのです。今でもそのやり方が 使われているということは、そのころの名残が今も残っているわけです。

    Chapter 4 - 5   ディレクティブ : Updated on 2004/08/20

    さらに先のプログラムは、「命令生成ディレクティブ」というのを用いて、 さらに見やすく書くことができます。ちなみに名前の頭にドット「.」がついているのは、 ディレクティブと呼ばれますが、擬似命令とほぼ同じものと考えてもらって 差し支えないでしょう。

    ;******************************************************************************
    ; 入出力プログラム string2.asm
    ;******************************************************************************
    
    .model small
    .486
    
            include <conio.inc>
    
    STRLEN  equ     128
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MENU    db      '---------- MENU ----------', 0dh, 0ah
            db      '1: 文字列入力', 0dh, 0ah
            db      '2: 入力した文字列表示', 0dh, 0ah
            db      '3: メニューの表示', 0dh, 0ah
            db      '9: 終了', 0dh, 0ah, 0dh, 0ah, '$'
    COM     db      'COMMAND > $'
    COMSTR  db      'STRING > $'
    ERR1    db      'コマンドが違います... MENUは 3 を押してください', 0dh, 0ah, '$'
    BUFFER  label   byte
    MAXLEN  db      ?
    INPUTED db      ?
    STRING  db      128 dup(20h)
    RETURN  db      0dh, 0ah, '$'
    
    ;******************************************************************************
    ; スタックセグメント
    ;******************************************************************************
    
            .stack
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
            .startup
    
    DISPMENU:
            fc_put_string MENU
    
    MAINLOOP:
            fc_put_string COM
            fc_get_char_echo
    
            push  ax
            fc_put_string RETURN
            pop   ax
    
            .if al == '1'
                fc_put_string COMSTR
                fc_get_string STRLEN, BUFFER
    
                xor   bx, bx
                mov   bl, INPUTED
                mov   buffer[bx + 2], '$'
    
                fc_put_string RETURN
    
            .elseif al == '2'
                fc_put_string STRING
                fc_put_string RETURN
    
            .elseif al == '3'
                jmp   DISPMENU
    
            .elseif al == '9'
                .exit
    
            .else
                fc_put_string ERR1
    
            .endif
    
            jmp   MAINLOOP
    end
    

    かなり見やすいプログラムになりました。この文の代入文を = (イコール)で差し替えれば、 C言語とほぼ形が一緒になりますね。なんとなくC言語が「中級言語」と呼ばれる理由がわかるような気がします。

    .if ディレクティブと .endif ディレクティブではさんで、 条件構文を形成しています。見ればそのままですね。この構文を使うかどうかは、使う人次第です。 条件分岐などで余計なシンボルを使う必要もなくなるので、出来るだけ使った方がいいとおもいます。

    Chapter 4 - 6   メモリ管理 : Updated on 2004/08/20

    メモリを管理するファンクションコールについて説明します。メモリの動的割り当てに 使います。メモリの動的割り当てとは、プログラムの実行中にメモリを取得することを言います。 これと反対に、プログラムの実行前に割り当てられていることを静的割り当てといいます。 ファンクションには次の種類があります。順番に説明していきましょう。

    48H <メモリの割り当て>
    入力 AH = 48H
    BX = 割り当てるメモリの大きさ(単位はパラグラフ単位)
     1パラグラフ = 16バイト
    出力 キャリーフラグがセット
     AX = エラーコード
     BX = 割り当て可能な最大のサイズ

    キャリーフラグがセットされない
     AX = 割り当てられたメモリのセグメントアドレス
    49H <割り当てられたメモリの開放>
    入力 AH = 49H
    ES = 開放するメモリ領域のセグメントアドレス
    出力 キャリーフラグがセット
     AX = エラーコード

    キャリーフラグがセットされない
     エラーなし
    4AH <割り当てられたメモリブロックの変更>
    入力 AH = 4AH
    ES = メモリ領域のセグメントアドレス
    DS = 変更したいメモリの大きさ(単位はパラグラフ単位)

     1パラグラフ = 16バイト
    出力 キャリーフラグがセット
     AX = エラーコード
     BX = 使用可能な最大の大きさ

    キャリーフラグがセットされない
     エラーなし

    エラーコードについては後のほうで説明します。マクロを定義しておきましょう。  → MEMORY.INC

    それではプログラムを見てみましょう。

    ;******************************************************************************
    ; メモリ割り当てプログラム malloc.asm
    ;******************************************************************************
    
    .model small
    .486
    
            include <conio.inc>
            include <memory.inc>
    
    MEMLEN  equ     128
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MSG0    db      'メモリの割り当てを行います', 0dh, 0ah, '$'
    MSG1    db      'メモリ割り当て成功', 0dh, 0ah, '$'
    
    ERR0    db      'メモリ割り当て失敗', 0dh, 0ah, '$'
    ERR1    db      'メモリ開放失敗', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; スタックセグメント
    ;******************************************************************************
    
            .stack
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
            .startup
    
            fc_put_string  MSG0
    
            fc_malloc  MEMLEN
            jc  ERROR1
    
            fc_free ax
            jc  ERROR2
    
            fc_put_string  MSG1
    
            jmp EXIT
    
    ERROR1:
            fc_put_string  ERR0
            jmp EXIT
    
    ERROR2:
            fc_put_string  ERR1
    
    EXIT:
            .exit
    end

    メモリを割り当てるだけのプログラムです。なんかそのままですね。

    Chapter 4 - 7   ファイル管理 : Updated on 2004/08/20

    次にファイルを扱います。ファイルを管理するファンクションコールには次のものがあります。 ファイルを使うといっても、実際にはハンドルを取得するだけです。ファイルを管理するのは、 DOSが行います。いくつかについて説明します。

    3CH <ハンドルを使うファイルの作成>
    入力 AH = 3CH
    DS:DX = パス名を含むファイル名
    CX = ファイルの属性
    出力 キャリーセット
     AX = エラーコード

    キャリーがセットされず
     AX = ファイルハンドル

    3DH <ハンドルを使うファイルのオープン>
    入力 AH = 3DH
    AL = ファイルアクセスコントロール
    DS:DX = パス名の位置
    出力 キャリーセット
     AX = エラーコード

    キャリーセットされず
     AX = ファイルハンドル

    3EH <ハンドルを使うファイルのクローズ>
    入力 AH = 3EH
    BX = クローズするファイルハンドル
    出力 キャリーセット
     AX = エラー

    キャリーセットされず
     エラーなし

    3FH <ファイルかデバイスの読み出し>
    入力 AH = 3FH
    DS:DX = バッファの位置
    CX = 読み込むバイト数
    BX = ファイルハンドル
    出力 キャリーセット
     AX = エラーコード

    キャリーセットされず
     AX = 読み出されたバイト数

    40H <ファイルかデバイスへの書き込み>
    入力 AH = 40H
    DS:DX = バッファの位置
    CX = 書き込むバイト数
    BX = ファイルハンドル
    出力 キャリーセット
     AX = エラーコード

    キャリーセットされず
     AX = 書き込まれたバイト数

    41H <ファイルの削除>
    入力 AH = 41H
    DS:DX = パスを含むファイル名
    出力 キャリーセット
     AX = エラーコード

    キャリーセットされず
     エラーなし

    42H <ファイルポインタの移動>
    入力 AH = 42H
    CX:DX = 移動するバイト数
    AL = 移動方法
    BX = ファイルハンドル
    出力 キャリーセット
     AX = エラーコード

    キャリーセットされず
     DX:AX = 新規のポインタ位置

    5BH <新しいファイルの作成>
    入力 AH = 5BH
    CX = 属性
    DS:DX = パス名を含んだファイル名
    出力 キャリーセット
     AX = エラーコード

    キャリーセットされず
     AX = ファイルハンドル

    もちろんファイルを扱うファンクションコールはこれだけではありません。 ほかのは省略させてもらいます。

    ファイルを扱うマクロをここにまとめておきました。 → FILE.INC

    それでは早速プログラムを見てみましょう。

    ;******************************************************************************
    ; ファイル操作プログラム file.asm
    ;******************************************************************************
    
    .model small
    .486
    
            include <conio.inc>
            include <fileio.inc>
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    HANDLE  dw      ?
    
    MSG0    db      'ファイルをオープンしてみます', 0dh, 0ah, '$'
    MSG1    db      'ファイルに書き込みを行いました', 0dh, 0ah, '$'
    MSG2    db      'Hello World!', 0dh, 0ah
    ERR0    db      'ファイルが存在しません', 0dh, 0ah
            db      '新規に作成します', 0dh, 0ah, '$'
    ERR1    db      'ファイルを作成できません', 0dh, 0ah, '$'
    
    FILE    db      'data.txt', 0
    
    ;******************************************************************************
    ; スタックセグメント
    ;******************************************************************************
    
            .stack
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
            .startup
    
            fc_put_string MSG0
    
            fc_open_handle FILE, FILE_OPEN_READWRITE
            mov   HANDLE, ax
    
            jnc   WRITEFILE
    
            fc_put_string ERR0
    
            fc_create_file FILE, FILE_ATTRIB_NORMAL
            mov   HANDLE, ax
    
            jc    ERROR
    
    WRITEFILE:
            fc_write_handle HANDLE, MSG2, sizeof MSG2
            fc_close_handle HANDLE
            fc_put_string MSG1
    EXIT:
            .exit
    ERROR:
            fc_put_string ERR1
            jmp   EXIT
    
    end

    ファイルを作成するプログラムです。ファイルの中には見慣れたあの文字が…。 実行してみてください。なんか本当にC言語みたいですね。sizeof とかあるし。どんどんMASMから遠ざかっていくような気がする。

    本当はエラーコードをハンドルしなければいけませんが、省略しています。

    Chapter 4 - 8   その他ファンクションコール : Updated on 2004/07/26

    今まで説明してきたもの以外のファンクションリクエストを紹介しておきましょう。 時刻の設定、バージョン情報などです。

    2AH <日付の取得>
    入力 AH = 2AH
    出力 CX = 年(1980〜2079)
    DH = 月(1〜12)
    DL = 日(1〜31)
    AL = 曜日(0=日曜日〜6=土曜日)
    2BH <日付の設定>
    入力 AH = 2BH
    CX = 年(1980〜2079)
    DH = 月(1〜12)
    DL = 日(1〜31)
    出力 AL = 00H 有効な日付
    AL = FFH 無効な日付
    2CH <時刻の取得>
    入力 AH = 2CH
    出力 CH = 時(0〜23)
    CL = 分(0〜59)
    DH = 秒(0〜59)
    2DH <時刻の設定>
    入力 AH = 2DH
    CH = 時(0〜23)
    CL = 分(0〜59)
    DH = 秒(0〜59)
    DL = 00H
    出力 AL = 00H 有効な時刻
    AL = FFH 無効な時刻
    30H <MS-DOSバージョンの取得>
    入力 AX = 3000H
    出力 AL = バージョン番号整数部
    AH = バージョン番号小数部
    BX = FF00H
    CX = 0000H
    36H <ディスクのフリースペースの取得>
    入力 AH = 36H
    DL = ドライブ番号(00H=カレント、01H=A:、…)
    出力 BX = 使用可能なクラスタ数
    DX = 1ドライブあたりの全クラスタ
    CX = 1セクタあたりのセクタ数
    AX = 1クラスタあたりのセクタ数
    4B00H <プログラムの実行とロード>
    入力 AX = 4B00H
    DS:DX = パスを含む実行ファイル名の位置
    ES:BX = パラメータブロックの位置
    出力 キャリーセット→エラー
    4CH <プロセスの終了>
    入力 AH = 4CH
    AL = リターンコード

    それでは、マクロ定義をしていきます。 マクロはこちらにまとめておきました。 → SYSTEM.INC

    あとは実際に自分で使ってみてくださいね。これで、conio.inc memory.inc fileio.inc system.inc の基本的なマクロが定義されました。ある程度のことはできると思うので、使ってみてくださいね。

    Chapter 4 - 9   エラーコード表 : Updated on 2004/08/20

    それではエラーコード表を載せておきましょう。エラーが生じるごとに、 エラー処理を行わなければなりません。「MS-DOS 5.0 プログラマーズリファレンス」の写しです。 キャリーで拾って、AXレジスタの値をチェックすると、これのどれかのエラーが入っているでしょう。

    番号 説明
    01H ファンクションコードが無効
    02H ファイルが見つからない
    03H パス名が見つからない
    04H ファイルをオープンしすぎている
    05H アクセスできない
    06H ハンドルが無効
    07H メモリコントロールブロックが破損
    08H メモリが足りない
    09H メモリブロックアドレスが無効
    0AH 環境が無効
    0BH 書式が無効
    0CH アクセスコードが無効
    0DH データが無効
    0EH 予約
    0FH ドライブ名が無効
    10H カレントディレクトリを削除しようとした
    11H 同じデバイスではない
    12H これ以上ファイルはない
    13H ディスクがライトプロテクトされている
    14H ディスクユニットが不良
    15H ドライブが準備されていない
    16H ディスクコマンドが無効
    17H CRCエラー
    18H 長さが無効
    19H シークエラー
    1AH MS-DOSのディスクでない
    1BH セクタが見つからない
    1CH 紙切れ
    1DH 書き込みが失敗
    1EH 読み出しが失敗
    1FH 一般的な失敗
    20H 共有違反
    21H ロック違反
    22H ディスクが不正
    23H FCB使用不可
    24-31H 予約
    32H ネットワークリクエストがサポートされていない
    33H リモートコンピュータがLISTEN状態でない
    34H ネットワーク名が重複している
    35H ネットワーク名が見つからない
    36H ネットワークビジー
    37H ネットワークデバイスはこれ以上ない
    38H ネットワークBIOSの限界を超えた
    39H ネットワークアダプタのハードエラー
    3AH ネットワークから不正な応答があった
    3BH 予期できないネットワークエラー
    3CH リモートアダプタが不正
    3DH プリント待ち行列が一杯
    3EH 待ち行列が一杯ではない
    3FH プリントファイルのためのスペースが足りない
    40H ネットワーク名はすでに削除されている
    41H アクセスできない
    42H ネットワークデバイスのタイプが不正
    43H ネットワーク名が見つからない
    44H ネットワーク名の限界を超えた
    45H ネットワークBIOSセッションの限界を超えた
    46H 一時休止
    47H ネットワークの要求を受け付けられない
    48H プリンタがディスクリダイレクト休止
    49-4FH 予約
    50H ファイルが存在する
    51H 予約
    52H 作成不能
    53H 割り込みタイプ24H失敗
    54H ネットワーク構造が不正
    55H 割り当て済み
    56H パスワードが無効
    57H パラメータが無効
    58H ネットワークへの書き込み失敗
    5AH システム関連ファイルがロードされていない

    Chapter 5   C言語とアセンブラの協調

    Abstract : C言語とアセンブラコードを共生する場合

    Chapter 5 - 1   Cからアセンブラコード呼び出し : Updated on 2004/08/20

    C言語からMASMでかかれたコードを呼び出すには、MASMでプロシージャを記述し それを呼び出すことになります。 MASMのプロシージャをC言語から呼び出すには、MASMのプロシージャをC言語の呼び出し規約に基づいて、 記述してやればよいのです。とりあえず、プログラムを見てみましょう。

    ;******************************************************************************
    ; C言語との協調 func.asm
    ;******************************************************************************
    
    .model small
    .486
    
            include <conio.inc>
    ;******************************************************************************
    ; データセグメント
    ;*****************************************************************************
    
            .data
    
    MSG     db      'はろーわーるど', 0dh, 0ah
            db      'アセンブラから呼び出しています', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
    func    proc c
            fc_put_string  MSG
            mov   ax, 10h
            ret
    func    endp
    
    end

    proc 擬似命令の後に c とつけると、そのプロシージャはC言語の呼び出し規約を使うことを明示します。そのほかにも、stdcall や syscall 、 pascal などが指定できます。C言語側は、次のように指定します。

    /******************************************************************************
     * C言語からの呼び出しプログラム main.c
     ******************************************************************************/
    
    #include <stdio.h>
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    int func(); /* この関数の本体をMASMで記述してある */
    
    #ifdef __cplusplus
    }
    #endif
    
    int main()
    {
            int ret;
    
            printf("MASM本体を呼び出します\n");
            ret = func();
            printf("func の戻り値は %d です\n", ret);
    
            return 0;
    }

    このプログラムをコンパイルして、リンクすると実行ファイルを作成します。 リンクするときには、C言語のライブラリをリンクする必要があります。

    C言語の関数の戻り値は、AX レジスタに代入されます。C言語側のプログラムで、 きちんと16が表示されたでしょうか。

    Chapter 5 - 2   C言語のデータ型 : Updated on 2004/08/20

    C言語のデータ型は、MASMでは以下のデータ型を取ります。もちろん16ビットコンパイラの場合です。

    C言語データ型 アセンブラデータ型
    char BYTE
    short WORD
    int WORD
    long DWORD
    float REAL4
    double REAL8
    void * WORD / DWORD

    32ビットコンパイラの場合は少し様子が違います。また、コンパイラごとに状況は違います。詳しくは、コンパイラに付属のマニュアルを参照したほうがよいでしょう。

    それでは、例題プログラムを見てみましょう。次のプログラムは、引数をいくつかとる関数を2つ作成しています。関数の実体はもちろんMASMで記述します。

    
    ;******************************************************************************
    ; C言語との協調 func2.asm
    ;******************************************************************************
    
    .model small, c
    .486
    
            include <conio.inc>
    
            public func1
            public func2
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    MSG0    db      'func1 実行中...', 0dh, 0ah, '$'
    MSG1    db      'func2 実行中...', 0dh, 0ah, '$'
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
    ;******************************************************************************
    ; int fun1(int n);
    ;   n に1を足した数を戻り値にする
    ;******************************************************************************
    
    func1   proc n:word
            fc_put_string  MSG0
            mov   ax, n
            inc   ax
            ret
    func1   endp
    
    ;******************************************************************************
    ; int func2(int n1, int n2);
    ;   n1 と n2 を足した数を戻り値にする
    ;******************************************************************************
    
    func2   proc n1:word, 
                 n2:word
            fc_put_string  MSG1
            mov   ax, n1
            add   ax, n2
            ret
    func2   endp
    
    end

    プログラム中にあるすべてのプロシージャをC言語の呼び出し規約に対応させるためには、 .model ディレクティブの記述の、メモリモデルの指定の次に言語指定をすることができます。 上記プログラムでは、デフォルトでプロシージャの記述をCの呼び出し規約を用いています。 これで前章のプログラムのように、プロシージャごとに言語指定をしてやる必要はありません。

    さてC言語側では、次のように記述してやります。

    /******************************************************************************
     * C言語側の記述 main2.c
     ******************************************************************************/
    
    #include <stdio.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    int func1(int n);
    int func2(int n1, int n2);
    
    #ifdef __cplusplus
    }
    #endif
    
    int main()
    {
            int n = 4, n1 = 332, n2 = 897;
            int sum;
    
            sum = func1(n);
            printf("func1(%d) = %d\n", n, sum);
    
            sum = func2(n1, n2);
            printf("func2(%d, %d) = %d\n", n1, n2, sum);
    
            return 0;
    }

    説明し忘れていましたが、コンパイラが C++ の場合、定数として __cplusplus という定数が定義されます。C++ で関数をコンパイルしてしまうといろいろと問題が生じるので(ポリモーフィズムとか)、 関数のプロトタイプはC言語でコンパイルします。そのために、extern を使っています。

    Chapter 5 - 3   アセンブラからC言語コードの呼び出し : Updated on 2004/08/20

    前の例では、C言語からMASMのプロシージャを呼び出してみました。では次は、MASMから C言語を呼び出してみましょう。これはものすごく簡単で、MASMのソースコード内で関数の プロトタイプ宣言をしてやればよいだけです。もちろんC言語の呼び出しを使うので、.model ディレクティブでC言語と指定してやらなければなりません。外部関数(プロシージャ)のプロトタイプには、 proto 擬似命令を使います。

    それではプログラムを見てみましょう。まずはMASM側のプログラムから。

    ;******************************************************************************
    ; MASMからC言語呼び出し main.asm
    ;******************************************************************************
    
    .model small, c
    .486
    
            include <fileio.inc>
    
    NUM1    =       1
    NUM2    =       4
    
    ;******************************************************************************
    ; 関数プロトタイプ
    ;******************************************************************************
    
    func    proto    n1:word, n2:word
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
            .data
    
    Sum     dw      0
    
    MSG0    db      'C言語の関数を呼び出します', 0dh, 0ah
    STR_S   label   byte
            db      'func を呼び出した結果は'
    NUM     dw      ?
            db      'です', 0dh, 0ah
    STR_E   label   byte
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
            .code
    
            .startup
            fc_write_handle stdout, MSG0, sizeof MSG0
    
            xor     ax, ax
    
            .while  Sum < 12
            invoke  func, NUM1, NUM2
            add     Sum, ax
            .endw
    
            mov     ax, Sum
            aaa
            rol     ax, 8
            add     ax, '00'
            mov     NUM, ax
    
            fc_write_handle stdout, STR_S, STR_E - STR_S
            .exit
    end

    今回は、文字列表示に fc_put_string マクロを使っていません。 標準出力のファイルハンドルと、fc_write_handle マクロを用いて文字列を表示しています。 ちょっとした小技も使っているので見てくださいね。よく見てみると、C言語の fputs と同じ関数ですね。

    .while ディレクティブを使ってみました。ほとんどC言語と同じように扱うことができます。 プログラムを見れば使い方はわかりますよね。

    それでは次はC言語のほうのソースファイルです。

    /******************************************************************************
     * C言語側の記述
     ******************************************************************************/
    
    int func(int n1, int n2)
    {
            return n1 + n2;
    }

    こちらのプログラムは特に問題はないでしょう。この2つのプログラムをコンパイル・アセンブルして、 リンクを行えば足し算の結果が表示されるはずです。このプログラムではC言語の関数を用いていないので、 ライブラリをリンクする必要はありません。


    Chapter 6   LSICでリンクするには

    Abstract : LSICとMASMコードの共生

    Chapter 6 - 1   はじめに : Updated on 2004/08/20

    LSIC と MASM で作成したソースコードをリンクするためには、ちょっと手間が必要です。それについて説明していきます。

    16ビットコンパイラの手に入らない人は、LSIC を使ってみましょう。LSIC は機能制限はあるものの、フリーのコンパイラです。これを活用しない手はないでしょう。

    機能制限といっても、メモリモデルがスモールモデルのみに限定されて、ライブラリが一部削られているだけです。学習用に使うのならば十分すぎる機能が備わっています。

    Chapter 6 - 2   LSICのインストール : Updated on 2004/08/20

    Cマガジンに付属している LSIC をインストールする方法について説明しておきましょう。まず圧縮ファイルを解凍します。今回は「C:\LSIC」というディレクトリ以下に解凍したとします。次に bin (バイナリ)ディレクトリにある _lcc というファイルの中身を変更します。次の表のように変更します。赤い部分が変更点です。

    # LSI C-86 compiler's configuration file
    
    -DLSI_C
    -XA:\LSIC86\BIN -LA:\LSIC86\LIB -IA:\LSIC86\INCLUDE -T -O
    -acdos.obj $LSICOPTS
    &		#Command line argument will be inserted here
    -lknjlib -ldoslib -v

    # LSI C-86 compiler's configuration file
    
    -DLSI_C
    -XC:\LSIC\BIN -LC:\LSIC\LIB -IC:\LSIC\INCLUDE -T -O
    -acdos.obj $LSICOPTS
    &		#Command line argument will be inserted here
    -lknjlib -ldoslib -v
    

    また autoexec.bat にパスをセットしておいてください。プログラムの作成には次のように指定します。ソースファイルとオブジェクトファイルは「C:\LSIC\USER」ディレクトリ以下に存在したとします。その場合には、次のようの実行ファイルを作成します。(カレントディレクトリはUSERであるとします)

    lcc -o main.exe main.c func.obj

    これで、main.exe 実行ファイルが作成されます。実行してみると、ね?実行できたでしょう?

    Chapter 6 - 3   LSIC問題点 : Updated on 2004/08/20

    簡略化セグメント方式を用いて MASM ソースを書くと、LSIC のオブジェクトファイルとリンクできません。実はこれが問題なのですね。というのも、簡略化セグメント方式では MASM が自動的にセグメントを作成してくれます。しかし困ったことに、MASM の作成するセグメント名と LSIC の作成するセグメント名に互換性がないために、リンクができないという事態が生じるのです。

    簡略化セグメント方式を使った場合、MASM はセグメント名として次の名前を使います。(メモリモデルはスモールモデル)

    コードセグメント → _TEXT

    データセグメント → _DATA

    さて LSIC はというと、セグメント名に次の名前を使います。(メモリモデルはスモールモデル)

    コードセグメント → TEXT

    データセグメント → DATA

    これが問題なのですね。セグメント名に _TEXT や _DATA をセグメントに使うのは、M社のコンパイラやその互換性のあるコンパイラに多く見られます。

    また関数名に問題があります。通常Cコンパイラは _ (下線)を関数名の先頭に付したものをオブジェクトファイルで定義しています。MASM では言語指定で C 言語を指定してやれば、自動的に関数名に _ (下線)をつけてくれます。関数名が func だとするなら、プロシージャ名は _func ということになります。

    しかし困ったことに、LSIC は関数名の後ろに _ (下線)を付します。関数名が func だとしたら、プロシージャ名は func_ になります。

    ということは、セグメントを LSIC 方式で名前を付けてやり、関数も LSIC 方式でつけてやればよいのです。そうすればきちんとリンクをしてくれます。

    Chapter 6 - 4   LSICでのクロスリンク : Updated on 2004/08/20

    セグメントの定義には、segment - ends 擬似命令を使います。 本章のほうでは、説明が面倒になってしまうので説明は省きました。ここでも大して説明はしません。 記述方法だけ示しておきましょう。

    ;******************************************************************************
    ; C言語との協調
    ;******************************************************************************
    
    .486
    
            include <conio.inc>
    
    CGROUP  group   TEXT
    DGROUP  group   DATA
    
    ;******************************************************************************
    ; データセグメント
    ;******************************************************************************
    
    DATA    segment byte public use16 'DATA'
    
    MSG     db      'はろーわーるど', 0dh, 0ah
            db      'アセンブラから呼び出しています', 0dh, 0ah, '$'
    
    DATA    ends
    
    ;******************************************************************************
    ; コードセグメント
    ;******************************************************************************
    
    TEXT    segment byte public use16 'CODE'
            assume cs:TEXT, ds:DATA
    
            ;int func()
    func_   proc
            fc_put_string  MSG
            mov   ax, 10h
            ret
    func_   endp
    
    TEXT    ends
    
    end

    セグメント定義をとりあえず、上のプログラムのように定義しておけばリンクしてくれるでしょう。C言語側のプログラムは、前に示した通りです。一応ここにも示しておきます。

    /******************************************************************************
     * C言語からの呼び出しプログラム
     ******************************************************************************/
    
    #include <stdio.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    int func(); /* この関数の本体をMASMで記述してある */
    
    #ifdef __cplusplus
    }
    #endif
    
    int main()
    {
            int ret;
    
            printf("MASM本体を呼び出します\n");
            ret = func();
            printf("func の戻り値は %d です\n", ret);
            return 0;
    }

    LSIC でプログラムを作成する場合には、上のプログラムのように簡略化セグメントを使わずにきちんとセグメント定義を行い、関数名の後ろに _ (下線)をつけなければなりません。


    Chapter 7   COMファイルについて

    Abstract : COMファイルについて

    Chapter 7 - 1   COMファイルについて : Updated on 2004/08/20

    ここでは、COM ファイルの作成法についての説明を行います。ついでに、メモリモデルも説明します。 次の順序で説明していきますが、興味のあるところを読んでいってくださいね。

    実行ファイル形式には、COM 形式と EXE 形式があります。これはなぜでしょう?? じつは、COM ファイルのほうは、CP/M 時代の名残です。8 ビットパソコン(その頃は、パーコンといったり、マイコンといったりした)の頃は、 メモリは 64K しかありませんでした。ちょうど、セグメントがなくて IP (インストラクションポインタ)だけで、命令をフェッチしていたからです。 IP は 16 ビットですから、2 の 16 乗で 65535 バイトしかアクセスできなかったのです。

    MS-DOS はその戦略上、CP/M のシェアを奪わなければなりませんでした。それの意味することは、 CP/M の実行ファイルが MS-DOS 上で実行できなければならないということでした。 それが今でも(Win9x系まで)残っているのですね。(この辺の詳しい経緯は知らないので、知っている方は教えてね)

    ということで、COM 形式として残ったのです。EXE 形式はセグメント分割を取り入れているので、 64K バイト以上の実行プログラムが作成可能です。

    Chapter 7 - 2   COMファイル作成 : Updated on 2004/08/20

    COM ファイルを作成するためには、メモリモデルを TINY モデルで作成しなければなりません。 このモデルで作成すると、自動的に 100h 番地から実行します。ML と LINK で EXE ファイルを作成した後に EXE2BIN (実行ファイル) を使って、 COM ファイルに変換しなければなりません。とりあえず、プログラムを見ればすぐわかるでしょう。

    ;------------------------------------------------------
    ; COM 作成のテスト
    ;------------------------------------------------------
    
    .model tiny
    .486
    
            include <conio.inc>
            include <fileio.inc>
    
    puts    macro string
            fc_write_handle stdout, string, sizeof string
            endm
    
            .code
            .startup
    
            puts MSG1
            puts MSG2
    
            fc_get_char_necho
    
            .exit
    
    MSG1    db  'このプログラムは表示するだけです', 0dh, 0ah
    MSG2    db  '何かキーを押してください…', 0dh, 0ah
    
    end

    このプログラムを入力した後に、(% はプロンプト)

    % ml /c sample.asm

    を実行します。これで、sample.obj が作成されますね。この次に、16ビットリンカで

    % link sample;

    として、sample.exe を作成します。ここでこのプログラムを実行しても、 ちゃんと表示されないはずです。さてとどめに、exe2bin を使って、

    % exe2bin sample.exe sample.com

    とすれば、あら不思議。COM ファイルが作成されましたね。とりあえず、この手順で COM ファイルを作成することが出来ます。もちろん、それぞれのプログラムにパスを通して おかなければなりません。

    Chapter 7 - 3   メモリモデル : Updated on 2004/08/20

    おまけです。small と tiny のメモリモデルについて説明しておきます。

  • tiny メモリモデル
  • プログラムがすべて単一のコードセグメント内にあります。 CS = DS = ES = SS としたほうが分かりやすいかな??
    しかも 16 ビットなので、65535 バイトのメモリ空間しかありません。

  • small メモリモデル
  • コードセグメントとデータセグメントの2つのセグメントを持つメモリモデルです。 プログラムのコードに 65535 バイト使え、それ以外のデータに 65535 バイト使うことが出来ます。あわせて 128K です。