作って理解するOS x86系コンピュータを動かす理論と実装
entry:
jmp $
times 510 - ($ - $$) db 0x00
db 0x55, 0xAA
jmp $
$
は現在の命令のアドレス
times 510 - ($ - $$) db 0x00
timesはディレクティブらしいのだが、どこにそれが書かれたリファレンスがあるのかわかっていない
$
は現在の命令のアドレス
$$
はセクションの開始アドレス
($ - $$)は、現在の命令のアドレスからセクションの開始アドレスを引いた結果
で、セクション内での現在の位置(offset)を表す
510 - ($ - $$)
で、510から現在の位置を引く
db 0x00
ディレクティブで指定された回数だけ0x00を繰り返しデータ定義(Define Byte)
dbのリファレンス
db 0x55, 0xAA
ブートセクタの終了を示す0x55,0xAAとブートフラグを記入
1 00000000 EBFE jmp $
2 00000002 00<rep 1FCh> times 510 - ($ - $$) db 0x00
3 000001FE 55AA db 0x55, 0xAA
出力されたリスティングファイル
jmp $が2バイトのバイトコードEBFEとして変換された
FE
が$
に対応するが、これは符号付き8ビット整数で-2を表す
つまり2バイト前にjmpすると言う意味
(EBFEの2バイト分IPレジスタが動く。IPレジスタはx86でのプログラムカウンタ)
00<rep 1FCh>
1FC回の繰り返し。hは16進ってこと
times 510 - ($ - $$) db 0x00
510バイト - 2バイト(ジャンプ命令) - 2バイト(ブートセクタ署名) = 508バイトだけを0x00でdefine byteしている。
疑問1: これ、実際の本では00<rept>となっているが、なんの違い??具体的な1FC回って出力されないの?
BIOSパラメータブロックの領域確保
entry:
jmp ipl
times 90 - ($ - $$) db 0x90
ipl:
jmp $
times 510 - ($ - $$) db 0x00
db 0x55, 0xAA
BPB(BIOS Parameter Block)と呼ばれる情報を書き込む。
これは外部記憶装置の属性等の情報らしい。あとで詳細を調べる。
このBPBは滅多に書き換えられることはないらしい。なぜ?(あとで)
IPL(Initial Program Loader)はBPBの後に置かれる。
0x90はNOP命令。今はとりあえずで埋めるそうだ。
ブートプログラム内にデータを保存
BOOT_LOAD equ 0x7C00 ;ブートプログラムのロードアドレス
ORG BOOT_LOAD ;ロードアドレスをアセンブラに通知
entry:
; BPB
jmp ipl
times 90 - ($ - $$) db 0x90
ipl:
cli ;割り込み禁止
mov ax, 0x0000
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BOOT_LOAD
sti ;割り込み許可
mov [BOOT.DRIVE], dl
ALIGN 2, db 0
BOOT: ;ブートドライブに関する情報
.DRIVE: ;ドライブ番号
dw 0
BIOSは、ブートプログラムに制御を移す時、ブートプログラムが保存されていた外部記憶装置の番号をDLレジスタに設定する。
DLレジスタはDXレジスタのあれ。DHとDLでDXレジスタ
疑問: なんでDLレジスタなの?
このDLレジスタの値だけがBIOSからブートプログラムに伝わる唯一の情報
その後セグメントレジスタの初期化を行う。
また、スタックポインタにプログラムがロードされる0x7C00番地を指定する。
疑問: 何で0x7C00なの?
ブートプログラムの直前の領域にスタック領域を割り当てることで、プログラムとスタックが重複しないようにする。
アセンブラは0x0000番地にロード、実行されることを期待しているので、ORGで0x7C000のオフセットであることを知らせる。
セグメントレジスタに即値を代入するCPU命令はないので、AXレジスタの0x0000を介して設定する。
疑問: BOOTラベルの直前のALIGNディレクティブの意味があまりよくわかってない。データ定義前には必ず入れた方が良さそうに見えるけど、合ってる?
dlレジスタの値をメモリ[BOOT.DRIVE]に保存。
14.4 文字の表示
putc関数モジュール
putc:
;スタックフレームの確保
push bp
mov bp, sp
;レジスタの保存
push ax
push bx
;処理の開始
mov al, [bp + 4] ;出力する文字を取得
mov ah, 0x0E ;テレタイプ式1文字出力
mov bx, 0x0000 ;ページ番号0、色0
int 0x10 ;BIOSのビデオサービスを呼び出す
;レジスタの復元
pop bx
pop ax
;スタックフレームの破棄
mov sp, bp
pop bp
ret
疑問: つまりブートローダの処理中に文字が出てくるあれは全てalレジスタに入れたあとにint 0x10してるの?もっというとBIOSが起動してからの文字は全てこの機構で出てくる?
BOOT_LOAD equ 0x7C00 ;ブートプログラムのロードアドレス
ORG BOOT_LOAD ;ロードアドレスをアセンブラに通知
%include "../include/macro.s"
entry:
; BPB
jmp ipl
times 90 - ($ - $$) db 0x90
ipl:
cli ;割り込み禁止
mov ax, 0x0000
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BOOT_LOAD
sti ;割り込み許可
mov [BOOT.DRIVE], dl
; 文字を表示
cdecl putc, word 'X'
cdecl putc, word 'Y'
cdecl putc, word 'Z'
jmp $
ALIGN 2, db 0
BOOT: ;ブートドライブに関する情報
.DRIVE: ;ドライブ番号
dw 0
%include "../modules/real/putc.s"
times 510 - ($ - $$) db 0x00
db 0x55, 0xAA
$ brew install qemu
$ qemu-system-x86_64 -fda boot.img
補足
これ基本的にサブルーチンには書かれるらしい。そりゃそうか
push bp
mov bp, sp
これがわかりやすかった
bpは、呼び出し元のスタックの一番下を指し示すレジスタ。これをpushすることでspが下に2バイト分減少し、スタックの新しいトップのアドレスを指し示す。
疑問: これ最後のpop書き忘れたら普通にスタック全崩れしない?
%macro cdecl 1-*.nolist
%rep %0 - 1
push %{-1:-1}
%rotate -1
%endrep
%rotate -1
call %1
%if 1 < %0
add sp, (__BITS__ >> 3) * (%0 - 1)
%endif
%endmacro
cdecl: C Declarationの略
こんなに呼び出し規約ってあるのか
1-*は、このマクロが1個以上の引数を取ることを示す。
.nolistは、このマクロをリストファイルに展開表示しないようにするということ。
%repは、繰り返し命令
%0はマクロに渡された引数の数
疑問: %0 - 1 って何で全個数回さないんだっけ?
-> 呼び出し関数はスタックにプッシュしないから
push %{-1:-1} マクロに渡された引数をスタックにプッシュする。%{-1:-1}は引数リストの最後を示す。これにより右の引数からスタックにプッシュしてる
%rotate -1は、引数リストを指定だけずらす。この場合右端からプッシュしたいので負数で右方向に回転させる。
再度%rotate -1を使用して、最初の引数をトップに持ってきます。
call %1は、最初の引数(実際には関数名)を呼び出すための命令です。
add sp, (BITS >> 3) * (%0 - 1)は、スタックポインタを調整して、プッシュした引数のサイズをスタックから引き戻す。
3bit右にシフトして、1/8の値を得る。16bitなら2バイト、32bitなら4バイトのような。
__BITS__はビット数(32ビットまたは64ビット)を示します。このシフト操作でバイト数に変換します。