<!-- 注: このサイトはまだ工事中です -->

自作OS#0 UEFI編(1)

{
  date: "",
}

オタクもすなる自作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)を採用している。

ややこしいことを避けるために,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言語への移植がいくつかあるが,どうせなので自分で書いていく。

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

関数形式マクロの対応

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" おお〜

まとめ

次回へ続く……

参考