Open7

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

S0r4tsuguS0r4tsugu
  • Windows 11 Pro 24H2
  • WSL2 (Ubuntu 22.04.5 LTS)

QEMU インストール。Windows に XServer をインストールしなくてもOK.

sudo apt install qemu-kvm
S0r4tsuguS0r4tsugu

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

S0r4tsuguS0r4tsugu

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

S0r4tsuguS0r4tsugu

ビルドで以下のエラーが出たら(このメッセージを見つけるのに少し時間かかった)、Loader.inf[Protocols]gEfiLoadedImageProtocolGuid の追加漏れがないかを確認する。gEfiLoadFileProtocolGuidgEfiSimpleFileSystemProtocolGuid も同様。

ld-temp.o:(.text.UefiMain+0x84): undefined reference to `gEfiLoadedImageProtocolGuid'
S0r4tsuguS0r4tsugu

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

S0r4tsuguS0r4tsugu

P80 の手順でビルドしたカーネルを QEMU で起動したところ、 KernelMain に到達せずハングする問題が発生した。調査の結果、原因は以下の 2 点だった。

  1. LLD 14 系によるエントリポイントのずれ( -z separate-code 追加で解消)
  2. 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 address0x101000 になった。

╭─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() の後にメモリマップを保存し中身を確認しても、 0x00100000EfiLoaderData となっており、やはりメモリには正しく読み込まれている模様。ファイルサイズも 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()やファイルやメモリ関連の機能など)を使うことができなくなります。

と書かれている。普通に見落としてた。。

S0r4tsuguS0r4tsugu

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 で起動したら、この画像のような表示、書籍に掲載されている画像とは違い斜めにグラデーションがかかってるような描画になった。これは正常に描画されたと思っていいんだろうか。