link.exeの/?オプションの簡易ヘルプを見ていたら/MACHINE:{ARM|ARM64|EBC|X64|X86}の選択肢EBCが気になった。

EBCはEFI Byte Codeのことで、今どきのPC/AT互換機に備わっている旧来のBIOSを置き換えるUEFIと呼ばれるファームウェア上で動かせるプログラムのことらしい。インテル的には2020年にはMBRをロードするBIOSは無くしたいらしくピュアなUEFIに一本化するみたい。個人的にはDOS互換が完全に消え失せたらPC/AT互換機ではないしDOS/V機でも無くなるっていうのが残念で仕方ないが、今使ってるWindows 10もUEFIブートマシンだからたぶん困ることも無さそう。

前に調べた通りUEFIはストレージのFATファイルシステムを解釈できてファイル単位でデータを読み込んでOSを起動できる。その仕組みを組むのがEBCでできたEFIアプリケーションってことなので、EFIアプリを自分で作れるってことは、OSを起動する前にコンピュータにあれこれ指示できるっていうことになる。これはおもしろそう。EFIアプリ自体はWindowsのアプリExe形式と同じでPortable Executable形式になっていて、EDKⅡgnu-efiという開発ツールを利用すると良いことになっている。ネット上にはgnu-efiとgccを使う例はかなりたくさん出ているし、EDK2+Visual Studioの例も結構出ててそれらの手順でやればほぼ問題なく作成できるのだが、フルでEDKをインストール・セットアップしてVSでごりごりやりましょうというものか、フルのスクラッチでSDKすら使わないよとか、GNU/Linuxのgccでgnu-efiを使うのがジョーシキっしょ、みたいのが多くて自分的にはもうちょっとあっさりした環境でやりたかったからちょっと縛りを入れて、要件は次の通りとした。

・Visual C++ (2013 Professioanl)のコマンドラインコンパイラとリンカのみを使う。
・EDK2も含めて追加のライブラリ、ユーティリティは使わない。
・各種定義を無から書くのは面倒なので、EDK2のヘッダを拝借する。

1.EDK2をダウンロードして、適当なフォルダに展開。

2.VC++のコマンドラインコンパイラ(64ビット環境)が使える「CL環境」にEDK2のインクルードパスに2か所追加する。

set include=%include%;C:\edk2\MdePkg\Include;C:\edk2\MdePkg\Include\X64

3.コードを書く。

※Wordpressの表示でおかしくなるので半角の<>は全角の<>に置換。

[EfiHello1.cpp]

// EDKⅡのヘッダファイルを流用
extern "C" {
#pragma warning(disable:4804)
#include <Uefi.h>
}

// EFIアプリケーション エントリーポイント
EFI_STATUS EFIAPI EfiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
	
	// 初期値5分で発動するウオッチドッグタイマーを無効にする。
	SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, (CHAR16*)NULL);
	
	// 画面クリアとメッセージ出力
	SystemTable->ConOut->ClearScreen(SystemTable->ConOut);
	SystemTable->ConOut->OutputString(SystemTable->ConOut, (CHAR16*)L"Hello World!\r\n");

	// キー入力とエコーバックを行う
	// 'q'でプログラム終了
	EFI_INPUT_KEY key;		// キー入力受け取り用
	wchar_t buf[2] = L" ";	// 表示用バッファ
	while(TRUE) {
		// キー入力
		if (!SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &key)) {
			// エコーバック
			buf[0] = key.UnicodeChar;
			SystemTable->ConOut->OutputString(SystemTable->ConOut, (CHAR16*)buf);
			
			// 入力に応じて挙動を変える
			if(key.UnicodeChar == L'q') {	// 'q'だったら終了
				break;
			}
			else if(key.UnicodeChar == L'\r') {	// リターンだったら改行も表示する
				buf[0] = L'\n';
				SystemTable->ConOut->OutputString(SystemTable->ConOut, (CHAR16*)buf);
			}
		}
	}

	return EFI_SUCCESS;
}

*SystemTableが超重要。アプリ側に渡されるもので、これを通してEFIのファンクションを呼び出す。オブジェクト指向っぽい感じがするが、C++のVtableよりもMSのCOMっぽい。

4.コンパイル

cl /GS- EfiHello1.cpp /link /out:bootx64.efi /subsystem:efi_application /entry:EfiMain

/GS- はバッファのセキュリティチェックを無効にする設定。セキュリティチェックに別途ライブラリ関数を呼び出すらしくUEFIアプリ作成時は使えないので無効にする。
/subsystem:efi_applicationが重要。
/entry:EfiMainはエントリポイントを指定する。CRT0なんかのスタートアップルーチンは無いのでソースに合わせれば何でも良い。

5.実行してみる。
5-1.出来上がったbootx64.efiをUSBメモリの/EFI/BOOT/にコピー。次の場所にファイルが存在するように。
(USB:)/EFI/BOOT/bootx64.efi

5-2.UEFIブートができるPCにUSBメモリを差し込んで起動。起動時オプションでUSBメモリから起動してみる。

QEMU+OVMFで動かしてみた。もちろん実機でも動く。
uefihello.png

お決まりのHello World!を表示したらあとはキー入力をエコーバックする。飽きたら電源を切る。一応qでアプリ終了。

本当にCだけ(本当はC++)で書いたプログラムがOS起動前のPCで動作する、というのは結構感動的。DOSをはじめMBRブートが使えなくなるのは残念だけど、UEFIは非常にすっきりしていて潔い。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中