ゼロから始めるOS自作入門 めも

- Windows 11 Pro 24H2
- WSL2 (Ubuntu 22.04.5 LTS)
QEMU インストール。Windows に XServer をインストールしなくてもOK.
sudo apt install qemu-kvm

P51 でブートローダーをビルドしようとしたら Instance of library class [RegisterFilterLib] is not found
とエラーになってビルドに失敗。
以下 Issue のコメントに書かれているとおり、edk2
のディレクトリに移動し git checkout 4ac0296
で特定のコミットに戻すことでビルドが成功した。
GitHub Issue - ブートローダーのビルド

VS Code (拡張機能で Microsoft C/C++ をインストール)を使って開発する際に Uefi.h
などが cannot open source file "Uefi.h"
となる場合は、拡張機能の設定の Include path
に ~/edk2/**
を追加。

ビルドで以下のエラーが出たら(このメッセージを見つけるのに少し時間かかった)、Loader.inf
の [Protocols]
に gEfiLoadedImageProtocolGuid
の追加漏れがないかを確認する。gEfiLoadFileProtocolGuid
や gEfiSimpleFileSystemProtocolGuid
も同様。
ld-temp.o:(.text.UefiMain+0x84): undefined reference to `gEfiLoadedImageProtocolGuid'

P80 ブートローダービルドするときに Loader.inf
の [Guids]
に gEfiFileInfoGuid
を追加する。

P80 の手順でビルドしたカーネルを QEMU で起動したところ、 KernelMain
に到達せずハングする問題が発生した。調査の結果、原因は以下の 2 点だった。
- LLD 14 系によるエントリポイントのずれ(
-z separate-code
追加で解消) -
ExitBootServices()
後にPrint()
を呼んでいせいでブートサービス使用で落ちた
以下は、上記の原因にたどり着くまでのメモ。ちなみに、めっちゃ時間かかった。
(qemu) info registers
...
RIP=000000003fb73016 RFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
...
(qemu) x /2i 0x3fb73016
0x000000003fb73016: cmpq $0x0,0x40(%rsp)
0x000000003fb7301c: je 0x3fb73016
(qemu)
最初原因が全く分からず、 とりあえず Print()
デバッグをしまくったが手がかりゼロ。
色々調べたところ GitHub Issue - [質問] P80にて,qemuモニタの出力が異なり,KernelMainが読み込まれない を見つけた。
リンカのバージョンにより、エントリーポイントアドレスが異なっていたことが原因。
書籍では LLD 9 系のバージョンを使用しているようで、私が使った LLD 14 系だと、エントリーポイントアドレスが 0x101120
とズレてしまう。
リンカのオプションで -z separate-code
を追加すれば、 0x101000
になる。
最終的に以下のコマンドでカーネルをビルド。
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 -z separate-code \
-o kernel.elf main.o
バージョンは以下。
╭─s0r4tsugu at MP in ~/development/homemade_os/myos/kernel 25-04-22 - 20:24:47
╰─○ ld.lld --version
Ubuntu LLD 14.0.0 (compatible with GNU linkers)
╭─s0r4tsugu at MP in ~/development/homemade_os/myos/kernel 25-04-22 - 20:24:51
╰─○ clang++ --version
Ubuntu clang version 14.0.0-1ubuntu1.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
╭─s0r4tsugu at MP in ~/development/homemade_os/myos/kernel 25-04-22 - 20:24:53
╰─○
Entry point address
が 0x101000
になった。
╭─s0r4tsugu at MP in ~/development/homemade_os/myos/kernel 25-04-22 - 20:24:41
╰─○ readelf -h kernel.elf
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x101000
Start of program headers: 64 (bytes into file)
Start of section headers: 8960 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 4
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 12
╭─s0r4tsugu at MP in ~/development/homemade_os/myos/kernel 25-04-22 - 20:24:47
╰─○
しかし、エントリーポイントアドレスの修正だけではまだハング。
GitHub Issue - kenelのhalt命令が見つけ出せない. を参考に、メモリの中身を確認。
QEMU のメモリに読み込まれているカーネル。
(qemu) xp/40xb 0x100000
0000000000100000: 0x7f 0x45 0x4c 0x46 0x02 0x01 0x01 0x00
0000000000100008: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0000000000100010: 0x02 0x00 0x3e 0x00 0x01 0x00 0x00 0x00
0000000000100018: 0x00 0x10 0x10 0x00 0x00 0x00 0x00 0x00
0000000000100020: 0x40 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(qemu) xp/40xb 0x101000
0000000000101000: 0x55 0x48 0x89 0xe5 0x66 0x2e 0x0f 0x1f
0000000000101008: 0x84 0x00 0x00 0x00 0x00 0x00 0x66 0x90
0000000000101010: 0xf4 0xeb 0xfd 0xcc 0xcc 0xcc 0xcc 0xcc
0000000000101018: 0xcc 0xcc 0xcc 0xcc 0xcc 0xcc 0xcc 0xcc
0000000000101020: 0xcc 0xcc 0xcc 0xcc 0xcc 0xcc 0xcc 0xcc
(qemu)
カーネルファイル。
╰─○ od -tx1 kernel.elf
0000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
0000020 02 00 3e 00 01 00 00 00 00 10 10 00 00 00 00 00
...
0010000 55 48 89 e5 66 2e 0f 1f 84 00 00 00 00 00 66 90
0010020 f4 eb fd cc cc cc cc cc cc cc cc cc cc cc cc cc
カーネルファイルの内容はメモリに正しく読み込まれている。
kernel_file->Read()
の後にメモリマップを保存し中身を確認しても、 0x00100000
は EfiLoaderData
となっており、やはりメモリには正しく読み込まれている模様。ファイルサイズも 9856 バイトで 3 ページに収まっている。
Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute
0, 3, EfiBootServicesCode, 00000000, 1, F
1, 7, EfiConventionalMemory, 00001000, 9F, F
2, 2, EfiLoaderData, 00100000, 3, F
3, 7, EfiConventionalMemory, 00103000, 6FD, F
4, A, EfiACPIMemoryNVS, 00800000, 8, F
...
これも原因が分からず、 最終的に ChatGPT にコード投げて聞いたら、「Boot Services を切ると Print() は使えない」という回答があった。
コードを再度確認すると、ブートサービス停止後に Print()
を呼んでいる箇所があった( entry_addr
の値を確認するために差し込んだやつ)。これをコメントアウトしてビルド・実行したら無事カーネルのコードが実行された。
(qemu) info registers
...
RIP=0000000000101011 RFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=1
...
(qemu) x /2i 0x101011
0x0000000000101011: jmp 0x101010
0x0000000000101013: int3
(qemu) x /2i 0x101010
0x0000000000101010: hlt
0x0000000000101011: jmp 0x101010
(qemu)
結局 ExitBootServices()
後に Print()
を呼んでいたせいで、そこでハングしていた模様。「 ExitBootServices()
後の Print()
を削除」で解決。
ちなみに書籍の P78 にもちゃんと
その後はブートサービスの機能(Print()やファイルやメモリ関連の機能など)を使うことができなくなります。
と書かれている。普通に見落としてた。。

P85 で clang++ $CPPFLAGS -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
を実行したところ、以下のように $CPPFLAGS
の変数展開がうまくいかずにエラー(no such file or directory)となった。
シェルは zsh 5.8.1 (x86_64-ubuntu-linux-gnu)
╭─s0r4tsugu at MP in ~/development/homemade_os/myos/kernel 25-06-06 - 20:28:33
╰─○ clang++ $CPPFLAGS -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
clang: error: no such file or directory: ' -I/home/s0r4tsugu/osbook/devenv/x86_64-elf/include/c++/v1 -I/home/s0r4tsugu/osbook/devenv/x86_64-elf/include -I/home/s0r4tsugu/osbook/devenv/x86_64-elf/include/freetype2 -I/home/s0r4tsugu/edk2/MdePkg/Include -I/home/s0r4tsugu/edk2/MdePkg/Include/X64 -nostdlibinc -D__ELF__ -D_LDBL_EQ_DBL -D_GNU_SOURCE -D_POSIX_TIMERS -DEFIAPI='__attribute__((ms_abi))''
Zsh はデフォルトで変数展開時に半角スペースを使った単語分割を行わないことが、このエラーの原因のようだ。
以下のように Zsh 独自の パラメータ展開フラグ を使用して、正常にビルドを実行できた。
パラメータ展開フラグを使わずに オプション - SH_WORD_SPLIT setopt shwordsplit
(一時的に単語分割を有効化)でも同様の効果が得られる。ただし、unsetopt
するか、シェルセッションが終了するまで設定が維持され、他の変数展開にも影響するため注意が必要。
clang++ ${(z)CPPFLAGS} -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
ちなみにこれをビルドして QEMU で起動したら、この画像のような表示、書籍に掲載されている画像とは違い斜めにグラデーションがかかってるような描画になった。これは正常に描画されたと思っていいんだろうか。