MacでGRUBから自作OSを起動

久々にOS書いてます。そろそろ取っ掛からないと、来年もSECCAMPの機会を逃しかねないですね。

以前までははりぼてOSをLinuxに移植していたのですが、途中でちょっと詰んでしまったので、一からフルスクラッチすることにしました。
ただ、今はMacbookを使っているので、以前とまた環境が違っていろいろと環境整備に苦労しました。
その一環がクロスコンパイラのビルド。詳しくは以下。
【参考】Handwriting「Mountain Lionユーザのためのクロスコンパイラビルド」

さて、今回はブートローダを丁寧に書くのはやめて、その辺の処理をGRUBに投げる事にしました。
ところが、Linuxは多くのディストリビューションGRUBで起動するために非常に簡単に導入ができるのですが、MacはEFIという独自のブートローダを用いていてGRUBがうまくインストールできないため、この辺でちょっと困りました。
いちばん手っ取り早いのはLinuxGRUBのレスキューイメージを出力してMacに持ってくる事なんですが。

まあそのあたりを軽くまとめたいと思います。

こんな感じになります。

GRUBの選択画面
f:id:levelfour:20131016231049p:plain

自作OS選択後
f:id:levelfour:20131016231112p:plain
(割り込みのデバッグ中でなんか吐いてますが気にしないでください)

GRUBのレスキューイメージの用意

Linuxならgrubコマンドを使って生成する事ができます。
以下のページを参考にするといいと思います。
【参考】0から作るOS開発「GRUBその2 GRUBのインストール」

Macではソースコードからgrubをビルドしようとしても(少なくとも僕の環境では)うまくいきませんでした。
しょうがないので、レスキューイメージ自体をダウンロードしてきます。
http://sourceforge.jp/projects/sfnet_supergrub.berlios/releases/

上記のページにgrub-rescue-cdrom.isoというファイルがあるはずなので、それをダウンロードします。

VirtualBoxGRUBレスキューイメージを起動

適当な仮想マシンを作成して、IDEコントローラにgrub-rescue-cdrom.isoをつっこんで起動します。
すると、GRUBの起動選択画面が出ます。
"c"を押すとGRUBのコンソールに移動します。

f:id:levelfour:20131016232239p:plain

プログラムを書く

GRUBはMultiboot Specificationという規格に則って開発されています。
このMultiboot Specificationとは、ざっくり言うとGNUが策定した「OSのブートローダを共通化するための約束事」です。
自分のOSをMultiboot Specificationに従って書いてやれば、GRUBで起動できるという寸法なのです。
【参考】Multiboot Specification

そのために、まずはヘッダファイルを作ります。

multiboot.h
#ifndef __MULTIBOOT_H
#define __MULTIBOOT_H

#define KERNEL_LOAD_ADDRESS		0x00100000

#define MBH_FLAG_PAGE_ALIGN		0x00000001
#define MBH_FLAG_MEMORY_INFO		0x00000002
#define MBH_FLAG_VIDEO_MODE		0x00000004
#define MBH_FLAG_ENABLE_ADDR		0x00010000

#define MULTIBOOT_HEADER_MAGIC		0x1BADB002
#define MULTIBOOT_HEADER_FLAGS		(MBH_FLAG_PAGE_ALIGN | MBH_FLAG_MEMORY_INFO)
#define MULTIBOOT_HEADER_CHECKSUM	-(MULTIBOOT_HEADER_MAGIC+MULTIBOOT_HEADER_FLAGS)
#define KERNEL_STACK_SIZE		0x00100000

#define MULTIBOOT_HEADER_MODE_TYPE	0x00000001	// text mode
#define MULTIBOOT_HEADER_WIDTH		0x00000050
#define MULTIBOOT_HEADER_HEIGHT		0x00000028
#define MULTIBOOT_HEADER_DEPTH		0x00000000

#endif // __MULTIBOOT_H

次に、この定数を使ってGRUBから呼び出される部分をアセンブリで書きます。

boot.S
#include "multiboot.h"

.text
.code32

.extern _kernel_entry
.global _start

_start:
	jmp entry
	.align 4
	# multiboot header
	.long MULTIBOOT_HEADER_MAGIC
	.long MULTIBOOT_HEADER_FLAGS
	.long MULTIBOOT_HEADER_CHECKSUM

