ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

ブートローダを探検してみた

プロローグ

PC の電源を入れると、まずは BIOS が走っていろいろします。
この辺りは FreeBSD じゃないのでさっくりと飛ばして……


BIOS のブート優先順位に従って「HDD0!君に決めた!」となると、
HDD0 の第0セクタを、メモリの 0x7c00 から1セクタ分(512Byte)だけ読んで、
命令ポインタを 0x7c00 に設定します。

boot0 その1

第0セクタが 0x7c00 以降に読みこまれて、その先頭を指しています。
実際に書かれているデータは id:Hossy:20120117 で読んだ通り。


このセクタは前半にブートローダ(boot0)が実装されていて、
後半にスライステーブルが記録されているという構造になっています。


ブートローダ部分のソースコードはこちら。
/usr/src/sys/boot/i386/boot0


boot0.S の start: ラベルのある命令、

start:          cld                             # String ops inc

これが 0x7c00 にある命令です。


ここからずらずらと実行を始めますが、最初に行う処理はこれです。

movw $LOAD,%sp  #  stack

movw %sp,%si    # Source
movw $start,%di # Destination
movw $0x100,%cx # Word count
rep             # Relocate
movsw           #  code

$LOAD ( 0x7c00 ) から 0x100 word ( 512Byte ) を、$start ( 0x600 ) に転送する処理です。
C で書くと

memcpy( (void*)0x600, (void*)0x7c00, 512 );

こういうことです。


$LOAD boot0.S の上の方で定義されているマクロで、
第0セクタが読まれたアドレス、0x7c00 を示します。


$start は先ほどの start ラベルのアドレスなので、リンク時に解決します。
Makefile では

BOOT_BOOT0_ORG ?= 600

と定義したマクロを使って -Ttext ${BOOT_BOOT0_ORG} とリンカに指定しているため、
boot0 は 0x600 から始まる、と仮定してアドレス解決を行います。
そのため、$start は 0x600 としてハードコーディングされます。


コードのコピーが終わったら、

       jmp main-LOAD+ORIGIN            # Jump to relocated code
main:

現在は 0x7c00 から始まる方の main ラベル直前にいますが、
この jmp 命令で、0x600 から始まる方の main ラベルに飛びます。
大体想像できると思いますが、ORIGIN は 0x600 と定義されているマクロです。


なぜ一旦 0x600 に自身をコピーしてそっちに移動するかと言うと、
この後で boot1 を 0x7c00 に読みこむので、int 命令から戻ってこれなくなるためです。

boot0 その2

さて、第0セクタが丸々 0x600 以降に読みこみ済みということは、
スライステーブルはただのメモリアクセスで読める状態です。
以降はアドレスを計算しながらテーブルをパースして、
おなじみのブートセレクタを画面に出力する処理があります。


この辺り特に面白い処理はないですが、C だと通常 \0 ヌル文字で終端する所、
1バイト節約のためか、最上位ビットを立てた文字で終端判定をしています。

os_win:         .ascii "Wi";   .byte 'n'|0x80

boot0 その3

さて、ブートセレクタで適当なスライスが選択された場合、
check_selection ラベルの 3 という地点から boot1 に接続します。

3:      movw $LOAD,%bx          # Address for read
        movb $0x2,%ah           # Read sector
        callw intx13            #  from disk

intx13 サブルーチンはその名の通り、int 13h 命令を発行するコードです。
処理内容は ah で指定している通り、HDD から1セクタだけ読みこみます。


読みこむセクタは、サブルーチン中でスライステーブルから計算します。
id:Hossy:20120117 で読んだ BSD スライスの第0セクタ、つまり boot1 を指します。


また、読みこみ先は boot0 の実行が始まったアドレスと同じく 0x7c00 です。
現在地は 0x600 に転送した方のイメージ内なので、自分自身が書き換わることもありません。

        cmpw $MAGIC,0x1fe(%bx)  # Bootable?
        jne beep                # No

読んだセクタの簡単な検査。末尾に MBR と同様にマジックナンバーがあるそうです。
id:Hossy:20120117 確かに。開始番地だけでなくマジックナンバーも揃えているなんて。

        jmp *%bx                # Invoke bootstrap

で、boot1 を読みこんだ 0x7c00 へ FAR ジャンプして boot0 は終了です。