Closed19

MacでゼロからのOS自作入門をゼロから勉強中

参考情報

参考情報です。随時追記します。

公式情報

https://zero.osdev.jp/

https://github.com/uchan-nos/mikanos-build

https://github.com/uchan-nos/mikanos

https://www.youtube.com/playlist?list=PLbBGNsln3DxTLHB9GFM6_drAJ1JQXIOud

重要情報

Macで動かすの絶望していたのですが、この記事に助けられました!

https://qiita.com/yamoridon/items/4905765cc6e4f320c9b5

その他

今後必要になりそうな物のメモ

https://github.com/KhaosT/ACVM

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に関しては、以下参照ください。

https://karaage.hatenadiary.jp/entry/2016/05/13/073000

バイナリエディタ「0xED」をインストールします。

$ brew install 0xed

必要なツール類をインストールします。

$ brew install dosfstools qemu llvm nasm

QEMUはエミュレータです。MacでRaspberry Piを動かすためにも使えます(以下参照)。

https://qiita.com/karaage0703/items/366eb17906a3341f1999

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に関しては以下記事参照ください。

https://karaage.hatenadiary.jp/entry/2016/03/30/073000

これ以降は、パスを通すコマンドは省略します。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ファイルに関しては、以下記事などを参考にしましょう。

https://qiita.com/kakinaguru_zo/items/f74bcfbf9f75d7e7a913

最後に、バイナリファイルを書き込んだイメージを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

Mac で始める「ゼロからのOS自作入門」より引用

ビルドします。

$ 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のコンパイラの違いでしょうか。よく分からないですが、0x3fb7301c0x3fb73016にジャンプしているので、永久ループしているように見えます。

きっとこれで良いのでしょう。

ブートローダからピクセルを描く

$ 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の情報があることを知りました。

https://fukuno.jig.jp/3165

以下、無料で公開されています。

http://yuma.ohgami.jp/UEFI-Bare-Metal-Programming/

素晴らしい情報です。ただ、これ「ゼロからの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でテスト中。

https://zenn.dev/sarisia/articles/6b57ea835344b6

事前準備として以下が必要です。

  • Docker
  • VS Code
  • XQuartz

Docker

Dockerに関しては以下記事参考にしてインストール、起動しておきます。

https://karaage.hatenadiary.jp/entry/2019/05/17/073000

VS Code

VS Codeに関しては、以下参照ください。

https://zenn.dev/karaage0703/books/80b6999d429abc8051bb/viewer/5b814b

XQuartz

$ brew install xquartz

設定のセキュリティで以下設定

以下実行しておきます。

$ xhost + 127.0.0.1

https://gokids.hatenablog.com/entry/2018/11/14/190000
このスクラップは4ヶ月前にクローズされました
ログインするとコメントできます