MacでゼロからのOS自作入門をゼロから勉強中
はじめに
ゼロからのOS自作入門を買いました。
出張中で、手元にUbuntu PCが無いので、とりあえずMacで動かすためのメモです。
ネイティブのLinuxとMac(Intel)+Dockerで実践できることが分かりました。詳細は、以下のZennの記事にまとめました。
参考情報
参考情報です。随時追記します。
公式情報
YouTubeのvideoIDが不正です
重要情報
Macで動かすの絶望していたのですが、この記事に助けられました!
その他
今後必要になりそうな物のメモ
0章 OSって個人で作れるの?
読みました。ワクワクしてきました!
1章 PCの仕組みとハローワールド
Hello karaageしてみます。ここでは、Intel MacでエミュレータとしてQEMUを使うことを想定しています。
必要なツールのセットアップ
Homebrewを入れます。
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Homebrewに関しては、以下参照ください。
バイナリエディタ「0xED」をインストールします。
$ brew install 0xed
必要なツール類をインストールします。
$ brew install dosfstools qemu llvm nasm
QEMUはエミュレータです。MacでRaspberry Piを動かすためにも使えます(以下参照)。
dosfstoolsはMacでmkfsを使えるようにするためのソフトです。
続いて、llvmとdosfstoolsにパスを通します。
$ export PATH=/usr/local/Cellar/llvm/11.1.0/bin:$PATH
$ export PATH=/usr/local/Cellar/dosfstools/4.2/sbin:$PATH
この設定はターミナルを立ち上げるたびに必要になります。面倒なら.bashrc
か.zshrc
に書きましょう。.bashrc
に関しては以下記事参照ください。
これ以降は、パスを通すコマンドは省略します。llvmやmkfs関連でファイルが見つからないというエラーが出たら、パスを通すの忘れている可能性が高いので確認しましょう。
Hello Karaageに挑戦
書籍のHello, Worldを改造してHello, Karaageの表示にチャレンジします。
場所はどこでも良いですが、適当な作業ディレクトリを作ってその中で作業します。以下はwork
ディレクトリの中で作業する例です。
$ mkdir work && cd work
書籍のバイナリファイルを用意します。写経するのも良さそうですが、かなりハードル高いのでここは一つネットからお手本を落としてきましょう。
書籍からだと、バイナリのコードがどこにあるか少し分かりづらいですが、mikanos-buildリポジトリのday01/bin/hello.efi
がそれです。curlコマンドでダウンロードしましょう。
$ curl -O https://raw.githubusercontent.com/uchan-nos/mikanos-build/master/day01/bin/hello.efi
このままでも良いのですが、せっかくなので表示する文字を変えます。
hello.efi
をバイナリエディタ(0xED)で開きます。「Hello, World!」のところを「Hello,karaage」に変更します。
そのままで良い人は、この作業は不要です。
ここから書籍とMac で始める「ゼロからのOS自作入門」を参考に、コマンドを実行していきます。
イメージを作ってFATでフォーマットします。
$ qemu-img create -f raw karaage.img 200M
$ mkfs.fat -n 'KARAAGE OS' -s 2 -f 2 -R 32 -F 32 karaage.img
イメージをマウントして、バイナリファイル(Hello,karaage)を書き込んで、アンマウントします。
$ hdiutil attach -mountpoint mnt karaage.img
$ mkdir -p mnt/EFI/BOOT
$ cp hello.efi mnt/EFI/BOOT/BOOTX64.EFI
$ hdiutil detach mnt
IntelのPCの代わりに、エミュレータとしてQEMUを使います。書籍の通りにUEFIブートするために、OVMFファイルをmikanos-buildに用意してあるものをダウンロードします。
$ curl -O https://raw.githubusercontent.com/uchan-nos/mikanos-build/master/devenv/OVMF_CODE.fd
$ curl -O https://raw.githubusercontent.com/uchan-nos/mikanos-build/master/devenv/OVMF_VARS.fd
OVMFファイルに関しては、以下記事などを参考にしましょう。
最後に、バイナリファイルを書き込んだイメージをQEMUで読み込み、起動します。
$ qemu-system-x86_64 -drive if=pflash,file=OVMF_CODE.fd -drive if=pflash,file=OVMF_VARS.fd -hda karaage.img
以下のように表示されたらOKです。
C言語でハローワールド
バイナリでなく、C言語でハローワールドをしましょう。
C言語のソースをダウンロードして、以下のコマンドでコンパイル&リンクします。
$ curl -O https://raw.githubusercontent.com/uchan-nos/mikanos-build/master/day01/c/hello.c
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c hello.c
$ lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o
hello.efi
ができました。これを先ほどと同じ流れでQEMUで実行したら、Hello Worldが表示できます。
自動化
ここまで、理解を深めるために順を追ってコマンドをたくさん打ちました。ただ、毎回たくさんコマンドうつのは大変なので、手順を自動化します。自動化したスクリプトは、mikanos-buildに用意してあります。ただ、このままではMacに対応していないのでMac で始める「ゼロからのOS自作入門」を参考にスクリプトにパッチ当てたリポジトリを用意しました。
以下でダウンロードできます。
$ cd && git clone -b karaage https://github.com/karaage0703/mikanos-build osbook
バイナリに対して、以下コマンド実行するだけで、先ほど行った流れが一気に実行されて、QEMUで起動します。
$ ~/osbook/devenv/run_qemu.sh hello.efi
2章 EDK II入門とメモリマップ
EDK II入門
EDK IIというUEFI用の開発キットをセットアップします。書籍ではAnsibleを使って、スマートに環境構築されています。Linuxなら恐らく書籍のとおりにやれば半自動的にセットアップされると思うのですが、今回はMacを使うので地道にセットアップしていきます。
まずはedk2をダウンロードしてビルドします。
$ cd && git clone https://github.com/tianocore/edk2.git
$ cd edk2
$ git submodule update --init
$ cd BaseTools/Source/C
$ make
必要なファイルをダウンロードします。
$ cd ~/osbook/devenv
$ curl -L https://github.com/uchan-nos/mikanos-build/releases/download/v2.0/x86_64-elf.tar.gz | tar xz
mikanosをダウンロードします。
$ mkdir ~/workspace && cd ~/workspace
$ git clone https://github.com/uchan-nos/mikanos.git
$ cd mikanos
$ git checkout osbook_day02a
$ cd ~/edk2
$ ln -s $HOME/workspace/mikanos/MikanLoaderPkg ./
$ source edksetup.sh
Conf/target.txt を開き次のように変更します。
設定項目 設定値 ACTIVE_PLATFORM MikanLoaderPkg/MikanLoaderPkg.dsc TARGET DEBUG TARGET_ARCH X64 TOOL_CHAIN_TAG CLANGPDB
ビルドします。
$ cd ~/edk2
$ build
ビルドが終わって~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi
というファイルが生成されたらOKです。
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi
以下のように表示されたらOKです。
メモリマップの確認
$ cd ~/workspace/mikanos
$ git checkout osbook_day02b
$ cd ~/edk2
$ source edksetup.sh
$ build
QEMUを起動します。
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi
以下のように表示されたらOKです。
カレンとディレクトリに生成されたイメージファイルdisk.img
をマウントして中身を確認します。
$ hdiutil attach -mountpoint mnt disk.img
$ ls mnt
EFI/ memmap*
とmemmap
というファイルが表示されます。
中身を確認します。
$ cat mnt/memmap
中身は以下のようになっていました。
Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute
0, 3, EfiBootServicesCode, 00000000, 1, F
1, 7, EfiConventionalMemory, 00001000, 9F, F
...
確認したら、以下コマンドでアンマウントします。
hdiutil detach mnt
ポインタ入門
読みました。ポインタが理解できたような気がしてきました。
3章 画面表示の練習とブートローダ
QEMUモニタ
QEMUモニタの使い方を学びます。
CPUのレジスタの値を表示
(qemu) info registers
メモリの領域の値を表示
(qemu) x /4xb 0x067ae4c4
00000000067ae4c4: 0x00 0x00 0x00 0x00
書籍と値が違う…環境が違うから仕方ないのかな。
一応2命令文を逆アセンブル
(qemu) x /2i 0x067ae4c4
0x067ae4c4: 00 00 addb %al, (%rax)
0x067ae4c6: 00 00 addb %al, (%rax)
レジスタ
読みました。レジスタ奥が深いです。
初めてのカーネル
カーネルをコンパイルしてリンクします。
$ cd ~/workspace/mikanos
$ git checkout osbook_day03a
$ cd kernel
$ clang++ -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c main.cpp
$ ld.lld --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
main.cpp
の中身は以下たった3行の無限ループです。これがカーネルです。
extern "C" void KernelMain() {
while (1) __asm__("hlt");
}
ブートローダをビルドしてQEMUでカーネルを起動します。
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi ~/workspace/mikanos/kernel/kernel.elf
info registers
を実行します。
(qemu) info registers
RAX=000000003fb7b3e0 RBX=000000003fb79f3b RCX=000000003fb7b3e0 RDX=000000003fea03f8
RSI=0000000000000000 RDI=000000000080201a RBP=000000003fea87f0 RSP=000000003fea83d0
R8 =0000000000000001 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=000000003f308198 R13=0000000000000210 R14=000000003fb68234 R15=0000000000000006
RIP=000000003fb73016 RFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
RIP付近を確認します。
(qemu) x /2i 0x3fb73016
0x3fb73016: 48 83 7c 24 40 00 cmpq $0, 0x40(%rsp)
0x3fb7301c: 74 f8 je 0x3fb73016
書籍と違いますね。Linux(Ubuntu)とmacOSのコンパイラの違いでしょうか。よく分からないですが、0x3fb7301c
で0x3fb73016
にジャンプしているので、永久ループしているように見えます。
きっとこれで良いのでしょう。
ブートローダからピクセルを描く
$ cd ~/workspace/mikanos/kernel
$ git checkout osbook_day03b
$ clang++ -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c main.cpp
$ ld.lld --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi ~/workspace/mikanos/kernel/kernel.elf
カーネルからピクセルを描く
<cstint>
をincludeするための、ビルドの環境設定をします。ビルドするときに<cstint>
関係のエラーが出たら、以下忘れているので注意しましょう(よく本を読まずに前と同じ要領でビルドしたら失敗しました)。
$ source ~/osbook/devenv/buildenv.sh
パスが通っているか確認します。
echo $CPPFLAGS
カーネルをビルドします。
$ cd ~/workspace/mikanos/kernel
$ git checkout osbook_day03c
$ clang++ $CPPFLAGS -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
$ ld.lld $LDFLAGS --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
ブートローダーをビルドします。
$ cd ~/edk2
$ source edksetup.sh
$ source ~/osbook/devenv/buildenv.sh
$ build
QEMUで実行します。
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi ~/workspace/mikanos/kernel/kernel.elf
書籍では、模様が出るはずなのに単色の白…何か間違っていそう。
どう切り分けて良いのか分からないので、とりあえず以下にして画面が黒くなるか確認しました。
frame_buffer[i] = 0;
結果、カーネルで描く場合は白いままで、うまく描けていませんでした。前に戻ってブートローダで描く場合は、ちゃんと黒くなり正しく描けていました。カーネルの場合がうまくいっていないようです。
寄り道 フルスクラッチで作る!UEFIベアメタルプログラミング
以下のブログで、他にも自作OSの情報があることを知りました。
以下、無料で公開されています。
素晴らしい情報です。ただ、これ「ゼロからのOS自作入門」読んで事前知識なかったら、何から初めてよいか途方にくれていたと思います。ありがとう「ゼロからのOS自作入門」。
読んだ後なら、ちょちょいのちょいですね。以下でhello world的なプログラムをダウンロードして、コンパイル・ビルドします。
$ curl -O https://raw.githubusercontent.com/cupnes/c92_uefi_bare_metal_programming_samples/master/sample1_1_hello_uefi/main.c
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c main.c
$ lld-link /subsystem:efi_application /entry:efi_main /out:main.efi main.o
/entry:efi_main
とエントリーポイントを指定しないといけない点が注意です。これは「ゼロからのOS自作入門」では省略されていたポイントですね。「フルスクラッチで作る!UEFIベアメタルプログラミング」では、このあたり説明されているので、良い補完関係になっていると思います。
続いて、「ゼロからのOS自作入門」通りにイメージ作ります。
$ qemu-img create -f raw karaage.img 200M
$ mkfs.fat -n 'KARAAGE OS' -s 2 -f 2 -R 32 -F 32 karaage.img
$ hdiutil attach -mountpoint mnt karaage.img
$ mkdir -p mnt/EFI/BOOT
$ cp main.efi mnt/EFI/BOOT/BOOTX64.EFI
$ hdiutil detach mnt
QEMUで実行したらHello UEFI!
$ qemu-system-x86_64 -drive if=pflash,file=OVMF_CODE.fd -drive if=pflash,file=OVMF_VARS.fd -hda karaage.img
4章 ピクセル描画とmake入門
make入門
makeを試してみる。以下コマンドでビルド。
$ cd ~/workspace/mikanos
$ git checkout osbook_day04a
$ cd kernel
$ source ~/osbook/devenv/buildenv.sh
$ make
make cleanしてみる。
$ make clean
ピクセルを自在に描く
$ cd ~/workspace/mikanos
$ git checkout osbook_day04b
$ cd kernel
$ source ~/osbook/devenv/buildenv.sh
$ make
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/X64/Loader.efi ~/workspace/mikanos/kernel/kernel.elf
やはり白画…うーん、やはりうまくカーネルで絵が描けていない様子。QEMUのせいなのかなぁ。
実機でテスト
エミュレータの問題か、カーネルの問題か切り分けるために、実機でテストしてみる。
まずは、hello worldで動作確認するため、ファイルを落とします。
$ curl -O https://raw.githubusercontent.com/uchan-nos/mikanos-build/master/day01/bin/hello.efi
USBメモリを挿入。挿入したディスクのデバイスファイル名を確認。以下のコマンドで調べましょう。
$ df -h
どれがUSBメモリか自信が無い場合は、df -h
をUSBメモリ挿入前、挿入後で比較し、増えているものを確認すればOKです。以降は /dev/disk4
がUSBメモリの場合の説明です。
ディスクをアンマウント、FATでフォーマット、マウントしてファイルの書き込み、アンマントを実施します。
$ diskutil umountDisk /dev/disk4
$ sudo mkfs.fat /dev/disk4
$ mkdir mnt
$ sudo diskutil mount -mountPoint mnt /dev/disk4
$ mkdir -p mnt/EFI/BOOT
$ cp hello.efi mnt/EFI/BOOT/BOOTX64.EFI
$ diskutil umountDisk /dev/disk4
USBをPCに挿して起動します。
PCによって違いますが、手元にあったHPのPCのケースです。
起動後、ESCを押しっぱなしにすると、以下画面が出るので、F9を押します。
Boot From EFI Fileを選択します。
EFIを選択
Hello Worldが出ました
以下コマンドでUSBメモリにカーネルとブートローダを置いて、起動してみます。
$ diskutil umountDisk /dev/disk4
$ sudo mkfs.fat /dev/disk4
$ mkdir -p mnt/EFI/BOOT
$ cp ~/workspace/mikanos/kernel/kernel.elf mnt/
$ cp ~/edk2/Build/MikanLoaderX64/DEBUG_CLANGPDB/x64/Loader.efi mnt/EFI/BOOT/BOOTX64.EFI
$ diskutil umountDisk /dev/disk4
やっぱり白画。QEMUの問題では無さそうです。
うーん、分からん。難しいですね。
Dockerでチャレンジ
Dockerでチャレンジする方法もあったので、こちらを試してみます。Intel Macでテスト中。
事前準備として以下が必要です。
- Docker
- VS Code
- XQuartz
Docker
Dockerに関しては以下記事参考にしてインストール、起動しておきます。
VS Code
VS Codeに関しては、以下参照ください。
XQuartz
$ brew install xquartz
設定のセキュリティで以下設定
以下実行しておきます。
$ xhost + 127.0.0.1