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アセンブラプログラミングではなく、ゲームプログラミングとして検索した方が良いでしょう。