UEFIの呼び出し規則について調べたので忘れないようにメモしておきます。
呼び出し規則とバイナリフォーマット
UEFIのアプリケーションは以下の点が一般的なLinuxのアプリケーションと異なるため、Linux環境上でUEFIアプリをビルドするには解決の必要があります。
- 関数呼び出し規則(calling convention)が異なる(x86, x86_64の場合)
- 実行バイナリのファイル形式がPEフォーマット(UEFI Specification Version 2.5 Errata A pp.18 2.1.1 UEFI Images参照)
私のようにx86以外をターゲットとしてビルドする際は後者の解決が重要ですが、今回は前者について調べたことを記します。
呼び出し規則への対応
前者の呼び出し規則とは、関数を呼び出す際のレジスタの使い方などを決めているものです。 規則にはどのレジスタに引数を入れて渡すか、どのレジスタに戻り地を入れて返すかなどが決められているため、この規則が異なる関数をそのまま呼び出すことはできません。 この規則の違いがx86系のプロセッサ上で動作するUEFIアプリを作る際に問題となります。
x86 Linux向けにアプリをビルドすると、呼び出し規則はSystem V ABIに定められたものが使われます。 一方、UEFIではMS-ABIやMicrosoft x64 calling conventionと呼ばれるWindowsで使われる呼び出し規則を利用しているため、Linux等からUEFIアプリをビルドするためにはこの呼び出し規約の差異を解決する必要があります。 (UEFI Specification Version 2.5 Errata A pp.32 2.3.4.2 Detailed Calling Conventionsを参照。直接MS-ABIとは書いてはない)
私の知っている限りでは、この差異を解決する方法が2つあります。
- MS-ABIでビルドする
- ラッパー関数で呼び出し規則を修正する
MS-ABIでビルドする
前者はコンパイラにms-abiを用いるオプションを指定したり、attribute で指定することで解決を行います。
gnu-efiではコンパイラがms-abiに対応している場合、defineを使ってEFIAPIをつけた関数に __attribute__((ms_abi))
を適応してms-abiで呼び出すようにしています。
- efibind.h 188行目付近参照
ラッパー関数で呼び出し規則を修正する
後者はコンパイラがms-abiを利用できない場合に利用される方法です。 gnu-efi ではuefi_call_wrapperというラッパー関数(中身はマクロ)を利用して、引数の数に合わせて引数の順序を入れ替える関数を選んで使用しています。
- efibind.h 294行目以降参照
Linuxでも同じようなことをやっているようです(efi_call関数を参照)。
linux/efi_stub_64.S at 6f0d349d922ba44e4348a17a78ea51b7135965b1 · torvalds/linux · GitHub
なお、ARMやその他アーキテクチャでは基本的にLinuxと同様のABIを用いているので、ABIの変換作業は必要ありません。 実際にgnu-efiのラッパー関数も変換を行っていないようです。
gnu-efi / Code / [fc5af9] /inc/aarch64/efibind.h
以上です。 PEフォーマットの件は次回書きます。