はじめに
UEFI仕様のBIOSにHello World!
を表示するブートファイルをRustで作成します。
今回使用した開発環境は以下です。
- CPUのアーキテクチャ:x86_64
- 開発環境のホストOS:Windows11
- 実行環境:devcontainer
- エミュレーター:QEMU
環境構築
実行環境にはvscodeの拡張機能「devcontainer」を使っています。
検証はしておりませんが、devcontainerを使わない場合でも必要なものをインストールすれば、各自の環境でも開発は可能と思われます。
devcontainerの説明は割愛し、設定ファイルのみ公開します。
{
"name": "Rust",
"build": {
"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
を追加してください。
[build]
target = "x86_64-unknown-uefi"
[target.x86_64-unknown-uefi]
runner = "uefi-run"
実装
src
ディレクトリで必要なプログラムを実装していきます。
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の仕様書を参考にして、必要なプロトコルの呼び出し部分だけ実装しています。
#![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開発を進めていく予定ですので、進捗がありましたら共有いたします。
参考書