entry:
	# reset eflags
	pushl $0x0
	popf

	push %ebx		# push MULTIBOOT_HEADER_MAGIC
	push %eax		# push multiboot info structure pointer

	call _kernel_entry

loop:
	hlt
	jmp loop

(最初ハマってたんだけど、大文字の.Sにしないとgccプリプロセッサが効かないんだね)

主な処理は非常に単純で、ようするにentryにジャンプしてkernel_entryって関数を呼ぶだけです。
その途中で%eflagsをリセットしたり、kernel_entry関数に引数を詰んでいたりします。

jmp命令の直後にある、データを直接配置しているのはMultiboot Specificationの仕様です。
MULTIBOOT_HEADER_MAGICはMultibootのマジックナンバーです。

最後にkernel_entryを書いてやればOK。

kernel.c
#include "multiboot.h"

void _kernel_entry(unsigned long magic, unsigned long addr) {
    while(1) {
        __asm__ __volatile__ ("hlt");
    }
}

今回は手抜きでhltしてるだけなので、残念ながら画面的には何も変化がおきないです。

これら一連のプログラムのコーディング例は、Multiboot Specificationのページにあります。
【参考】Multiboot Specification: Examples

もっとマトモに書かれているので、参考にしてみてください。

コンパイルする

Makefileを作ります。

CC=i686-elf-gcc    # 自分のクロスコンパイラのパスを指定してください
AS=i686-elf-as     # 同上
LD=i686-elf-ld     # 同上
OPTION=-nostdlib -fno-builtin -Wall

a.img: boot.o kernel.o
    $(LD) -o a.img -Ttext=0x00100000 -nostdlib --script=boot.ls boot.o kernel.o

# General Rules
%.o: %.S
    $(AS) $(OPTION) $*.S -o $*.o

%.o: %.c
    $(CC) $(OPTION) $*.c -c -o $*.o

これでmakeすればa.imgというバイナリが出力されます。
ちなみにldに与えた-Ttext=0x00100000というオプションは、エントリポイントのアドレスを0x00100000に再配置するという意味です。

フロッピーイメージを作成する

このままではまだ実行する事ができません。
最終的にどうすればいいかというと、GRUBで読み込むためにはフロッピーイメージにする必要があります。
そのためにはmformatというコマンドを用いてMS-DOS用にフロッピーイメージファイルをフォーマットする必要があります。
このmformatというコマンドは、Macではmtoolsというパッケージでまとめられていますので、これをportなりなんなりでインストールします。

sudo port -d install mtools

mtoolsはmformatの他にも、MS-DOSのファイル操作を実現するコマンド群が揃っています。

では、フロッピーイメージを作成します。

mformat -f 1440 -C -i fd.img ::
tar cvf fd.img a.img

mformatでfd.imgというフロッピーイメージファイルをまず作成します。
そして、fd.imgのルートディレクトリにa.imgを書き込みます。
他に何かいい方法があるのだと思いますが、今のところtarで書き込む方法しか見つからなかった(というか成功しなかった)ので、これで運用しています。
これで、fd.imgのルートにa.imgが配置されました。

fd.img
/
+a.img

準備完了です。
仮想マシンのフロッピーコントローラにfd.imgをマウントします。

GRUBからa.imgを起動する

まずはVirtualBoxで再び仮想マシンを立ち上げて、GRUBの選択画面でcを押してコンソールに移動します。
そして、以下のコマンドを入力します。

set root=(fd0)
multiboot /a.img
boot

うまくいけば、これで起動されるはずです。
kernel_entryではhltしているので、フリーズするはずですね。
文字列表示なりなんなりをすると、わかりやすくなると思います。

set root=(fd0)がうまくいかないとき

仮想マシンにfd.imgはマウントされていますか?
GRUBコンソールでlsコマンドを叩いたときに一覧の中に(fd0)というのが見えなければ、恐らくマウントされていないです。

multiboot /a.imgがうまくいかないとき

ちゃんとfd.imgのルートディレクトリにa.imgがおかれていますか?
ls /
をたたくと、fd.imgのルートディレクトリに配置されているファイル・ディレクトリが見れます。
その中にa.imgがなければ、うまくいっていないです。
tarコマンドを叩くところをもう一度確認してください。