🦀

RustでUEFIのBIOSにHello World!を出す

2024/10/14に公開

はじめに

UEFI仕様のBIOSにHello World!を表示するブートファイルをRustで作成します。
今回使用した開発環境は以下です。

  • CPUのアーキテクチャ:x86_64
  • 開発環境のホストOS:Windows11
  • 実行環境:devcontainer
  • エミュレーター:QEMU

環境構築

実行環境にはvscodeの拡張機能「devcontainer」を使っています。
検証はしておりませんが、devcontainerを使わない場合でも必要なものをインストールすれば、各自の環境でも開発は可能と思われます。

devcontainerの説明は割愛し、設定ファイルのみ公開します。

devcontainer.json
{
	"name": "Rust",
	"build": {
		"dockerfile": "Dockerfile"
	}
}
Dockerfile
FROM mcr.microsoft.com/devcontainers/rust:1-1-bullseye

RUN sudo apt update && apt upgrade -y && apt install qemu-system -y \
    && rustup target add x86_64-unknown-uefi && cargo install uefi-run
  • エミュレーター「QEMU」をインストールするためにapt install qemu-systemします。
  • RustでUEFIでの実行形式をターゲットとしてコンパイルするためにrustup target add x86_64-unknown-uefiします。
  • Rustランナーをインストールするためにcargo install uefi-runします。

3つ目のuefi-runのインストールは任意ですが、こちらを使えばターミナルでcargo runを実行するだけで、自動的にQEMUでコンパイルしたブートファイルが起動します。

各種設定

devcontainerを起動後、Rustの設定をしていきます。
まずはcargo initを実行して、作業ディレクトリに.cargo/config.tomlを追加してください。

.cargo/config.toml
[build]
target = "x86_64-unknown-uefi"

[target.x86_64-unknown-uefi]
runner = "uefi-run"

実装

srcディレクトリで必要なプログラムを実装していきます。

src/uefi.rs
use core::ffi::c_void;

pub type EfiStatus = usize;
pub type EfiHandle = *const c_void;

#[repr(C)]
struct EfiTableHeader {
    signature: u64,
    revision: u32,
    header_size: u32,
    crc32: u32,
    reserved: u32,
}

#[repr(C)]
pub struct EfiSystemTable {
    hdr: EfiTableHeader,
    firmware_vendor: *const u16,
    firmware_revision: u32,
    console_in_handle: EfiHandle,
    con_in: usize,
    console_out_handle: EfiHandle,
    con_out: *const EfiSimpleTextOutputProtocol,
}

impl EfiSystemTable {
    pub fn con_out(&self) -> &EfiSimpleTextOutputProtocol {
        unsafe { &(*self.con_out) }
    }
}

#[repr(C)]
pub struct EfiSimpleTextOutputProtocol {
    reset: unsafe fn(this: &Self, extended_verification: bool) -> EfiStatus,
    output_string: unsafe fn(this: &Self, string: *const u16) -> EfiStatus,
}

impl EfiSimpleTextOutputProtocol {
    pub fn reset(&self, extended_verification: bool) -> EfiStatus {
        unsafe { (self.reset)(self, extended_verification) }
    }

    pub fn output_string(&self, string: *const u16) -> EfiStatus {
        unsafe { (self.output_string)(self, string) }
    }
}

以下のUEFIの仕様書を参考にして、必要なプロトコルの呼び出し部分だけ実装しています。

https://uefi.org/specs/UEFI/2.10_A/

src/main.rs
#![no_std]
#![no_main]

mod uefi;

#[no_mangle]
pub fn efi_main(
    _image_handle: uefi::EfiHandle,
    system_table: &uefi::EfiSystemTable,
) -> uefi::EfiStatus {
    system_table.con_out().reset(false);
    system_table.con_out().output_string(
        [
            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0,
        ]
        .as_ptr(),
    );

    loop {}
}

#[cfg(not(test))]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

0x48, 0x65, 0x6c, 0x6c, 0x6f, ...はそれぞれ表示する一文字に対応しています。

実行

cargo runで実行して、しばらく待つとHello World!が表示されます。

おわりに

ソースコードの説明が少なくて申し訳ありませんが、気が向いたときに追加するかもしれません。
RustでのOS開発を進めていく予定ですので、進捗がありましたら共有いたします。

参考書

https://zero.osdev.jp/

Discussion