MS-DOSのアセンブラでCOM形式の実行ファイルを作成する
MS-DOSのサンプルコード
Hello, World!を表示するプログラム
ASSUME CS:CODE,DS:CODE,SS:CODE,ES:CODE
CODE SEGMENT
org 100h
START:
;文字列出力
mov ah, 09h
mov dx, offset MSG
int 21h
;終了
mov ah,4ch
mov al,00h
int 21h
MSG db 'Hello, World!',0dh,0ah,'$'
CODE ENDS
END
サンプルプログラムは全てのセグメントを同一セグメントに収めるCOM形式のプログラムです。COM形式のバイナリファイルには余計なコードが入りません。バイナリエディタで見たときにわかりやすい構造になっています。
COM形式ファイルのアセンブル&リンク
JWASMR -bin -Fo hello.com hello.asm
バイナリエディタで確認
| アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00000000 | B4 | 09 | BA | 0D | 01 | CD | 21 | B4 | 4C | B0 | 00 | CD | 21 | 48 | 65 | 6C |
| 00000010 | 6C | 6F | 2C | 20 | 57 | 6F | 72 | 6C | 64 | 21 | 0D | 0A | 24 |
オペコードマップとアスキーコード表があればほぼアセンブラに置き換えられます。
デバッガで確認
| アドレス | バイナリ | オペコード | オペランド | オペランド |
|---|---|---|---|---|
| 0100 | B4 09 | MOV | AH | 09 |
| 0102 | BA 0D 01 | MOV | DX | 101D |
| 0105 | CD 21 | INT | 21h | |
| 0107 | B4 4C | MOV | AH | 4C |
| 0109 | B0 00 | MOV | AL | 00 |
| 010B | CD 21 | INT | 21 |
mov dx, offset MSG
の欄をを見るとバイナリがBA 0D 01、逆アセンブルの結果がMOV DX,101Dとなっています。offset MSGの部分が010DなのはMSGのオフセットアドレスがセットされた結果ですが、バイナリが 0D01なのはx86プロセッサが、メモリに展開する際のデータの並び順がリトルエンディアンだからです。
データ
| H | e | l | l | o | , | W | o | r | l | d | ! | Cr | Lf | $ | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 48 | 65 | 6C | 6C | 6F | 2C | 20 | 57 | 6F | 72 | 6C | 64 | 21 | 0D | 0A | 24 |
COM形式の実行ファイルの中には命令コードとデータ以外に付加情報がない事が分かります。
最後が$で終わっているのはファンクションコールの文字列表示の命令が$を終端文字として処理しているからです。要するに、文章の途中に$マークがあると文字列表示のファンクションコールは途中までしか表示できないという事です。
コードの終了とデータの開始の間に境界線がありません。
終了処理の際に、AHとALにセットする値を分けて書いていますが、まとめて書けば1バイト分だけファイルサイズが小さくなります。
終了をAH=4C,AL=00からAX=4C00に変えてみた
ASSUME CS:CODE,DS:CODE,SS:CODE,ES:CODE
CODE SEGMENT
org 100h
START:
;文字列出力
mov ah, 09h
mov dx, offset MSG
int 21h
;終了
mov ax,4c00h
int 21h
MSG db 'Hello, World!',0dh,0ah,'$'
CODE ENDS
END
| アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00000000 | B4 | 09 | BA | 0C | 01 | CD | 21 | B8 | 00 | 4C | CD | 21 | 48 | 65 | 6C | 6C |
| 00000010 | 6F | 2C | 20 | 57 | 6F | 72 | 6C | 64 | 21 | 0D | 0A | 24 |
データ宣言を先頭に書いてみる。
ASSUME CS:CODE,DS:CODE,SS:CODE,ES:CODE
CODE SEGMENT
org 100h
MSG db 'Hello, World!',0dh,0ah,'$'
START:
;文字列出力
mov ah, 09h
mov dx, offset MSG
int 21h
;終了
mov ax,4c00h
int 21h
CODE ENDS
END
一見、うまく動いてる様に見える事も有りますが、その後に影響し動作不良を起こすと思います。
| アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00000000 | 48 | 65 | 6C | 6C | 6F | 2C | 20 | 57 | 6F | 72 | 6C | 64 | 21 | 0D | 0A | 24 |
| 00000010 | B4 | 09 | BA | 00 | 01 | CD | 21 | B8 | 00 | 4C | CD | 21 |
データの部分がそのまま命令コードとして実行される事になります。
データ宣言を先頭に書いて正しく動作させる方法
ASSUME CS:CODE,DS:CODE,SS:CODE,ES:CODE
CODE SEGMENT
org 100h
jmp START
MSG db 'Hello, World!',0dh,0ah,'$'
START:
;文字列出力
mov ah, 09h
mov dx, offset MSG
int 21h
;終了
mov ax,4c00h
int 21h
CODE ENDS
END
無条件ジャンプで回避しています。
| アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00000000 | EB | 10 | 48 | 65 | 6C | 6C | 6F | 2C | 20 | 57 | 6F | 72 | 6C | 64 | 21 | 0D |
| 00000010 | 0A | 24 | B4 | 09 | BA | 02 | 01 | CD | 21 | B8 | 00 | 4C | CD | 21 |
簡略化セグメントで記述
.model tiny
.data
MSG db "Hello, World!",0dh,0ah,'$'
.code
.startup
;文字列出力
mov ah, 09h
mov dx, offset MSG
int 21h
;終了
.exit 00h
END
他言語のライブラリとリンクするならこれを利用する方が楽
| アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00000000 | B4 | 09 | BA | 0C | 01 | CD | 21 | B8 | 00 | 4C | CD | 21 | 48 | 65 | 6C | 6C |
| 00000010 | 6F | 2C | 20 | 57 | 6F | 72 | 6C | 64 | 21 | 0D | 0A | 24 |
データの定義が上に有りますが、バイナリでは最初のサンプルソースと同じ様にコードの後ろにデータが付いています。自動的に後ろに並び替えてくれるようです。
Linuxのnasmに書き方はこれに近いので学習するならこの書き方が良い気がします。