ひまわり

はやく人間になりたい

EDK2は使えるのか? (→ コードの修正は必要だが手元では動いた)

BitVisor Advent Calendar 2020 5日目の記事です。

この記事では、BitVisorの複数あるloaderの一つである、uefi-loaderが用いているEDKをEDKⅡに差し替えて動くところまでがんばるという内容です。

BitVisorとEDK

BitVisorのuefi-loaderは、BitVisor自身をロードするもので、ファームウェアから処理が移る最初の部分。この部分がEDKを使って開発されている。

処理の流れ等は、過去のBitVisor Advent Calendarに記事を載せてくださる方がいるのでそちらを参照ください。そもそもEFI/UEFIとは何みたいな話も色々記事があるのでそちらを。

EDK, EDKⅡの違い

EDKのライセンスからもわかるように、EFIを提唱し始めたIntelが提供を始めたものですが、もう既にサポートは終わっており、後継のEDKⅡを実装しているtianocoreも、EDKⅡを使うようにドキュメントにある。

他にも、Intelが出している後継という意味では Intel UDK等もあるらしいが、今使われているものがEDKなので、今回はEDKⅡを使ってみる.

EDKとEDKⅡの差分は、以下のtianocoreのドキュメント曰く、

...
1. EDK II has richer libraries.
1. EDK II uses Platform Configuration Database (PCD) for parameterization and binary patch support, and supports.
1. EDK II provides compatibility with EDK-style source code using EdkCompatiblityPkg (ECP).
1. EDK II is also designed to work with Doxygen...
1. EDK II supports later versions of the UEFI and PI Specifications, ...
...

from Differences between EDK and EDK II · tianocore/tianocore.github.io Wiki · GitHub

要するに、ライブラリが充実していて、PCDを使ったおかげで便利になって(PCDに詳しくないのでわからない)、最新版の仕様に準拠して、マニュアルもしっかりしてる。けれども、EDKとの互換を保つパッケージもあるよ。とのこと

この他にも、多くのプラットフォームへの対応も進めていて、対応しているのは virtual, intel processor, arm processor, etc。
詳細は以下の表を参照ください。 EDK II Platforms · tianocore/tianocore.github.io Wiki · GitHub

動機

これらを踏まえると、EDKⅡを使うことにより、上記の利点を享受できる。
EFI/UEFIではネットワークに接続したり、様々なことができるため、開発コストが下がると嬉しい人は居そう。
互換を保つためのパッケージを用いることで移植も他に比べたら容易かもしれない。

さらに他プラットフォームの移植への足がかりができる!(?)

動機が弱いですが、楽しそうなのでということで。

移植作業

まず、BitVisorのディレクトリにEDKⅡを持ってきてEDKと差し替えてみる。 環境は Ubuntu 18.04

$ apt-get -y install mercurial make gcc mingw-w64 git
$ hg clone http://hg.code.sf.net/p/bitvisor/code bitvisor
$ cd bitvisor
$ rm -rf edk
$ git clone https://github.com/tianocore/edk2.git edk

もちろんこれだけではビルドできないので、調べると EDKⅡには、MdePkgというものがあり、

The Module Development Environment Package (MdePkg) is a special package as it is the minimum infrastructure required to build a module.

from MdePkg · tianocore/tianocore.github.io Wiki · GitHub

とのこと。最低限もmoduleを作るだけのものが入っているらしい。 確かに, header等は入っているみたい。

ここからMdePkgに内包されているものを使ったり、uefi-loaderのコードを書き換えたり.
1つずつ直して行ったが思ったより修正は少なかった.
edkのディレクトリを除くと修正の差分は以下

大きくは、3つ

Makefileのパスの変更

1つ目。include pathを適切に変更

diff -r bitvisor-orig/boot/uefi-loader/Makefile bitvisor-edk2/boot/uefi-loader/Makefile
35a36,37
>        -I$(EDK_DIR)/MdePkg/Include \
>        -I$(EDK_DIR)/MdePkg/Include/X64/ \
49a52,53
>        -I$(EDK_DIR)/MdePkg/Include \
>        -I$(EDK_DIR)/MdePkg/Include/X64/ \
各c, headerのコード修正

