UEFI shellの引数をBitVisor側へ渡してみようの巻

過去にMultiboot specificationに基づいてブートする際にBitVisorへオプションを渡せるようにしたという話がありました．

- @deep_tkkn, [BitVisor でブートオプションっぽく文字列を取得してみる](https://qiita.com/deep_tkkn/items/8c366819e858fe78a02e), BitVisor Advent Calendar 2015.

起動時にオプションが渡せると何かと便利なことがあります．
今回はUEFI shellからBitVisorを起動する際にオプション（と言う名の文字列)を渡せるようにしてみたいと思います．

## おさらい: UEFI shellからの起動
本題の前に，USBメモリブートしてUEFI shellからBitVisorを起動する方法を簡単に書きます．

まずUSBメモリをFAT32でフォーマットしたのち， `/BOOT/EFI` ディレクトリを作成します．
その中に`bitvisor.elf`と`loadvmm.elf` (BitVisorのソースの`boot/uefi-loader`の中にあります）を置きます．
また，UEFI Shell (自分の場合は[このShell.efi](https://github.com/tianocore/edk2/tree/master/ShellBinPkg/UefiShell/X64)を`/BOOT/EFI/BOOTX64.efi`とリネームして置きます．

あとはUSBブートをするとuefi shellが自動で立ち上がるはずです．UEFI shell上で`fs0:\BOOT\EFI\loadvmm.elf`みたいにすればBitVisorが起動できます．
(ちなみに，`loadvmm.elf`を`BOOTX64.efi`とリネームして置けばUSBブートした際自動でBitVisorが起動します．)

今回はここで起動する際に，`fs0:\BOOT\EFI\loadvmm.elf <option>`みたいな感じでBitVisor側へオプションを渡すことを考えます．

## UEFI shellのパラメータの取得方法
BitVisorのuefi loaderは[`boot/uefi-loader/loadvmm.c`](https://bitbucket.org/bitvisor/bitvisor/src/default/boot/uefi-loader/loadvmm.c)にあります．
`efi_main`がUEFIアプリケーションのエントリポイントです．

[LOADED IMAGE PROTOCOL](http://wiki.phoenix.com/wiki/index.php/EFI_LOADED_IMAGE_PROTOCOL)を利用すると，ロードしたイメージの情報が取得できます．
このとき，UEFI shellからアプリケーションを起動した場合，`LOAD_OPTIONS`に引数の情報が入っているようです．
BitVisorのローダーの中でにすでにLOADED IMAGE PROTOCOLは使われているので，`loaded_image->LoadOptions`で引数にアクセスできます．

ちなみに，UEFI shell specificationの方でも何かオプションに関する項目があるみたいですが，試してません．

## BitVisor側へのパラメータ送信方法
`loadvmm.efi`からBitVisor側のエントリポイントに飛んだ際，どこに行くかというと，core/entry.sの`entry`及び`ufi64_enry`を経由してcore/uefi.cの`uefi_init()`にいきます． 
このときページテーブルを切り替えるので，BitVisor側からUEFIローダー側のメモリ領域を単純に参照することはできません．そこでちょっと工夫がいるわけですが，実のところBitVisorからUEFI関数を呼び出すことがあるので，既にページテーブル切り替えやUEFI側のメモリ領域を参照する機能が存在します．
また昨年追加された[uefi-loader-login](https://qiita.com/hdk_2/items/5f2556e6fede840607bc)において，ローダー側から`struct bitvisor_boot`というデータ構造をBitVisor側に渡して，BitVisor側はそれを参照する仕組みが実現されているので，これを利用すれば簡単にオプションが渡せます．

## 要するに

```diff
diff -r dd0a7173203a boot/uefi-loader/loadvmm.c
--- a/boot/uefi-loader/loadvmm.c	Sat Dec 01 10:28:52 2018 +0000
+++ b/boot/uefi-loader/loadvmm.c	Sun Dec 02 08:32:19 2018 +0000
@@ -39,6 +39,7 @@
 
 static EFI_GUID LoadedImageProtocol = EFI_LOADED_IMAGE_PROTOCOL_GUID;
 static EFI_GUID FileSystemProtocol = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
+CHAR8 option[256];
 
 static void
 printhex (EFI_SYSTEM_TABLE *systab, uint64_t val, int width)
@@ -119,6 +120,9 @@
 	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fileio;
 	EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
 	EFI_PHYSICAL_ADDRESS paddr = 0x40000000;
+	UINT32 optionsize;
+	CHAR16* loadoption;
+	int i, j;
 
 	status = systab->BootServices->
 		HandleProtocol (image, &LoadedImageProtocol, &tmp);
@@ -163,11 +167,31 @@
 	entry = *(uint32_t *)(paddr + 0x18);
 	entry_func = (entry_func_t *)(paddr + (entry & 0xFFFF));
 
+	optionsize = loaded_image->LoadOptionsSize;
+	loadoption = (CHAR16*)loaded_image->LoadOptions;
+	// CHAR16 => CHAR8
+	optionsize /= 2;
+	if(optionsize > 255){
+		optionsize = 255;
+	}
+	// skip program name
+	for(i = 0; i < optionsize && (CHAR8)(*loadoption) != ' ' ; i++, loadoption++);
+	if(i < optionsize && (CHAR8)(*loadoption) == ' '){
+		i++, loadoption++;
+	}
+	for(j = 0; i < optionsize; i++, j++){
+		option[j] = (CHAR8)(*loadoption++);
+	}
+	option[j] = '\0';
+
+
 	struct bitvisor_boot boot_ext = {
 		UEFI_BITVISOR_BOOT_UUID,
 		paddr,
 		readsize,
-		file2
+		file2,
+		&option[0],
+		j-1
 	};
 	void *boot_exts[] = {
 		&boot_ext,
diff -r dd0a7173203a core/uefi.c
--- a/core/uefi.c	Sat Dec 01 10:28:52 2018 +0000
+++ b/core/uefi.c	Sun Dec 02 08:32:19 2018 +0000
@@ -77,6 +77,8 @@
 ulong SECTION_ENTRY_DATA uefi_protocols_per_handle;
 ulong SECTION_ENTRY_DATA uefi_uninstall_protocol_interface;
 ulong SECTION_ENTRY_DATA uefi_create_event;
+char SECTION_ENTRY_DATA uefi_boot_option[256];
+ulong SECTION_ENTRY_DATA uefi_boot_option_size;
 bool uefi_booted;
 
 static void SECTION_ENTRY_TEXT
@@ -261,6 +263,10 @@
 	uefi_entry_pcpy (uefi_entry_virttophys (&uefi_read),
 			 &file->Read, sizeof uefi_read);
 
+	uefi_entry_pcpy (uefi_entry_virttophys (&uefi_boot_option[0]),
+				bitvisor_opt.option, sizeof uefi_boot_option);
+	uefi_boot_option_size = bitvisor_opt.optionsize;
+
 	uefi_init_get_vmmsize (&vmmsize, &align);
 	vmmsize = (vmmsize + PAGESIZE - 1) & ~PAGESIZE_MASK;
 	alloc_addr64 = 0xFFFFFFFF;
diff -r dd0a7173203a core/uefi.h
--- a/core/uefi.h	Sat Dec 01 10:28:52 2018 +0000
+++ b/core/uefi.h	Sun Dec 02 08:32:19 2018 +0000
@@ -52,6 +52,8 @@
 extern ulong uefi_protocols_per_handle;
 extern ulong uefi_uninstall_protocol_interface;
 extern ulong uefi_create_event;
+extern char uefi_boot_option[256];
+extern ulong uefi_boot_option_size;
 extern bool uefi_booted;
 
 #endif
diff -r dd0a7173203a include/share/uefi_boot.h
--- a/include/share/uefi_boot.h	Sat Dec 01 10:28:52 2018 +0000
+++ b/include/share/uefi_boot.h	Sun Dec 02 08:32:19 2018 +0000
@@ -61,6 +61,8 @@
 	unsigned long long int loadaddr;
 	unsigned long long int loadsize;
 	void *file;
+	void *option;
+	unsigned int optionsize;
 };
 
 #define INITIAL_BOOT_EXT {{UEFI_BITVISOR_BOOT_UUID}, 0, 0, 0}
```

ローダーの中で`loaded_image->LoadOptions`を参照してshellに入力された文字列を取得しています．ここで少し注意が必要なのは，UEFIで扱う文字列はUTF16(正確にはUCS-2)であるという点です．したがって1文字2バイトですが，扱いにくいのでここでは無理やり1バイトにキャストしてしまっています．
`uefi_init()`の中で`uefi_entry_pcpy`を使って，ローダーのメモリ領域にあるオプション文字列をBitVisor側のメモリ領域にコピーします．あとはBitVisor側から必要に応じてこの文字列を参照するだけです．

### 実行例

```diff
diff -r dd0a7173203a core/main.c
--- a/core/main.c       Sat Dec 01 10:28:52 2018 +0000
+++ b/core/main.c       Sun Dec 02 08:37:06 2018 +0000
@@ -61,6 +61,7 @@
 #include "vramwrite.h"
 #include "vt.h"
 #include "vt_init.h"
+#include "uefi.h"
 #include <share/uefi_boot.h>

 static struct multiboot_info mi;
@@ -86,6 +87,7 @@
 print_startvm_msg (void)
 {
        printf ("Starting a virtual machine.\n");
+       printf ("uefi: option = %s, (len=%ld)\n", uefi_boot_option, uefi_boot_option_size);
 }

 static void
```

![uefi-boot-option.png](https://qiita-image-store.s3.amazonaws.com/0/153099/349c9db8-f5b2-ef34-6223-49d99e34d8d4.png)