x86エミュレータであるBochsを使うことで、BIOSレベルでのプログラミングを楽しむことが出来ます。
今回はNASMを使って"Hello, World!"を出力させてみましょう。
なお、本記事ではMaster Boot Record (MBR)を使ったアセンブラプログラミングの詳細は解説しません。すでに入門レベルは経験済みの読者を想定しています。
最初にBochsが使う仮想マシン設定ファイルを用意します。拡張子は一般的に".bxrc"とすることが多いようです。
Bochs付属のサンプルを使っても良いですが、最低限度の機能があれば良いので、次の5行だけでもOKです。
hello.bxrc:
romimage: file=$BXSHARE/BIOS-bochs-latest vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest megs: 16 floppya: 1_44=hello1.img, status=inserted boot: floppy
フロッピーからのBOOTにのみ対応し、16MBのメインメモリ、フロッピーイメージは"hello1.img"で挿入された状態で起動します。
では、これから"hello1.img"を作ってみましょう。
BIOSのINT 10hがビデオ関連の割り込みになっています。
まず最初に、改行コードなども扱ってくれる INT 10h - AH=13h 割り込みを使って表示させてみます。
INT 10h - AH=13h 割り込みでは画面のページ番号やカーソル位置などを設定しておく必要があります。
ページ番号は INT 10h - AH=0Fh , カーソル位置は INT 10h - 03h で取得出来ますので、事前に呼んでおきます。
ソースコードは次のようになります。
hello1.asm:
org 0H bits 16 JMP 0x07C0:start ; far jmp, update CS to 0x07C0 start: ; copy CS(0x07C0) to DS, ES MOV AX, CS MOV DS, AX MOV ES, AX ; get current video mode XOR AX, AX XOR BX, BX MOV AH, 0FH INT 10H ; active page num => BH ; get cursor position and size MOV AH, 03H INT 10H ; (row, column) => (DH, DL) ; write string MOV AH, 13H MOV AL, 1 ; bit0 = 1 => update cursor ; BH = page num # already set by INT10H/AH=0FH MOV BL, 11111001B ; character attribute, blinking, bg=light gray, fg=light blue MOV CX, [hellomsg_len] ; string length ; DH, DL (row, column) already set by INT10H/AH=03H PUSH BP MOV BP, hellomsg INT 10H POP BP JMP $ hellomsg_0: hellomsg: db `Hello, \r\nBIOS World!\r\n` hellomsg_len: dw $ - hellomsg_0 times 510-($-$$) db 0 dw 0xAA55
MBRは0x7C00以降に読み込まれます。念のため、far jmpを使ってCSを更新しておきます。
MBRですので、510バイト0埋め + "0xAA55"が必要です。NASMですと以下の記述で実現出来ます。
times 510-($-$$) db 0 dw 0xAA55
"$"はNASMではアセンブラの現在位置を示します。"$$"は現在のセクションの先頭位置を示します。よって
$ - $$
で現在位置がセクション先頭からどれだけ離れているかが分かります。
"times"プレフィクスは、その後ろに続くアセンブラを指定回数分繰り返す機能があります。
これらの組み合わせで、
times 510-($-$$) db 0
これが「現在位置から510バイトまで0埋め」と解釈されます。
コンパイル:
> nasm -f bin -o hello1.img hello1.asm
Bochsで実行してみると、"Booting from Floppy ..."の次の行に、改行処理されて"Hello, BIOS World!"文字列が点滅しながら表示されます。色も指定した通りの背景色・文字色で表示されています。
IBM PCの伝統として、セグメント0xB800がカラーテキストモードのデフォルト画面にマッピングされています。
0xB800:0000 以降に、直接カラーデータやASCIIコードを書き込むことで文字列を表示することが可能です。
1文字は1ワードで表現され、ASCIIデータが低位に、カラーデータが高位に格納されています。
画面サイズは、Bochs起動直後は80桁x25行になっています。
では、画面背景をLight Grayで塗りつぶし、左上から"Hello, BIOS World!"を黒字で表示させてみましょう。
hello2.asm:
org 0H bits 16 JMP 0x07C0:start ; far jmp, update CS to 0x07C0 videoseg equ 0xB800 cols equ 80 rows equ 25 bgcolor equ 01110000B ; no blink, bg = light gray, fg = black bgtext equ 0x20 ; space char start: ; copy CS(0x07C0) to DS, ES MOV AX, CS MOV DS, AX MOV AX, videoseg MOV ES, AX MOV DI, 0 ; clear background color and texts MOV AL, bgtext MOV AH, bgcolor ; for (i = 0; i < rows; i++) { MOV CX, rows .bg_fill_rows: PUSH CX ; for (j = 0; j < cols; j++) { MOV CX, cols .bg_fill_cols: MOV [ES:DI], AX ADD DI, 2 LOOP .bg_fill_cols ; } POP CX LOOP .bg_fill_rows ; } XOR AX, AX XOR DI, DI MOV SI, hellomsg .print: MOV BYTE AL, [DS:SI] CMP AX, 0 JZ .print_end MOV BYTE [ES:DI], AL INC SI ADD DI, 2 JMP .print .print_end: JMP $ hellomsg: db "Hello, BIOS World!", 0 times 510-($-$$) db 0 dw 0xAA55
コンパイル:
> nasm -f bin -o hello2.img hello2.asm
hello.bxrc でhello2.imgを指定します。
floppya: 1_44=hello1.img, status=inserted -> floppya: 1_44=hello2.img, status=inserted
Bochsで実行してみます。上手く表示出来ました。
NASMとBIOS, VGAの提供する INT 10h 割り込みを使って "Hello, World!" アセンブラプログラミングを体験しました。
なおデバッグ機能を有効にしたBochsで試す時は、起動後に
> b 0x7C00
としてMBRの先頭の物理アドレス指定でブレークポイントを設定し、
> c
で実行すると、MBRの先頭で止まります。"vb"で"0x07C0:0000"形式で設定してもブレークしないので、必ず物理アドレスでブレークポイントを設定して下さい。
今回はテキストモードしか取りあげませんでしたが、グラフィックモードなどを使うことでビデオゲームを作ることも出来ます。
VGAプログラミングの詳細は、x86アセンブラプログラミングではなく、ゲームプログラミングとして検索した方が良いでしょう。