ツールキットを使わずに UEFI アプリケーションの Hello World! を作る
UEFI アプリケーションを作成するためのツールキットには、EDK II や gnu-efi などがあります。しかし、EDK II は複雑すぎて中身の構造が分かりにくく、とっつきにくい印象があります。また、gnu-efi は wrapper 経由で関数を呼び出さなければならないなど、標準的な UEFI アプリケーションのソースコードと若干異なるものになってしまうのが気になります。
そこで、UEFI の勉強も兼ねて、これらのツールキットを使わずに、本当に最小限の Hello World を表示するだけのアプリケーションを作ってみました。
開発環境は Linux です。必要な物は PE32+ executable を作るためのクロスコンパイラです。私は Fedora を使っているので、下記のコマンドでインストールできました。他のディストリビューションを使っている場合は、適宜探してインストールしてみてください。
yum install mingw64-gcc
実際に作成してみたソースコード(main.c)は以下のとおりです。定義はコンソール出力に必要な物だけに絞っており、かなり端折ってあります。その代わり、見える部分の定義はなるべく UEFI の仕様書のとおりになるようにしてみました。
#define IN #define EFIAPI #define EFI_SUCCESS 0 typedef unsigned short CHAR16; typedef unsigned long long EFI_STATUS; typedef void *EFI_HANDLE; struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL; typedef EFI_STATUS (EFIAPI *EFI_TEXT_STRING) ( IN struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, IN CHAR16 *String ); typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { void *a; EFI_TEXT_STRING OutputString; } EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL; typedef struct { char a[52]; EFI_HANDLE ConsoleOutHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; } EFI_SYSTEM_TABLE; EFI_STATUS EFIAPI EfiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello World!\n"); while(1); return EFI_SUCCESS; }
EfiMain がエントリーポイントです。この関数に渡される EFI_SYSTEM_TABLE 型の SystemTable が各種システム・サービスへのポインタとなっています。その中に EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 型の ConOut というデータがあり、オブジェクト指向のような関数テーブルとなっています。この中の OutputString という関数を呼び出すと、コンソールに文字列を出力することが出来ます。出力する文字列は Unicode なので、文字列の定義に L を付けます。
上記の main.c をコンパイルするための Makefile は以下のとおりです。
CC = x86_64-w64-mingw32-gcc CFLAGS = -shared -nostdlib -mno-red-zone -fno-stack-protector -Wall \ -e EfiMain all: main.efi %.efi: %.dll objcopy --target=efi-app-x86_64 $< $@ %.dll: %.c $(CC) $(CFLAGS) $< -o $@ qemu: main.efi OVMF.fd image/EFI/BOOT/BOOTX64.EFI qemu-system-x86_64 -nographic -bios OVMF.fd -hda fat:image image/EFI/BOOT/BOOTX64.EFI: mkdir -p image/EFI/BOOT ln -sf ../../../main.efi image/EFI/BOOT/BOOTX64.EFI OVMF.fd: wget http://downloads.sourceforge.net/project/edk2/OVMF/OVMF-X64-r15214.zip unzip OVMF-X64-r15214.zip OVMF.fd clean: rm -f main.efi
クロスコンパイラとして x86_64-w64-mingw32-gcc を指定しています。他のディストリビューションでは名称が異なるようですので、適宜修正してください。
このクロスコンパイラを使って、まず普通の PE 形式のファイル main.dll を作ります。余計なものをリンクしないように、-nostdlib を付けています。また、エントリポイントとして EfiMain を指定しています。
生成された main.dll は Windows 等で使われる DLL などと同じ形式になっているので、objcopy を使って subsystem が 10 になるように変換して、UEFI アプリケーションとして認識されるようにします。
生成された main.efi を qemu を使って実行してみます。
まず、qemu 用の UEFI のファームウェアである OVMF.fd を入手します。http://tianocore.sourceforge.net/wiki/OVMF から最新のファームウェアをダウンロードできます。執筆時点では、r15214 でした。先ほどの Makefile を使うと自動的に入手できます。
make OVMF.fd
次に先ほどの Makefile を使って qemu を起動します。
make qemu
-nographic を指定することで、グラフィックスモードは使わずに、コンソールから起動するようにしています。-bios で UEFI のファームウェアのファイルを指定しています。-hda fat:image とすることで、image ディレクトリ以下を FAT フォーマットの仮想ディスクとして見えるようにしています。image 以下には EFI/BOOT/BOOTX64.EFI というファイル名で生成された EFI ファイルを置いておくことで、仮想マシンの起動時に自動的に EFI アプリケーションが実行されるようにしています。
うまくいけば、Hello World! と表示されて停止するはずです。Ctrl-a x で qemu を抜けましょう。
Boot Failed. EFI Floppy
Boot Failed. EFI Floppy 1
Boot Failed. EFI DVD/CDROM
Hello World!
QEMU: Terminated
※GitHub にコードを置きました.https://github.com/utshina/uefi-simple