自作OS#0 UEFI編(1)

目次

オタクもすなる自作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形式(Wind○ws用)のバイナリで作成する必要があるので,Linuxで開発を進めていくにあたり,それを吐くコンパイラ・リンカを用意する必要がある. 筆者は無難にMinGW環境を選択した.
ここで,ldcがCOFF形式のオブジェクトファイルを吐く必要があるのだが,これは-mtriple=x86_64-w64-mingw32フラグ(clangの--targetに対応するようなもの)を渡してやるとよい.

ローダーのmakefileではコンパイラとリンカを独立したコマンドで呼び出しているが,これに特に深い意味はない.

コンパイル

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

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

リンク

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

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

ライブラリ

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

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

  • struct EFI_SYSTEM_TABLE : エントリーポイントに渡される,全体的に使われるデータ構造
  • enum EFI_STATUS : Boot ServicesとかRuntime Servicesとかの戻り値として使われたりする
  • struct EFI_BOOT_SERVICES : Boot Services
    • enum EFI_ALLOCATE_TYPE : 使う
    • enum EFI_MEMORY_TYPE : 使う
    • struct EFI_MEMORY_DESCRIPTOR : 使う
  • struct EFI_RUNTIME_SERVICES : Runtime Services
  • struct EFI_DEVICE_PATH_PROTOCOL : Device Path Protocol
  • struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL : コンソール出力に使う

関数形式マクロの対応

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

まとめ

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

次回へ続く……

参考