この記事はComputer Society Advent Calendar 2025の7日目の記事です。
導入
本記事ではFreeDOSをQEMUで起動し、DEBUGコマンドを使用して文字列を画面に表示します。
使用環境は以下の通りです。
- WSL2 (Ubuntu 24.04.3 LTS)
- QEMU emulator version 8.2.2
FreeDOSとは
FreeDOSは、1980年代に開発されていたMS-DOS互換のOSで、MS-DOSの販売中止が発表された1994年に開始したOSSプロジェクトです。MS-DOS用のソフトウェアの実行のサポートなどを目的としています。
FreeDOSは、x86プロセッサのリアルモード (16bit) で動作します。リアルモードは全てのx86プロセッサの起動時の、8086互換の動作モードです。そのため、FreeDOSはx86_64でも動作します。
また、DOS APIと呼ばれるソフトウェア割り込みによるAPIを提供しています。呼び出すには、特定のレジスタに呼び出したい機能に対応した番号、他のレジスタにパラメータを設定し、ソフトウェア割り込みの命令を使用します。DOS APIのほとんどの呼び出しはINT 21h命令で呼び出されます。
※ 21hのように後ろにhをつけた数字は16進数として扱います
DEBUGとは
DOS標準のデバッガで、debugと打つとレジスタやメモリの値の確認、16bit命令のアセンブルや実行を行うことができます。本記事ではDOS API呼び出しをするアセンブリを書いて実行するために利用します。
FreeDOSのダウンロードと起動
公式サイトのThe FreeDOS Projectからダウンロードできます。左上の FreeDOS 1.4 LiveCD が扱いやすい形式なので、こちらを取得します。
ダウンロードするとFD14BOOT.imgとFD14LIVE.isoが含まれています。
readme では、通常の PC や仮想環境ではFD14LIVE.isoを使うことが推奨されていました(FD14BOOT.imgはLiveCDが利用できない環境向けの最小構成イメージ)。そのため、本記事でもFD14LIVE.isoを利用します。
次に、QEMUでisoファイルからFreeDOSを起動します。
qemu-system-x86_64 -cdrom FD14LIVE.iso
-cdrom <file> は指定したファイルを CD-ROM ドライブとして接続するオプションです。QEMU の BIOS は CD-ROM をブート対象としているため、このオプションのみでOSが起動します。
コマンドを実行すると、QEMU のウィンドウが立ち上がり、FreeDOS のブートメニューが表示されます。
"Use FreeDOS 1.4 in Live Environment mode" を選択すると、起動してプロンプトが表示されます。
Live Environment は、OSをハードディスクにインストールせず、CD-ROM からRAMにシステムイメージを読み込んで起動するモードです。このモードでは手軽にOSを動作させることができますが、ハードディスクへの書き込みはできません。
今回は小さいプログラムを書いてみるだけで永続化は不要なので Live Environment を選択しました。
DEBUGの操作の説明
DEBUGのいくつかの操作とアドレスの指定方法について説明します。
- 起動は
debugで、-のプロンプトが出る -
q: 終了する -
?: ヘルプの表示 -
r: レジスタの値を表示-
r ax 0001のように指定すると値を直接書き換えられる
-
-
a <addr>: そのアドレスに16bit命令をアセンブルして書き込める -
d <addr>: メモリのダンプ -
e <addr>: バイト列や文字列を直接書き込む -
u <addr>: 逆アセンブル -
g=<addr>: 指定アドレスから実行
Microsoft DEBUG Utilityにその他の多くの機能が記載されています。
アドレス指定について
リアルモードでは、アドレスの指定はA:Bという形で行います。Aに入るのはセグメントレジスタでCS, DS, ES, SSがあります。Bは16bitのオフセットで16進数を使います。また、A:Bの物理アドレスはA * 16 + Bです。
DEBUGではセグメントレジスタを指定しない場合、a, u, gではCSがオフセットとなり、d, eはDSがオフセットになります。
メモリに直接命令を書き込んで実行する
実際にDOS APIを呼び出して Hello World するプログラムを書いて実行してみます。
※ 私の場合、JISキーボードからUS配列を打つことになったので、こちらの記事を参考にしました。
-e 200 "Hello World!",0D,0A,"$"
-a
10B9:0100 mov ah, 9
10B9:0102 mov dx, 200
10B9:105 int 21
10B9:107 mov ah, 4C
10B9:109 mov al, 0
10B9:10B int 21
10B9:10D
-g=100
Hello World!
まず、e 200 "Hello World!",0D,0A,"$"00でDS:0200に"Hello World!"の文字列と"$"を配置しています。最後に"$"を置く理由は文字列を出力するDOS APIがこの文字を読むまで文字列を出力するためです。
次にaでCS:0100から命令を書いています。アセンブリで命令を書いて改行すると、命令が機械語に変換されてメモリに書き込まれて、次の書き込み位置から入力できるようになっています。
次にAHに09h、DXに200hを設定しています。AHは呼び出したい機能(AH=09hは文字列の出力)に対応した番号、DXは文字列を配置したアドレスのDSからのオフセットです。そして、int 21の呼び出しで文字列が表示されます。
ここで書き込むのを終わりにしてしまうとCPUはメモリの値を実行できる限り進み続けてしまうため、終了命令を置く必要があります。AHに4Chで終了機能を、ALに終了コードとして0を設定してDEBUGに制御が戻るようにします。
g=100で実行すると、Hello World! が出力されます。
参考
- FreeDOS - Wikipedia, https://ja.wikipedia.org/wiki/FreeDOS
- DEBUGで機械語, https://kasayan86.web.fc2.com/asm/asm_prog2.htm
- Microsoft DEBUG Utility, https://bitsavers.trailing-edge.com/pdf/microsoft/msdos_2.0/MS-DOS_2.0_DEBUG.pdf
- INT 21h - The general function despatcher, http://bbc.nvg.org/doc/Master%20512%20Technical%20Guide/m512techb_int21.htm
