Mac で「ゼロからのOS自作入門」の「8.7 メモリ管理に挑戦」まで進められたので、ひとまずそこまでの手順をメモとして残しておきます。この環境構築方法で本の最後まで進められるかどうかはまだわかりません。
環境は macOS Sonoma(14.5), CPU は Intel でも Apple Silicon でも OK です。Homebrew はすでにインストールされているものとします。
PATH
にいろいろ追加していきますが、適宜 .zshrc
等の PATH
の設定にも追加してください。
EDK II の準備
後でインストールする binutils がすでにインストールされ PATH
に追加されていると失敗するので注意してください。
$ cd $HOME
$ git clone https://github.com/tianocore/edk2.git
$ cd edk2
$ git checkout edk2-stable202208
$ git submodule update --init
$ cd BaseTools/Source/C
$ make
mikanos-build リポジトリの準備
$ cd $HOME
$ git clone https://github.com/uchan-nos/mikanos-build.git osbook
$ cd osbook/devenv
$ curl -L https://github.com/uchan-nos/mikanos-build/releases/download/v2.0/x86_64-elf.tar.gz | tar xz
$ cd ..
次の内容の mac.patch というファイルを作って適用します。
--- a/devenv/make_image.sh
+++ b/devenv/make_image.sh
@@ -23,11 +23,24 @@ qemu-img create -f raw $DISK_IMG 200M
mkfs.fat -n 'MIKAN OS' -s 2 -f 2 -R 32 -F 32 $DISK_IMG
$DEVENV_DIR/mount_image.sh $DISK_IMG $MOUNT_POINT
-sudo mkdir -p $MOUNT_POINT/EFI/BOOT
-sudo cp $EFI_FILE $MOUNT_POINT/EFI/BOOT/BOOTX64.EFI
+if [ `uname` = 'Darwin' ]; then
+ mkdir -p $MOUNT_POINT/EFI/BOOT
+ cp $EFI_FILE $MOUNT_POINT/EFI/BOOT/BOOTX64.EFI
+else
+ sudo mkdir -p $MOUNT_POINT/EFI/BOOT
+ sudo cp $EFI_FILE $MOUNT_POINT/EFI/BOOT/BOOTX64.EFI
+fi
if [ "$ANOTHER_FILE" != "" ]
then
- sudo cp $ANOTHER_FILE $MOUNT_POINT/
+ if [ `uname` = 'Darwin' ]; then
+ cp $ANOTHER_FILE $MOUNT_POINT/
+ else
+ sudo cp $ANOTHER_FILE $MOUNT_POINT/
+ fi
fi
sleep 0.5
-sudo umount $MOUNT_POINT
+if [ `uname` = 'Darwin' ]; then
+ hdiutil detach $MOUNT_POINT
+else
+ sudo umount $MOUNT_POINT
+fi
diff --git a/devenv/mount_image.sh b/devenv/mount_image.sh
index ba8233e..aea4d7d 100755
--- a/devenv/mount_image.sh
+++ b/devenv/mount_image.sh
@@ -16,5 +16,9 @@ then
exit 1
fi
-mkdir -p $MOUNT_POINT
-sudo mount -o loop $DISK_IMG $MOUNT_POINT
+if [ `uname` = 'Darwin' ]; then
+ hdiutil attach -mountpoint $MOUNT_POINT $DISK_IMG
+else
+ mkdir -p $MOUNT_POINT
+ sudo mount -o loop $DISK_IMG $MOUNT_POINT
+fi
$ patch -p1 < mac.patch
QEMU のインストール
Homebrew でインストールします。
$ brew install qemu
LLVM のインストール
本では LLVM7 を使っていますが、現在(2024 年 7 月時点) mikanos リポジトリでは LLVM14 を使うようになっていることに合わせて LLVM14 を使います。
$ brew install llvm@14
# Intel Mac の場合
$ export PATH=/usr/local/opt/llvm@14/bin:$PATH
# Apple Silicon Mac の場合
$ export PATH=/opt/homebrew/opt/llvm@14/bin:$PATH
その他のビルドに必要なパッケージのインストール
$ brew install nasm dosfstools binutils
# Intel Mac の場合
$ export PATH=/usr/local/opt/binutils/bin:$PATH
# Apple Silicon Mac の場合
$ export PATH=/opt/homebrew/sbin:/opt/homebrew/opt/binutils/bin:$PATH
「2.2 EDK II でハローワールド」をビルドする
$ cd $HOME
$ mkdir workspace
$ cd workspace
$ git clone https://github.com/uchan-nos/mikanos.git
$ cd mikanos
$ git checkout osbook_day02a
$ cd $HOME/edk2
$ ln -s $HOME/workspace/mikanos/MikanLoaderPkg .
$ source edksetup.sh
https://github.com/uchan-nos/mikanos/commit/b5f7740c04002e67a95af16a5c6e073b664bf3f5 で行われいるのと同じ変更を MikanLoaderPkg/MikanLoaderPkg.dsc
に行います。(この変更は mikanos リポジトリの master ブランチには入っているが先ほどチェックアウトした osbook_day02a タグには入っていないので手動で変更する必要があります。)
Conf/target.txt
を開き次のように変更します。TOOL_CHAIN_TAG
が本とは異なるので注意してください。CLANG38 ツールチェインは Linux でしか使えないため代わりに CLANGPDB ツールチェインを使用します。
設定項目 | 設定値 |
---|---|
ACTIVE_PLATFORM | MikanLoaderPkg/MikanLoaderPkg.dsc |
TARGET | DEBUG |
TARGET_ARCH | X64 |
TOOL_CHAIN_TAG | CLANGPDB |
変更を保存したらビルドします。
$ build
$HOME/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi
というファイルが作られれば成功です。
QEMU で実行
次のようにして QEMU を実行します。
$ $HOME/osbook/devenv/run_qemu.sh $HOME/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi
うまくいくと QEMU が起動し、さっきビルドしたプログラムが実行されるはずです。
「3.3 初めてのカーネル」の補足
本とは異なり「LLVMのインストール」で LLVM14 を使うようにしました。LLVM 10 以降では -z separate-code
というオプションをつけてリンクしないと本書のブートローダでロードすることができません。(「ゼロからのOS自作入門」 03日目 を参考にさせていただきました。ありがとうございます。)
本の
$ ld.lld $LDFLAGS --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
の代わりに
$ ld.lld $LDFLAGS --entry=KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf -z separate-code main.o
とします。
「3.4 ブートローダからピクセルを描く」の補足
Linux では標準のシェルとして bash が使われているのに対し、Mac では zsh が使われています。変数に含まれるスペースの扱いが zsh と bash では異なるため、bash を前提としている本の
$ clang++ $CPPFLAGS -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
ではコンパイルできません。代わりに
$ clang++ ${=CPPFLAGS} -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
としてください。
「3.5 カーネルからピクセルを描く」の補足
『「2.2 EDK II でハローワールド」をビルドする』で TOOL_CHAIN_TAG
に本で指定している CLANG38 ではなく、CLANGPDB を指定しました。CLANG38 ツールチェインは System V AMD64 ABI を使用しますが、CLANGPDB ツールチェインは Microsoft x64 ABI を使用します。(ABI については本の「コラム 4.1 ABI」を参照してださい。)このため System V AMD64 ABI で引数が渡ってくることを想定しているカーネルの KernelMain
に引数を渡せません。そこで本のリスト 3.8 の
typedef void EntryPointType(UINT64, UINT64);
を
typedef void __attribute__((sysv_abi)) EntryPointType(UINT64, UINT64);
にすると、強制的に System V AMD64 ABI でカーネルを呼び出すことができ、引数を渡せます。
「4.1 make 入門」の補足
Makefile の LDFLAGS の行を変更し、-z separate-code
をつけてリンクするようにします。
LDFLAGS += --entry KernelMain -z norelro --image-base 0x100000 --static -z separate-code
「5.2 分割コンパイル」の補足
Makefile で sed を使うようになりますが、Linux の sed と Mac の sed はコマンドラインオプションが異なります。sed を使う行は
sed -I '' -e 's|$(notdir $(OBJ))|$(OBJ)|' $@
にする必要があります。
また本とは使用するツールチェインが異なるためだと思いますが、 make を実行すると
ld.lld: error: undefined symbol: __cxa_pure_virtual
>>> referenced by graphics.cpp
>>> graphics.o:(vtable for PixelWriter)
make: *** [kernel.elf] Error 1
というエラーが出るようになります。完全仮想関数を呼び出そうとしてしまった場合に呼ばれるエラーハンドラの実装を提供する必要があるそうです。(OS 自作に便利な C++ の機能 10 選(前編))
main.cpp に次のような実装を書いておくとビルドできるようになります。
extern "C" void __cxa_pure_virtual() {
while (1) __asm__("hlt");
}