COMファイルは、メモリの 0x100
番地以降の内容をそのままファイルに格納した、単純な形式の実行可能ファイルである。
この形式のファイルは、CP/M や MS-DOS などの複数のOSで採用されているが、通常はその中身は実行する CPU や OS によって異なる。
そこで、今回は、Z80 (CP/M) と x86 (MS-DOS) の2種類の環境で共通してそのまま実行できるCOMファイルを作ってみた。
ポイント
おまじない
EB 03 C3 xx xx
を実行すると、x86 はこのままこの後のプログラムを実行し、Z80 は絶対アドレス xx xx
で指定した場所に実行を移す。
原理
EB 03
は、x86では JMP 命令で3バイト飛ばして実行することを表す。
すなわち、今回は C3 xx xx
の部分を飛ばして実行することになる。
一方、Z80 では、以下の命令として実行する。
データ | 命令 |
---|---|
EB |
EX DE, HL |
03 |
INC BC |
C3 xx xx |
JP xxxx |
そのため、絶対アドレス xx xx
にジャンプすることになる。
これらの命令は未定義以外のフラグを変更しないので、もし EX
および INC
命令で変化するレジスタの値が重要であれば、逆の操作
DEC BC
EX DE, HL
により戻せばよい。
プログラム例
今回は、Z80 (CP/M) で実行すると「hello from Z80」と、x86 (MS-DOS) で実行すると「hello from x86」と出力するプログラムを作ってみた。
Z80 (CP/M) 用のプログラム
CP/M information archive : BDOS system calls
CP/M の機能 (BDOS) は、以下の手順で呼び出すことができる。
-
C
レジスタに機能番号を、DE
レジスタにパラメータを格納する -
CALL 5
を実行する (5番地を呼び出す)
今回は、以下の機能を用いる。
- 文字列の出力 機能番号:9
-
DE
レジスタで指定したアドレスから、$
が格納されている位置の手前までのASCII文字列を表示する。
-
- アプリケーションの終了 機能番号:0
- アプリケーションを終了し、コマンドプロンプトに戻る。
MikeAssembler を用い、以下のプログラムのアセンブルを行った。
target z80
org 0x140
ld c, 9
ld de, message
call 5
ld c, 0
call 5
message:
datab "hello from Z80", 0x0d, 0x0a, "$"
おまじない、および x86 用のプログラムを置くための領域をあけるため、0x100
番地ではなく 0x140
番地から配置するよう設定している。
x86 (MS-DOS) 用のプログラム
MS-DOS の機能は、以下の手順で呼び出すことができる。
- 各レジスタにパラメータを格納する
-
INT 0x21
命令を実行する
今回は、以下の機能を用いる。
-
文字列出力
-
AH
に機能番号の9
を、DS:DX
に出力するデータの開始アドレスを格納して呼び出す。 - 指定したアドレスから
$
の手前までのデータを出力する。
-
-
プログラム終了
-
AH
に機能番号の0x4C
、AL
にリターンコード (0:正常終了) を格納して呼び出す。 - プログラムを終了してメモリを開放する。
-
今回は、以下の NASM 用のプログラムを用意した。
CPU の種類によって分岐するおまじない、および前章の Z80 (CP/M) 用のプログラムも埋め込んでいる。
bits 16
cpu 8086
org 0x100
db 0xeb, 0x03, 0xc3, 0x40, 0x01
mov ah, 9
mov dx, message
int 0x21
mov ax, 0x4c00
int 0x21
message:
db "hello from x86", 0x0d, 0x0a, "$"
align 0x40
db 0x0e, 0x09, 0x11, 0x4d, 0x01, 0xcd, 0x05, 0x00
db 0x0e, 0x00, 0xcd, 0x05, 0x00, 0x68, 0x65, 0x6c
db 0x6c, 0x6f, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20
db 0x5a, 0x38, 0x30, 0x0d, 0x0a, 0x24
完成品
以下が、今回作成したファイル (のhexdump) である。
00000000 eb 03 c3 40 01 b4 09 ba 11 01 cd 21 b8 00 4c cd |...@.......!..L.|
00000010 21 68 65 6c 6c 6f 20 66 72 6f 6d 20 78 38 36 0d |!hello from x86.|
00000020 0a 24 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |.$..............|
00000030 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
00000040 0e 09 11 4d 01 cd 05 00 0e 00 cd 05 00 68 65 6c |...M.........hel|
00000050 6c 6f 20 66 72 6f 6d 20 5a 38 30 0d 0a 24 |lo from Z80..$|
CyberChef でデコードし、結果を保存することで、バイナリを入手できる。
実行
AltairZ80 を用いて、Z80 での実行をシミュレーションした。
簡単な使い方は CP/Mシミュレータ に載っている。
ファイルをインポートする際になぜか余計なデータ (1A
の連続) がついているが、実行には関係ない部分であり、狙い通り「hello from Z80」が出力された。
DOSBox を用いて、x86 での実行をシミュレーションした。
狙い通り、「hello from x86」が出力された。
同じファイルを Z80 (CP/M) と x86 (MS-DOS) それぞれのエミュレータで実行し、どちらでも意図通りの出力がされたことを確認できた。
結論
プログラムの先頭に CPU の種類によってジャンプしたりしなかったりする命令を置くことで、CPU の種類によって分岐し、1個のCOMファイルに2種類の環境用のプログラムをそのまま実行できる形で格納することができた。