zebian.log

技術系備忘録とか

RustでUEFIアプリのHello, world!

github.com

GRUBx86自作OSを動かす(しかもUEFI非対応)という、とんでもなくレガシーな自作OSを作り始めて早半年。やっぱりx86_64でUEFI対応の自作OS作りたくね?自作ブートローダーで動かしたくね?って思ったので、まずは自作ブートローダーをどうやって作るかを調べた。

「ゼロからのOS自作入門」を眺めたら、どうやらブートローダーにEDK IIというUEFIアプリを開発するためのSDKを使っているらしいことがわかった。つまりブートローダー=UEFIアプリだということがわかったので、とりあえずRustでUEFIアプリのHello Worldをやってみることにする(Rust依存症)。

book.mynavi.jp github.com

プロジェクト作成

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の中でベアメタル環境でも使用できるクレート」を指定し、それらをビルドに含める設定をしている。corememcpy,memcmp,memsetを必要としていて、それらが含まれるcompiler_builtinsも含めなければならない。allocではグローバルアロケータを自前で実装することによって、コレクション等が使えるようになる。また、build-std-featurescompiler-builtins-memを指定することで、前述のmemcpy等が使えるようになる(コンパイラがこれらを提供している?)。

次にCargo.toml[dependencies]に追記する。

[dependencies]
uefi = { version = "0.16.0", features = ["exts"] }
uefi-services = "0.13.0"

github.com

ここで登場するのが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(プロジェクト名).efiEFIバイナリが生成される。あとはこれをQEMUで実行するだけ。

QEMU

実行する前に、QEMUUEFIを動かすための準備が必要。

OVMFのインストール

QEMUUEFIファームウェアを動かすために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