切腹のイラスト

自作OS#0 UEFI編(1)

{
  date: "",
}

オタクもすなる自作OSといふものを、我もしてみむとて、するなり。

情報

  • 目標 : 実機で動く
  • 目的 : 完全にお遊び
  • 技術 : ほとんどをD言語で,1から実装してみる
  • ターゲット : 実機で動かしたいので,x86_64ベースで作っていく。
  • 開発 : GitHub(yamader/os)

UEFIアプリケーション

1口にOSといっても単語の当たり判定がデカいので,とりあえずカーネルを読み込み・実行するためのUEFIアプリケーションを作成するところからやっていきたいと思う。

UEFIはいろんなマシンで共通する規格のようなもので,その規格書はuefi.orgにて無料で公開されている。 いくつかのCPUのアーキテクチャに対応しているようだが,ターゲットがx86_64なので今回はx86_64を選ぶ。

開発環境

Gentooを使っているので,以下のようにした。そのうちDockerなりAnsibleなりのスクリプトを用意しようと思う。

# emerge -N crossdev
# crossdev --target x86_64-w64-mingw32

何故MinGWを入れているのかは,あとで説明する。

ビルド

D言語のコンパイラは,以下の理由からldc2(以下,ldc)を採用している。

  • なんか移植性高そう
  • クロスコンパイルの情報がすぐ見つかった
  • LLVMのドラゴンかっこいい

ややこしいことを避けるために,UEFIアプリケーション開発ではldcに-betterCフラグを渡している。 betterCでも十分に高機能だ。

UEFIアプリケーションはPE形式のフォーマットで作成する必要があるので,Linuxで開発を進めていくにあたり,それを扱うツールチェーンを用意する必要がある。 今回は無難にMinGWを選択したが,LLVMでも良いと思われる。

今回のMakefileではコンパイラとリンカを別々で呼び出している。 ldcコマンドでリンクまで済ませることもできるが,オプションがどうも長ったらしくなってしまうので,こうしている。
どちらにせよ,ldcはCOFF形式のオブジェクトファイルを吐く必要があるのだが,これは--mtriple=x86_64-w64-mingw32フラグ(clangの--target=...に対応するようなもの)を渡してやると対応できる。

コンパイル・リンク

さっき軽く説明した通りのオプション。最適化フラグ(-O2とか)を立ててみてもいいかも?

% ldc2 -betterC --mtriple=x86_64-w64-mingw32 --of=hoge.obj -c hoge.d

リンクには,MinGW(のbinutils)に入ってるldを使う。--subsystem 10で,これがUEFIアプリケーションであることを指定する。

% x86_64-w64-mingw32-ld -static --subsystem 10 -e piyo hoge.obj fuga.obj

ライブラリ

巷にはefi.hUefi.hのD言語への移植がいくつかあるが,どうせなので自分で書いていく。

以下に実装する必要がありそうなあれこれを列挙する。

  • enum EFI_STATUS
  • struct EFI_SYSTEM_TABLE
  • struct EFI_BOOT_SERVICES
    • enum EFI_ALLOCATE_TYPE
    • enum EFI_MEMORY_TYPE
    • struct EFI_MEMORY_DESCRIPTOR
  • struct EFI_RUNTIME_SERVICES
  • struct EFI_DEVICE_PATH_PROTOCOL
  • struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL

関数形式マクロの対応

D言語はよさげに関数をインラインに展開してくれるので,文字列レベルの操作を行う必要はない。

例えば上記のようなマクロは,D言語では関数で定義する。

bool EFI_ERROR(EFI_STATUS status) {
  return cast(INTN)(status) < 0;
}

実機で動かす

  1. 何らかのメディアをFAT32でフォーマットし,fdiskやpartedでパーティションタイプの変更(espフラグを立てる)をしておく
  2. 次に,それをマウントしてコンパイル・リンクしたバイナリをメディアの/EFI/BOOT/BOOTx64.EFIに置く

あとは人柱となるマシンにメディアを差し替えて起動させるだけ。

サンプル

import efi;

extern(C):

__gshared {
  EFI_SYSTEM_TABLE* gST;
  EFI_BOOT_SERVICES* gBS;
  EFI_RUNTIME_SERVICES* gRT;
}

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) {
  EFI_STATUS status;

  gST = SystemTable;
  gBS = gST.BootServices;
  gRT = gST.RuntimeServices;

  status = gST.ConOut.OutputString(gST.ConOut, cast(wchar*)"hello, world (from Dlang)"w);
  if(EFI_ERROR(status)) {
    return status;
  }

  while(true) asm { hlt; }
  return EFI_STATUS.EFI_SUCCESS;
}

UEFIで"hello, world" おお〜

まとめ

  • UEFIアプリケーションのビルドができるようになった

次回へ続く……

参考