↑GRUBでx86自作OSを動かす(しかもUEFI非対応)という、とんでもなくレガシーな自作OSを作り始めて早半年。やっぱりx86_64でUEFI対応の自作OS作りたくね?自作ブートローダーで動かしたくね?って思ったので、まずは自作ブートローダーをどうやって作るかを調べた。
「ゼロからのOS自作入門」を眺めたら、どうやらブートローダーにEDK IIというUEFIアプリを開発するためのSDKを使っているらしいことがわかった。つまりブートローダー=UEFIアプリだということがわかったので、とりあえずRustでUEFIアプリのHello Worldをやってみることにする(Rust依存症)。
プロジェクト作成
cargo new bootloader cd bootloader echo "nightly" >> rust-toolchain
.cargo/config.toml
を作成する。
[unstable] build-std = ["core", "compiler_builtins", "alloc"] build-std-features = ["compiler-builtins-mem"] [build] target = "x86_64-unknown-uefi"
no_std
のベアメタル環境で作るので、標準ライブラリstdを使うことはできない。build-std
では、「stdの中でベアメタル環境でも使用できるクレート」を指定し、それらをビルドに含める設定をしている。core
はmemcpy
,memcmp
,memset
を必要としていて、それらが含まれるcompiler_builtins
も含めなければならない。alloc
ではグローバルアロケータを自前で実装することによって、コレクション等が使えるようになる。また、build-std-features
にcompiler-builtins-mem
を指定することで、前述のmemcpy
等が使えるようになる(コンパイラがこれらを提供している?)。
次にCargo.toml
の[dependencies]
に追記する。
[dependencies] uefi = { version = "0.16.0", features = ["exts"] } uefi-services = "0.13.0"
ここで登場するのがuefi-rsクレート。UEFIアプリケーション開発歴1日の自分にとってはとても心強い。しかもリポジトリにUEFIアプリのテンプレートがついてる親切設計。正直使おうかどうかかなり迷ったが(自作OSと呼べるのか問題)、ここで永遠に躓く訳には行かないので素直に使わせてもらうことにした。
src/main.rs
を変更する。
#![no_std] #![no_main] #![feature(abi_efiapi)] use core::fmt::Write; use uefi::prelude::*; #[entry] fn efi_main(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status { uefi_services::init(&mut system_table).unwrap(); system_table.stdout().reset(false).unwrap(); writeln!(system_table.stdout(), "Hello, world!"); // return Status::SUCCESS; return Status::ABORTED; }
戻り値をStatus::SUCCESS
にすると一瞬で画面が変わってしまうので、あえて別のものに差し替えている(戻り値を消してloopにしてもよかったかも)。これだけでHello, world!が完成してしまった...
ちなみに字下げスタイルなのはC#の影響を強く受けすぎてしまったせい(どの言語でも大体字下げスタイルで書いてる)。
cargo build --release
cargo build
でコンパイルすると、target/x86_64-unknown-uefi/release/bootloader(プロジェクト名).efi
にEFIバイナリが生成される。あとはこれをQEMUで実行するだけ。
QEMU
OVMFのインストール
QEMUでUEFIファームウェアを動かすためにOVMFをインストールする。
sudo apt install ovmf
イメージの作成
UEFIファームウェアの仕様で、電源を入れるとEFIシステムパーティションからブートローダーを自動的に探して起動するらしい。UEFI Specification 13.3.1. System Partitionに詳しい説明が載っているので見てみると、MBRの先頭セクタまたはGPTのLBA1にEFIシステムパーティションを配置する必要があるらしい。が、今回は複数のパーテーションを作る必要はないので、特にそういった作業はしない。 uefi.org ja.wikipedia.org wiki.archlinux.jp
mkdir build qemu-img create -f raw ./build/app.img 200M mkfs.fat -F 32 -s 2 ./build/app.img sudo mount -o loop ./build/app.img /mnt sudo mkdir -p /mnt/EFI/BOOT sudo cp ./target/x86_64-unknown-uefi/release/bootloader.efi /mnt/EFI/BOOT/BOOTX64.EFI sudo umount /mnt
まずqemu-imgで200MB分の空イメージを作成。それをFAT32でフォーマットし、ループバックデバイスとして/mnt
にマウントする。UEFI Specification 13.3.1.3. Directory Structureを見てみると
\EFI \BOOT BOOT{machine type short name}.EFI
BOOT以下にサポートされている各アーキテクチャ用の実行可能なEFI イメージを一つだけ存在する必要がある、という仕様が。ということで、コンパイルしたEFIイメージをここにコピー。{machine type short name}
はX64
にすればok。
いざ実行!
qemu-system-x86_64 \ -bios /usr/share/edk2-ovmf/x64/OVMF.fd \ -device ahci,id=ahci \ -device ide-cd,drive=disk,bus=ahci.0 \ -drive id=disk,if=none,format=raw,file=build/app.img
ざーっと書いてみたが、もっと短い書き方があるような気がする。 できたー!!!ウィンドウサイズが固定されてて若干見づらいかもしれないが、「Hello, world!」の文字が。uefi-rsクレートの作者さんには感謝しかないですね。
参考サイト
こんな初心者記事よりむしろこっちを見たほうが参考になります。 zenn.dev sksat.hatenablog.com