2つ目。 * EfiCommon.hEfiApi.hが見当たらなく似たような定義がUefi.hにあったため差し替え
* プロトコルの定義に使われていた、EFI_PROTOCOL_DEFINITIONが見当たらなかったので、修正
* uint32_t, uint64_tの定義がどうも見つからず、それぞれ大文字の定義に変更、UINT32UINT64

diff -r bitvisor-orig/boot/uefi-loader/bsdriver.c bitvisor-edk2/boot/uefi-loader/bsdriver.c
30,32c30,32
< #include <EfiCommon.h>
< #include <EfiApi.h>
< #include EFI_PROTOCOL_DEFINITION (LoadedImage)
---
> #include "Uefi.h"
> #include <Protocol/LoadedImage.h>
diff -r bitvisor-orig/boot/uefi-loader/discon.h bitvisor-edk2/boot/uefi-loader/discon.h
54c54
<               " incorrect interface ", (uint64_t)*interface);
---
>               " incorrect interface ", (UINT64)*interface);
56c56
<               (uint64_t)correct_interface);
---
>               (UINT64)correct_interface);
diff -r bitvisor-orig/boot/uefi-loader/loadvmm.c bitvisor-edk2/boot/uefi-loader/loadvmm.c
30,33c30,32
< #include <EfiCommon.h>
< #include <EfiApi.h>
< #include <Protocol/SimpleFileSystem/SimpleFileSystem.h>
< #include <Protocol/LoadedImage/LoadedImage.h>
---
> #include "Uefi.h"
> #include <Protocol/SimpleFileSystem.h>
> #include <Protocol/LoadedImage.h>
35a35
> #include "DevicePath.h"
52c52
< printhex (EFI_SYSTEM_TABLE *systab, uint64_t val, int width)
---
> printhex (EFI_SYSTEM_TABLE *systab, UINT64 val, int width)
64c64
< print (EFI_SYSTEM_TABLE *systab, CHAR16 *msg, uint64_t val)
---
> print (EFI_SYSTEM_TABLE *systab, CHAR16 *msg, UINT64 val)
125c125
<    uint32_t entry;
---
>    UINT32 entry;
178c178
<    entry = *(uint32_t *)(paddr + 0x18);
---
>    entry = *(UINT32 *)(paddr + 0x18);
DevicePath.hの追加

3つ目。 ここまで修正してきたが、loadvmm.cの中で、いくつか関数が見つからない。 それらは、EfiIsDevicePathEnd, EfiDevicePathNodeLengthと、EfiNextDevicePathNode。DevicePath関連のutil関数のよう。

これらはどうも、EDKⅡでは実装されなかったみたいで、調べると、例のEDK互換のためのパッケージのEdkCompatiblityPkg(ECP)に入っていた.

ECP · tianocore/tianocore.github.io Wiki · GitHub

そのファイルが, DevicePath.h

Only in bitvisor-edk2/boot/uefi-loader: DevicePath.h

これで無事にビルドができた。

実機実行

ビルドはできたが、本当に動くのかがはわからなかったため、手元にあったx230にインストールして動作確認を行った.

無事起動できる.(本当は、動画なりなんなり取りたかったが、"うまくedk2だよ”というところを見せる方法が思いつかないので、怪しいと思う方は手元で確かめてみてください) 少なくともx230のファームウェアでは問題ない模様。
突貫工事だったが動いたので良かった

その他

他の実機のファームウェアで試していないので、すべての環境で動くのかどうかは不明。  
ちょっと、機器、メーカーによるファームウェアの謎の挙動の話も聞くのでわからない。

macapple製のファームウェアもどうだろうか?
一応EDK or EDK2を用いられて作られており、UEFI準拠らしい。
UEFIファームウェアのセキュリティの概要 - Apple サポート

MdePkgの使い方あってるのか怪しい上に、まともにECPを使えば、もう少し修正を減らせるのかもしれない

書いてて気づいたが、bitvisorの中にもfirmwareと話す部分がたしかあり、その挙動がどうなっているのかは未調査。