MBR とは
MBR とはハードディスクの先頭セクタに書かれている 512 バイトの領域であり、ブートローダの第一段階とパーティションテーブルが記載されている。
512 バイトでできることはそうそう多くなく、役目としては GRUB カーネルを読み込んで起動するだけ。
よって、Linux のブートプロセスの最初の流れは以下のようになる。
MBR -> GRUB カーネル -> Linux カーネル
今回は MBR のコードを追ってみる。
GRUB2 のソースコードをダウンロード
以下のコマンドでダウンロードしましょう。 今回は訳あって grub-2.00~beta4 にします。
$ wget http://alpha.gnu.org/gnu/grub/grub-2.00~beta4.tar.gz
$ tar xvf grub-2.00~beta4.tar.gz
早速 MBR を読んで見る
MBR のコードは grub-core/boot/i386/pc/boot.S
に書かれている。
.code16
.globl _start, start;
_start:
start:
/*
* _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
*/
/*
* Beginning of the sector is compatible with the FAT/HPFS BIOS
* parameter block.
*/
jmp LOCAL(after_BPB)
.code16 は移行のコードは 16 bit 命令であるという宣言。
jmp で LOCAL(afterBPB) にジャンプしている。
LOCAL(afterBPB) 以前は GRUB カーネルを読み込むときにしようするデータ領域が記載されている。
LOCAL(after_BPB):
/* general setup */
cli /* we're not safe here! */
/*
* This is a workaround for buggy BIOSes which don't pass boot
* drive correctly. If GRUB is installed into a HDD, check if
* DL is masked correctly. If not, assume that the BIOS passed
* a bogus value and set DL to 0x80, since this is the only
* possible boot drive. If GRUB is installed into a floppy,
* this does nothing (only jump).
*/
. = _start + GRUB_BOOT_MACHINE_DRIVE_CHECK
boot_drive_check:
jmp 3f /* grub-setup may overwrite this jump */
testb $0x80, %dl
jz 2f
3:
/* Ignore %dl different from 0-0x0f and 0x80-0x8f. */
testb $0x70, %dl
jz 1f
2:
movb $0x80, %dl
1:
/*
* ljmp to the next instruction because some bogus BIOSes
* jump to 07C0:0000 instead of 0000:7C00.
*/
ljmp $0, $real_start
real_start:
/* set up %ds and %ss as offset from 0 */
xorw %ax, %ax
movw %ax, %ds
movw %ax, %ss
/* set up the REAL stack */
movw $GRUB_BOOT_MACHINE_STACK_SEG, %sp
cli は割り込み禁止。
bootdrivecheck では、BIOS はフロッピーから読み込んだ場合は DL レジスタに 0x00 ~ 0x0f を格納し、ハードディスクなら 0x80 ~ 0x8f の値を格納する。
例えば 1 番目のハードディスクなら 0x80 、2 番目なら 0x81 となる。
MBR は自分自身を読み込んだドライブをこのレジスタから教えてもらう。
boot_drive:
.byte 0xff /* the disk to load kernel from */
/* 0xff means use the boot drive */
... ( 中略 ) ...
sti /* we're safe again */
/*
* Check if we have a forced disk reference here
*/
movb boot_drive, %al
cmpb $0xff, %al
je 1f
movb %al, %dl
1:
movb 命令で bootdrive の内容を AL レジスタに読み込みそれが 0xff ならば 1: に飛ぶ。 もし違うなら、AL レジスタの内容を DL レジスタにコピーする。 bootdrive が指すアドレスのデータは grub2-bios-setup プログラムにより 0xff が書き込まれるが、boot_drive の位置にドライブの番号を書くことで、指定したドライブからブートローダを読み込んでくれるようになる。
1:
/* save drive reference first thing! */
pushw %dx
/* print a notification message on the screen */
MSG(notification_string)
/* set %si to the disk address packet */
movw $disk_address_packet, %si
/* check if LBA is supported */
movb $0x41, %ah
movw $0x55aa, %bx
int $0x13
/*
* %dl may have been clobbered by INT 13, AH=41H.
* This happens, for example, with AST BIOS 1.04.
*/
popw %dx
pushw %dx
/* use CHS if fails */
jc LOCAL(chs_mode)
cmpw $0xaa55, %bx
jne LOCAL(chs_mode)
andw $1, %cx
jz LOCAL(chs_mode)
MSG(notificationstring) で “GRUB” の 4 文字を表示。( notificationstring: .asciz “GRUB ” より ) movb $0x41, %ah と movw $0x55aa %bx 、また int $0x13 で BIOS がサポートするドライブのアクセス機能の種類を BIOS に問い合わせている。
lba_mode:
xorw %ax, %ax
movw %ax, 4(%si)
incw %ax
/* set the mode to non-zero */
movb %al, -1(%si)
/* the blocks */
movw %ax, 2(%si)
/* the size and the reserved byte */
movw $0x0010, (%si)
/* the absolute address */
movl kernel_sector, %ebx
movl %ebx, 8(%si)
movl kernel_sector + 4, %ebx
movl %ebx, 12(%si)
/* the segment of buffer address */
movw $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si)
LBA モードでディスクドライブからデータを読み込むための DAP データ構造を作成。LBA モードについてはこちらを参考に。
DAP を指定して、ディスクからメモリの特定領域にデータを読み込む。これで、ディスクから 1 セクタ分のデータを読み込んで、物理メモリの 0x7000:0x0000 に転送する。
movb $0x42, %ah
int $0x13
/* LBA read is not supported, so fallback to CHS. */
jc LOCAL(chs_mode)
movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
jmp LOCAL(copy_buffer)
ブートローダをドライブから読み込む。
読み込みが成功した場合は、BX レジスタに $GRUBBOOTMACHINEBUFFERSEG (0x7000) を設定し、copy_buffer にジャンプ。
LOCAL(copy_buffer):
/*
* We need to save %cx and %si because the startup code in
* kernel uses them without initializing them.
*/
pusha
pushw %ds
movw $0x100, %cx
movw %bx, %ds
xorw %si, %si
movw $GRUB_BOOT_MACHINE_KERNEL_ADDR, %di
movw %si, %es
cld
rep
movsw
popw %ds
popa
/* boot kernel */
jmp *(kernel_address)
$GRUBBOOTMACHINEKERNELADDR は色々あって 0x8000 。
“movw $0x7000, %bx” より BX->0x7000
“movw $0x100, %cx”
CX->0x100
“movw %bx, %ds”
DS->BX->0x7000
“xorw %si, %si”
SI->0x0000
“movw $GRUBBOOTMACHINEKERNELADDR, %di”
DI->0x8000
“movw %si, %es”
ES->SI->0x0000
movsw
(DS:SI -> ES:DI) => 0x7000:0x0000 -> 0x0000:0x8000
rep 命令により 2 Bytes 転送を 0x100 回行う。-> 512 bytes
これにより、先ほどメモリに読み込んだ 1 セクタ分のデータが 0x08000 にコピーされる。
“jmp *(LOCAL(kerneladdress))”
kerneladdress -> 0x8000 なので 0x8000 に飛ぶ。つまり、さっきディスクから読み込んだやつ。(ブートローダ)