CLang&CMake でUEFIアプリケーションをビルドする

# EDKⅡが動かないって話。　マーベリックだよね
初投稿です。至らない点が多々あると思いますがコメントで指摘してくださるとありがたいです。[みかん本](https://www.amazon.co.jp/ゼロからのOS自作入門-内田-公太/dp/4839975868)を元にOSを作ってみようとしたのですがな！ぜ！か！動かないしそもそもAppPkgディレクトリもないしで途方に暮れていたのと、そもそもEFIが搭載されていない環境（Raspberry Pi PICOとか）でもOSもどき的なものを作りたいと思った次第です。CMakeでEFIアプリケーションをビルドする紹介記事もな！ぜ！か！日本語ではほとんど見かけなかったので（な！ぜ！か！CMakeを使わずEDKⅡやらgnu-efiやらを使った記事ばかり...）CMakeでEFIアプリケーションをビルドするチュートリアル的なものを書いてみようと思った次第です。　
# EFIアプリケーションとは何者か？
そもそもEFIアプリケーションとは何なんでしょうか。OSと何が違うのでしょうか。OSは標準ライブラリやシステムコールなどパソコンの動作に必要な機能を提供します。しかし、パソコンは起動直後はシステムコールなどを使えません。パソコンはOSを起動してシステムコールなどを使えるようにする必要があります。これを行うのがEFIアプリケーションです。通常(U)EFIを搭載したコンピューターは自己診断プログラムを実行後ハードディスクにあるEFIアプリケーションを起動します。つまり(U)EFIを搭載したコンピューターはEFIアプリケーションさえあればOSを起動できます。ここでEFIがイカしてるのがEFIアプリケーションはEFIに対応してさえいればどんなパソコンでも起動できます。
 
EFIアプリケーションはPE形式を採用しています。つまりwindowsと同じ形式を採用しています。この後出てくるビルドプロセスでwindowsとかmicrosoftとかが出てくるのはそういうことです。




# 環境　
筆者が今回使用する環境は以下の通りです。
- macOS Ventura 13.4.1
- homebrew
- xcode-commandline-tools
- clang-16（homebrewでインストール)
- cmake（homebrewでインストール） 

clangに関してはそれなりに新しいものが必要です。基本的には
```
brew install llvm
```

