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 | moremycmd.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 = 0000H36H <ディスクのフリースペースの取得> 入力 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 endproc 擬似命令の後に 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 です。