でインストールするのがおすすめです。（これでllvm関係のツールとclangがインストールされます）　
基本的にllvmとclangとcmakeが入っていれば今回の方法で問題ないと思われます（他の環境で障害が発生した場合はコメントをお願いします）　
# ClangでEFIアプリケーションをビルドする 
ClangでEFIアプリケーションをビルドする例は[みかん本](https://www.amazon.co.jp/ゼロからのOS自作入門-内田-公太/dp/4839975868)に掲載されています。次はhello.c([みかん本](https://www.amazon.co.jp/ゼロからのOS自作入門-内田-公太/dp/4839975868)より引用)からEFIアプリケーション(hello World!)をビルドする例です

```c:main.c
#以下 [みかん本](https://www.amazon.co.jp/ゼロからのOS自作入門-内田-公太/dp/4839975868)より引用

typedef unsigned short CHAR16;
typedef unsigned long long EFI_STATUS;
typedef void *EFI_HANDLE;

struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
typedef EFI_STATUS (*EFI_TEXT_STRING)(
    struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, CHAR16 *String);

typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
  void *dummy;
  EFI_TEXT_STRING OutputString;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

typedef struct {
  char dummy[52];
  EFI_HANDLE ConsoleOutHandle;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
} EFI_SYSTEM_TABLE;

EFI_STATUS EfiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
  SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello, world!\n");
  while (1)
    ;
  return 0;
}
```
```zsh:ビルドコマンド 
clang -target x86_64-windows-msvc -mno-red-zone -fno-stack-protector -fshort-wchar -Wall hello.c -fuse-ld=lld-link -Wl,-subsystem:efi_application -Wl,-entry:EFIMain -Wl,-out:BOOTX64.efi -ffreestanding -flto -nostdlib
```
ソースコードの解説はここではしません。[みかん本](https://www.amazon.co.jp/ゼロからのOS自作入門-内田-公太/dp/4839975868)の1.9を読んでください。

# ビルドコマンドの解説 
## -target <ターゲット名>
ここでビルドするアーキテクチャとABIを指定しています。いわゆるtripleってやつです（わかんなくても大丈夫です）tripleは次の要素で構成されています
>  \<arch>\<sub>-\<vendor>-\<sys>-\<env>　

\<arch>はCPUのアーキテクチャ名です。x86_64がいわゆる64bitというやつです。ARM64bitであればaarch64が入ります。
\<sub>はなんかバージョン的なやつですarmで使う場合が多く、armであればv6m,v7a,v7mなどが入ります。Raspberry Pi PICOに搭載されているRP2040はcortex-m0+なのでv6mが入ると思われますｼﾗﾝｹﾄﾞ。
\<vendor>-\<sys>-\<env>はメーカーやらOSやらABIやらが入るらしいです。詳しくは[こちら](https://clang.llvm.org/docs/CrossCompilation.html#target-triple)をご覧ください。EFIアプリケーションをビルドする分にはwindows-msvcを\<arch>\<sub>-の後ろにつけておけば大丈夫です。つまり次のようになります。 
```
<x86_64 or aarch64>-windows-msvc
```
## -mno-red-zone -fno-stack-protector -fshort-wchar -Wall
よくわかんないです。正確には意味は一応ありますがこれを指定する必要性がよくわかりません。有識者の方コメントをお願いします。 
## -fuse-ld=lld-link
リンカの指定です。lld-link(windows用のlldリンカ)を指定しています。-fuse-ld=lldでも動きますが一応windows向けのlldを使いたいのでこうしています。 
# -Wl,*
-Wl,*の*にはリンカつまりlld-linkに渡される引数を指定することができます。-entry:EfiMainはエントリポイントを指定しています。つまりここを-entry:mainとすればmain関数を使えるということです。よくEDKⅡを使うときはEfiMainを使えとか言われますが別にそれに従う必要も特にありません。 

-entry:\<エントリポイント名>
-subsystem:efi_application

は必須となります。-out:<出力ファイル名>に関してはお好みで入れてください。ただし拡張子は.efiにしてください。パソコンがEFIアプリケーションであると認識するために必要です。

# -nostdlib
標準ライブラリが多分ビルド環境下にないのとあったとしてもベアメタル環境前提ではない（つまり共有ライブラリは基本使えない）と思われるので指定しておいてください。EFI環境で標準ライブラリを使用できる方法があればコメントで教えてください。 

# つまりCMakeで何をすれば良いのか
CMakeは先ほど紹介したコマンドを自動生成して実行してくれるものです。つまりCMakeには次のように指示をすればなんか上手いこと良い感じにやってくれるでしょう。たぶん。 (以下x64向け)
- -targetにx86_64-windows-msvcを指定する(コンパイラオプション）
- -fuse-ld=lld-linkでlld-linkを使用するように指定する(リンカオプション)
- -Wl,-subsystem:efi_applicationでリンカに-subsystem:efi_applicationを渡す（リンカオプション）
- -mno-red-zone -fno-stack-protector -fshort-wchar -Wallをコンパイルオプションとする
# CMake-toolsとcmake-kits.jsonについて 
本稿ではVSCodeとCMakeを組み合わせて使用するためCMake-tools拡張機能をインストールしています。この拡張機能にはkitsと呼ばれるコンパイラを指定するなどコマンドライン引数の入力を自動化してくれるものがあります。これを利用してtoolchain.cmakeをcmakeコマンドへ渡してこの段階でコンパイラの指定やコンパイルオプションの指定を行います。使用するにはワークスペースディレクトリ配下に.vscodeディレクトリを作り、その下にcmake-kits.jsonファイルを作成して記入します。つまり次のような配置になります。 
```
${workspaceFolder}/.vscode/cmake-kits.json
```
そしてcmake-kits.jsonは次のようにしてください。 
```json:cmake-kits.json
[
    {
        "name": "clang-efiBuild",
        "toolchainFile": "${workspaceFolder}/toolchains/x64-efi.cmake"
    }
]
```
"name"にはkitの名前を書きます(今回の場合は"clang-efiBuild"と表示されます)。"toolchainFile"には使用するtoolchainFileのパスを書きます(今回の場合は"${workspaceFolder}/toolchains/x64-efi.cmake"となります)。この他にもコンパイラを指定することもできます(デフォルトで提供されているkitはコンパイラのみを指定しているケースが多いです)詳細は[こちら](https://github.com/microsoft/vscode-cmake-tools/blob/main/docs/kits.md)を参照してください。 
# toolchainファイルの作成と配置 
今回はツールチェインの配置専用ディレクトリを作成してあるので次のような配置になります。 
```
${workspaceFolder}/
    ┣.vscode/
        └cmake-kits.json
    ┗toolchains/
        └x64-efi.cmake
    ┗CMakeLists.txt
```
x64-efi.cmakeがツールチェインとなります。toolchainファイルの書き方は[たのしい組み込みCMake](https://qiita.com/tnishinaga/items/c01dfd5f43e446653cd1)を参考に一部改変を加えています。今回は次のようにしました(解説は[たのしい組み込みCMake](https://qiita.com/tnishinaga/items/c01dfd5f43e446653cd1)に詳細が載っていますのでそちらを参照してください）。 
```cmake:x64-efi.cmake
set(CMAKE_C_COMPILER clang)
set(CMAKE_AS llvm-as)
set(CMAKE_NM llvm-nm)
set(CMAKE_OBJDUMP llvm-objdump)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER_TARGET x86_64-pc-windows-msvc)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-microsoft-static-assert -mno-red-zone -fno-stack-protector -fshort-wchar -target x86_64-pc-windows-msvc -nostdlib")
set(ld_flags "-Wl,-subsystem:efi_application -fuse-ld=lld -target x86_64-pc-windows-msvc -nostdlib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${ld_flags}")
```
大事なところだけ解説します。 
>set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-microsoft-static-assert -mno-red-zone -fno-stack-protector -fshort-wchar -target x86_64-pc-windows-msvc -nostdlib")

ここでコンパイラに渡すオプションを指定しています。大切なのは-targetを指定することです。CMAKE _C＿COMPILER＿TARGETでも指定していますがCMAKE_C_FLAGSに-targetを指定しないとコンパイラは認識しませんでした。指定してください。 
>set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
 
これはコンパイラテストのターゲットに静的ライブラリを使用することを指定しています。なぜならmac環境下ではwindows用の共有ライブラリはビルドできないためこれを指定しないとコンパイラーチェックが通りません。windows環境であればこれは不要かもしれませんが一応指定してください。
>set(ld_flags "-Wl,-subsystem:efi_application -fuse-ld=lld -target x86_64-pc-windows-msvc -nostdlib")
set(CMAKE_EXE_LINKER_FLAGS "\${CMAKE_EXE_LINKER_FLAGS} \${ld_flags}")

ここでリンカフラグを指定しています。-Wl,*で渡してる理由はリンカをclangで起動しているためです。リンカも`clang オブジェクトファイルたち`で起動することができます。というかCMakeは基本リンカを`lld-link`で起動せずに`clang`で起動するためここで指定するリンクオプションも-Wl,*で渡す必要があります。
ld_flags変数を作ってる理由はよくわかんないです。直接CMAKE_EXE_LINKER_FLAGSにリンカオプションを書き足してもうまくいきます。尚CMAKE_EXE_LINKER_FLAGSは実行ファイルに渡すリンカオプションです。この他にもCMAKE_SHARED_LINKER_FLAGSとかCMAKE_STATIC_LINKER_FLAGSがありますがefiアプリをビルドする分には指定しなくても特にエラーが起きていないため指定していません。必要に応じて指定してください。 

あとは普通にCMakeLists.txtを書いてadd_executableを使えばEFIアプリケーションをビルドできます。お疲れ様でした。

# まとめ 
[サンプルアプリのリポジトリ](https://github.com/danfunc/uefi-application-with-Cmake)です。intelMacで動作確認済みです(ARM Macだとうまく動かないかもしれません）。このリポジトリではUEFIのヘッダーファイルをEDKⅡからパクってきてますがそのための方法はまた今度~~書きます。~~ 
[書きました](https://qiita.com/DanfuncL/items/83d7aba597f3d6973485)
CMakeとclangを使ってEFIアプリケーションをビルドすることができましたとさ。
めでたしめでたし

# 参考文献
- [みかん本](https://www.amazon.co.jp/ゼロからのOS自作入門-内田-公太/dp/4839975868)
- [たのしい組み込みCMake](https://qiita.com/tnishinaga/items/c01dfd5f43e446653cd1)