Top

第3章 UEFIアプリケーションのロード・実行

EFI_BOOT_SERVICES内のLoadImage()とStartImage()を使用することで、UEFIアプリケーションをロード・実行できます。ただし、そのためにはUEFIの「デバイスパス」という概念でパスを作成する必要があります。

この章ではデバイスパスを見てみる、作ってみるところから、順を追って説明し、UEFIアプリケーションをロード・実行する方法を紹介します。

なお実は、今のLinuxカーネルはUEFIアプリケーションとしてカーネルイメージを生成する機能があります。そこで、この章の最後では、Linuxカーネルの起動を行ってみます。

3.1 自分自身のパスを表示してみる

LoadImage()でUEFIアプリケーションの実行バイナリをロードするには、実行バイナリへのパスを「デバイスパス」というもので作成する必要があります。

実は、自分自身(EFI/BOOT/BOOTX64.EFI)のデバイスパスを取得する方法がありますので、この章では、既存のデバイスパスを改造して、起動したいUEFIアプリケーションのデバイスパスを作ることにします。

この節では、まず、自分自身のデバイスパスを画面へ表示し、どんなものか見てみます。

サンプルのディレクトリは"030_loaded_image_protocol_file_path"です。

3.1.1 EFI_LOADED_IMAGE_PROTOCOL

ロード済みのイメージ(UEFIアプリケーション)の情報を取得するには、EFI_LOADED_IMAGE_PROTOCOLを使用します(リスト3.1)。

リスト3.1: EFI_LOADED_IMAGE_PROTOCOLの定義(efi.hより)

struct EFI_LOADED_IMAGE_PROTOCOL {
        unsigned int Revision;
        void *ParentHandle;
        struct EFI_SYSTEM_TABLE *SystemTable;
        // Source location of the image
        void *DeviceHandle;
        struct EFI_DEVICE_PATH_PROTOCOL *FilePath;
        void *Reserved;
        // Image’s load options
        unsigned int LoadOptionsSize;
        void *LoadOptions;
        // Location where image was loaded
        void *ImageBase;
        unsigned long long ImageSize;
        enum EFI_MEMORY_TYPE ImageCodeType;
        enum EFI_MEMORY_TYPE ImageDataType;
        unsigned long long (*Unload)(void *ImageHandle);
};

EFI_LOADED_IMAGE_PROTOCOLは、これまで見てきたプロトコルとは異なり、メンバのほとんどが変数や構造体のポインタで、これらのメンバにロード済みイメージの各種情報が格納されています。リスト3.1を見てみると、"FilePath"というメンバがあります。しかも型が"EFI_DEVICE_PATH_PROTOCOL"という名前なので、ここにロード済みイメージのデバイスパスが格納されていそうです。

3.1.2 OpenProtocol()でEFI_LOADED_IMAGE_PROTOCOLを取得

そして、ロード済みイメージのEFI_LOADED_IMAGE_PROTOCOLを取得するためにEFI_BOOT_SERVICESのOpenProtocol()を使用します(リスト3.2)。

リスト3.2: OpenProtocol()の定義(efi.hより)

struct EFI_SYSTEM_TABLE {
        ・・・
        struct EFI_BOOT_SERVICES {
                ・・・
                unsigned long long (*OpenProtocol)(
                        void *Handle,
                                /* オープンするプロトコルで
                                 * 扱う対象のハンドルを指定 */
                        struct EFI_GUID *Protocol,      /* プロトコルのGUID */
                        void **Interface,
                                /* プロトコル構造体のポインタを格納 */
                        void *AgentHandle,
                                /* OpenProtocol()を実行している
                                 * UEFIアプリケーションのイメージハンドル
                                 * すなわち、自分自身のイメージハンドル */
                        void *ControllerHandle,
                                /* OpenProtocol()を実行しているのが
                                 * 「UEFIドライバー」である場合に指定する
                                 * コントローラハンドル
                                 * そうでないUEFIアプリケーションの場合、
                                 * NULLを指定 */
                        unsigned int Attributes
                                /* プロトコルインタフェースを開くモードを指定 */
                        );
                ・・・
        } *BootServices;
};

EFI_LOADED_IMAGE_PROTOCOLを開く場合、OpenProtocol()の第1引数へはEFI_LOADED_IMAGE_PROTOCOLで情報を見たい対象のイメージハンドルを指定し、第4引数へはOpenProtocol()を実行しているUEFIアプリケーションのイメージハンドルを指定します。今回の場合はどちらも、起動時から実行しているUEFIアプリケーションです。

なお通常、UEFIアプリケーションのイメージハンドルは後述するLoadImage()でUEFIアプリケーションをロードする際に取得しますが、起動時から実行しているUEFIアプリケーションについては、エントリ関数の第1引数"ImageHandle"が自分自身のイメージハンドルです。そのため、OpenProtocol()の第1引数と第4引数へはImageHandleを指定します。

また、OpenProtocol()の第6引数"Attributes"へ指定できるモード定数はリスト3.3の通りです。今回の場合、"EFI_OPEN_PROTOCOL_GET_PROTOCOL"を指定します。

リスト3.3: OpenProtocol()第4引数"Atributes"へ指定できる定数(efi.hより)

#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL    0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL  0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER   0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER     0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE     0x00000020

ここまでをまとめると、OpenProtocol()はリスト3.4の様に使用します。なお、EFI_LOADED_IMAGE_PROTOCOLのGUID"lip_guid"は、efi.h(リスト3.5)とefi.c(リスト3.6)へ定義を追加しています。

リスト3.4: OpenProtocol()の使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     unsigned long long status;
 7:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 8: 
 9:     efi_init(SystemTable);
10:     ST->ConOut->ClearScreen(ST->ConOut);
11: 
12:     status = ST->BootServices->OpenProtocol(
13:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
14:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
15:     assert(status, L"OpenProtocol");
16: 
17:     while (TRUE);
18: }

リスト3.5: lip_guidのextern(efi.hより)

 1: extern struct EFI_GUID lip_guid;

リスト3.6: lip_guidの定義(efi.cより)

 1: struct EFI_GUID lip_guid = {0x5b1b31a1, 0x9562, 0x11d2,
 2:                         {0x8e, 0x3f, 0x00, 0xa0,
 3:                          0xc9, 0x69, 0x72, 0x3b}};

3.1.3 EFI_DEVICE_PATH_TO_TEXT_PROTOCOL

EFI_DEVICE_PATH_TO_TEXT_PROTOCOLを使用することで、デバイスパスを画面に表示できるようテキストへ変換できます(リスト3.7)。

リスト3.7: EFI_DEVICE_PATH_TO_TEXT_PROTOCOLの定義

struct EFI_DEVICE_PATH_TO_TEXT_PROTOCOL {
        unsigned long long _buf;
        unsigned short *(*ConvertDevicePathToText)(
                const struct EFI_DEVICE_PATH_PROTOCOL* DeviceNode,
                unsigned char DisplayOnly,
                unsigned char AllowShortcuts);
};

前節で取得したEFI_LOADED_IMAGE_PROTOCOLのstruct EFI_DEVICE_PATH_PROTOCOL *FilePathの内容をテキストへ変換して表示してみます(リスト3.8)。

リスト3.8: ConvertDevicePathToText()の使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     unsigned long long status;
 7:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 8: 
 9:     efi_init(SystemTable);
10:     ST->ConOut->ClearScreen(ST->ConOut);
11: 
12:     status = ST->BootServices->OpenProtocol(
13:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
14:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
15:     assert(status, L"OpenProtocol");
16: 
17:     /* 追加(ここから) */
18:     puts(L"lip->FilePath: ");
19:     puts(DPTTP->ConvertDevicePathToText(lip->FilePath, FALSE, FALSE));
20:     puts(L"\r\n");
21:     /* 追加(ここまで) */
22: 
23:     while (TRUE);
24: }

実行すると図3.1の様に表示されます。

デバイスパスの表示例

図3.1: デバイスパスの表示例

図3.1を見ると、起動時から実行している自分自身のデバイスパスとしては、実行バイナリを配置した通り、"\EFI\BOOT\BOOTX64.EFI"というデバイスパスが格納されていることが分かります。

3.2 デバイスパスを作成してみる(その1)

前節では確認のためにデバイスパスをテキストへ変換する関数ConvertDevicePathToText()を紹介しました。

実はその逆に、テキストをデバイスパスへ変換する関数もあります。この節ではその関数を使用してみます。

サンプルのディレクトリは"031_create_devpath_1"です。

テキストをデバイスパスへ変換する関数は、EFI_DEVICE_PATH_FROM_TEXT_PROTOCOLのConvertTextToDevicePath()です(リスト3.9)。

リスト3.9: EFI_DEVICE_PATH_FROM_TEXT_PROTOCOLの定義(efi.hより)

struct EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL {
        unsigned long long _buf;
        struct EFI_DEVICE_PATH_PROTOCOL *(*ConvertTextToDevicePath) (
                const unsigned short *TextDevicePath);
};

EFI_DEVICE_PATH_FROM_TEXT_PROTOCOLの取得はLocateProtocol()で行います(リスト3.10)。

リスト3.10: EFI_DEVICE_PATH_FROM_TEXT_PROTOCOLの取得(efi.hより)

struct EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL *DPFTP;  /* => efi.hでextern */

void efi_init(struct EFI_SYSTEM_TABLE *SystemTable)
{
        ・・・
        struct EFI_GUID dpftp_guid = {0x5c99a21, 0xc70f, 0x4ad2,
                                      {0x8a, 0x5f, 0x35, 0xdf,
                                       0x33, 0x43, 0xf5, 0x1e}};
        ・・・
        ST->BootServices->LocateProtocol(&dpftp_guid, NULL, (void **)&DPFTP);
}

それでは、ConvertTextToDevicePath()を使用してデバイスパスを作成してみたいと思います。前節で、起動時から実行している自分自身のデバイスパスは"\EFI\BOOT\BOOTX64.EFI"というテキストでした。"\test.efi"というテキストをデバイスパスへ変換すれば、「USBフラッシュメモリ直下のtest.efiというUEFIアプリケーション」を表せそうです。ConvertTextToDevicePath()の使用例はリスト3.11の通りです。

リスト3.11: ConvertTextToDevicePath()の使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle __attribute__ ((unused)),
 5:           struct EFI_SYSTEM_TABLE *SystemTable)
 6: {
 7:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;
 8: 
 9:     efi_init(SystemTable);
10:     ST->ConOut->ClearScreen(ST->ConOut);
11: 
12:     dev_path = DPFTP->ConvertTextToDevicePath(L"\\test.efi");
13:     puts(L"dev_path: ");
14:     puts(DPTTP->ConvertDevicePathToText(dev_path, FALSE, FALSE));
15:     puts(L"\r\n");
16: 
17:     while (TRUE);
18: }

リスト3.11では、確認のために、作成したデバイスパスをConvertDevicePathToText()を使用してテキストへ戻し、画面表示しています。

実行すると図3.2の様に表示されます。

ConvertTextToDevicePath()の実行例

図3.2: ConvertTextToDevicePath()の実行例

意図した通りのデバイスパスが作成できました。

3.3 デバイスパスをロードしてみる(その1)

デバイスパスを作成できたので、LoadImage()でロードしてみます。

サンプルのディレクトリは"032_load_devpath_1"です。

LoadImage()はEFI_BOOT_SERVICES内で定義されています(リスト3.12)。

リスト3.12: LoadImage()の定義(efi.hより)

struct EFI_SYSTEM_TABLE {
        ・・・
        struct EFI_BOOT_SERVICES {
                ・・・
                //
                // Image Services
                //
                unsigned long long (*LoadImage)(
                        unsigned char BootPolicy,
                                /* ブートマネージャーからのブートか否かの指定
                                 * 今回はブートマネージャー関係ないのでFALSE */
                        void *ParentImageHandle,
                                /* 呼び出し元のイメージハンドル
                                 * 今回の場合、エントリ関数第1引数の
                                 * ImageHandleを指定 */
                        struct EFI_DEVICE_PATH_PROTOCOL *DevicePath,
                                /* ロードするイメージへのパス */
                        void *SourceBuffer,
                                /* NULLで無ければ、ロードされるイメージの
                                 * コピーを持つアドレスを示す
                                 * 今回は特に使用しないためNULLを指定 */
                        unsigned long long SourceSize,
                                /* SourceBufferのサイズを指定(単位:バイト)
                                 * SourceBufferを使用しないため0を指定 */
                        void **ImageHandle
                                /* 生成されたイメージハンドルを格納する
                                 * ポインタ */
                        );
                ・・・
        } *BootServices;
};

使用例はリスト3.13の通りです。

リスト3.13: LoadImage()の使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle __attribute__ ((unused)),
 5:           struct EFI_SYSTEM_TABLE *SystemTable)
 6: {
 7:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;      /* 追加 */
 8:     unsigned long long status;      /* 追加 */
 9:     void *image;
10: 
11:     efi_init(SystemTable);
12:     ST->ConOut->ClearScreen(ST->ConOut);
13: 
14:     dev_path = DPFTP->ConvertTextToDevicePath(L"\\test.efi");
15:     puts(L"dev_path: ");
16:     puts(DPTTP->ConvertDevicePathToText(dev_path, FALSE, FALSE));
17:     puts(L"\r\n");
18: 
19:     /* 追加(ここから) */
20:     status = ST->BootServices->LoadImage(FALSE, ImageHandle, dev_path, NULL,
21:                                          0, &image);
22:     assert(status, L"LoadImage");
23:     puts(L"LoadImage: Success!\r\n");
24:     /* 追加(ここまで) */
25: 
26:     while (TRUE);
27: }

デバイスパスの生成までは前節のままで、生成したデバイスパス"dev_path"をLoadImage()に渡しています。LoadImage()に失敗した場合はassert()でログを出して止まり、成功した場合はputs()で"LoadImage: Success!"が表示されます。

リスト3.13を実行してみます。なお、ロードされる側の"test.efi"は、適当なUEFIアプリケーションをビルドして用意しておけばよいのですが、1点だけビルド時の注意点がありますので、後述のコラムをご覧ください。ここでは、前著"フルスクラッチで作る!UEFIベアメタルプログラミング(パート1)"のサンプルプログラム"sample1_1_hello_uefi"を使用します*1。そしてビルドしたefiバイナリを"test.efi"とリネームし、起動ディスクとして使用するUSBフラッシュメモリのルート直下へ配置したこととします。

[*1] https://github.com/cupnes/c92_uefi_bare_metal_programming_samplesで公開しています

実行してみると、結果は図3.3の通りです。

LoadImage()の実行例

図3.3: LoadImage()の実行例

失敗しています。EFI_STATUSの"0x80000000 0000000E"は、最上位の0x8が"エラーである"事を示し、下位の"0xE"が"EFI_NOT_FOUND"を示します*2

[*2] 詳しくは仕様書の"Appendix D Status Codes"を見てみてください

LoadImage()がイメージを見つけられなかった理由はデバイスパスが完全では無かったからです。実は、デバイスパスにはもう少し付け加えなければならない要素があり、次節から説明します。

ロードされるUEFIアプリケーションは"-shared"オプションを付けてビルド

LoadImage()でロードされるUEFIアプリケーションの実行バイナリはリロケータブルなバイナリである必要があります。

そのため、Makefileの"fs/EFI/BOOT/BOOTX64.EFI"ターゲットで"x86_64-w64-mingw32-gcc"のオプションを指定している箇所へ"-shared"オプションを追加してください(リスト3.14)。

リスト3.14: "-shared"の追加例

all: fs/EFI/BOOT/BOOTX64.EFI

fs/EFI/BOOT/BOOTX64.EFI: main.c
        mkdir -p fs/EFI/BOOT
        x86_64-w64-mingw32-gcc -Wall -Wextra -e efi_main -nostdinc \
        -nostdlib -fno-builtin -Wl,--subsystem,10 -shared -o $@ $<
        #                                         ^^^^^^^
        #                                          追加
・・・

3.4 デバイスを指定するパス指定

これまで、"\EFI\BOOT\BOOTX64.EFI"や"\test.efi"のようにパスを指定してみました。ただし、考えてみれば、これでは「どのデバイスであるか」を指定できておらず、例えば、ノートPC本体のハードディスクとUSBフラッシュメモリのどちらを指しているのかが分かりません。そもそも、デバイスをパスの形で指定できるからこそ"デバイスパス"であるにも関わらず、"デバイス"の箇所を指定していませんでした。

それでは、デバイスの部分はどのように指定するのかを確認するために、例として、起動時から実行しているUEFIアプリケーション自身のデバイス部分のパスを見てみます。

サンプルのディレクトリは"033_loaded_image_protocol_device_handle"です。

実は、デバイス部分のパスを得るための情報もEFI_LOADED_IMAGE_PROTOCOLにあります(リスト3.15)。

リスト3.15: (再掲)EFI_LOADED_IMAGE_PROTOCOLの定義(efi.hより)

struct EFI_LOADED_IMAGE_PROTOCOL {
        unsigned int Revision;
        void *ParentHandle;
        struct EFI_SYSTEM_TABLE *SystemTable;
        // Source location of the image
        void *DeviceHandle;
        struct EFI_DEVICE_PATH_PROTOCOL *FilePath;
        void *Reserved;
        // Image’s load options
        unsigned int LoadOptionsSize;
        void *LoadOptions;
        // Location where image was loaded
        void *ImageBase;
        unsigned long long ImageSize;
        enum EFI_MEMORY_TYPE ImageCodeType;
        enum EFI_MEMORY_TYPE ImageDataType;
        unsigned long long (*Unload)(void *ImageHandle);
};

"Source location of the image"のコメントが書かれている箇所には"FilePath"の他に"DeviceHandle"があります。この"DeviceHandle"を使用してデバイス部分のパスを得ることができます。

デバイスパス(EFI_DEVICE_PATH_PROTOCOL)を得る方法は、EFI_LOADED_IMAGE_PROTOCOLを取得する時と同じく、OpenProtocol()を使用します。そして、OpenProtocol()の第1引数にこの"DeviceHandle"を指定することで、"DeviceHandle"の"EFI_DEVICE_PATH_PROTOCOL"を得ることができます(リスト3.16)。

リスト3.16: DeviceHandleのデバイスパスを取得する例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 7:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;
 8:     unsigned long long status;
 9: 
10:     efi_init(SystemTable);
11:     ST->ConOut->ClearScreen(ST->ConOut);
12: 
13:     /* ImageHandleのEFI_LOADED_IMAGE_PROTOCOL(lip)を取得 */
14:     status = ST->BootServices->OpenProtocol(
15:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
16:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
17:     assert(status, L"OpenProtocol(lip)");
18: 
19:     /* lip->DeviceHandleのEFI_DEVICE_PATH_PROTOCOL(dev_path)を取得 */
20:     status = ST->BootServices->OpenProtocol(
21:             lip->DeviceHandle, &dpp_guid, (void **)&dev_path, ImageHandle,
22:             NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
23:     assert(status, L"OpenProtocol(dpp)");
24: 
25:     /* dev_pathをテキストへ変換し表示 */
26:     puts(L"dev_path: ");
27:     puts(DPTTP->ConvertDevicePathToText(dev_path, FALSE, FALSE));
28:     puts(L"\r\n");
29: 
30:     while (TRUE);
31: }

なお、上記の実装に合わせて、efi.hとefi.cへEFI_DEVICE_PATH_PROTOCOLのGUIDを定義します(リスト3.17、リスト3.18)。

リスト3.17: EFI_DEVICE_PATH_PROTOCOLのGUIDのextern(efi.hより)

extern struct EFI_GUID dpp_guid;

リスト3.18: EFI_DEVICE_PATH_PROTOCOLのGUIDの定義(efi.cより)

struct EFI_GUID dpp_guid = {0x09576e91, 0x6d3f, 0x11d2,
                            {0x8e, 0x39, 0x00, 0xa0,
                             0xc9, 0x69, 0x72, 0x3b}};

実行すると、図3.4の様に表示されます。

DeviceHandelのデバイスパスの表示例

図3.4: DeviceHandelのデバイスパスの表示例

"PciRoot"から始まるテキストが表示されており、「デバイス」の「パス」っぽいですね。本書で扱う範囲では立ち入らずに済むためあまり説明しませんが、UEFIの概念ではこの様に、デバイスをパスの形で指定して扱います。ストレージデバイスだけでなく、PCに接続されるマウス等のデバイスもこの様にパスの形で指定します。

3.5 デバイスパスを作成してみる(その2)

ここまでで、"PciRoot"から始まる正に"デバイス"を指定しているパスと、"\test.efi"といったよく見るファイルのパスの2つのパスを確認しました。実は、この2つを連結することでフルパスとなります。

サンプルのディレクトリは"034_create_devpath_2"です。

パス連結等のパスを操作する関数群を持つのがEFI_DEVICE_PATH_UTILITIES_PROTOCOLです。ここでは、AppendDeviceNode()を使用します(リスト3.19)。

リスト3.19: EFI_DEVICE_PATH_UTILITIES_PROTOCOL.AppendDeviceNode()の定義(efi.hより)

struct EFI_DEVICE_PATH_UTILITIES_PROTOCOL {
        unsigned long long _buf[3];
        /* デバイスパスとノードを連結し、連結結果のパスを返す */
        struct EFI_DEVICE_PATH_PROTOCOL *(*AppendDeviceNode)(
                const struct EFI_DEVICE_PATH_PROTOCOL *DevicePath,
                        /* 連結するデバイスパスを指定 */
                const struct EFI_DEVICE_PATH_PROTOCOL *DeviceNode
                        /* 連結するデバイスノードを指定 */
                );
};

"デバイスノード"は、デバイスパスの部分で、"\"で区切られた1要素のことです。例えば、"\EFI\BOOT\BOOTX64.EFI"の場合、"EFI"や"BOOT"がデバイスノードです。そのため、AppendDeviceNode()は、デバイスパスの末尾に1つのノードを追加する関数、となります*3

[*3] EFI_DEVICE_PATH_UTILITIES_PROTOCOLの中にはデバイスパスにデバイスパスを連結する関数もあるのですが、使用しないため省略します。

今回の場合、ファイルパスに相当する部分は"test.efi"という単一のデバイスノードで済むため、AppendDeviceNode()を使用します。それに併せて、テキストからデバイスパスを生成する関数群を持つEFI_DEVICE_PATH_UTILITIES_PROTOCOLにはConvertTextToDeviceNode()という関数もあるので、ここではこちらの関数を使用します(リスト3.20)。

リスト3.20: ConvertTextToDeviceNode()の定義(efi.hより)

struct EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL {
        /* 指定されたテキストをデバイスノードへ変換、変換後のデバイスノードを返す */
        struct EFI_DEVICE_PATH_PROTOCOL *(*ConvertTextToDeviceNode) (
                const unsigned short *TextDeviceNode
                        /* デバイスノードへ変換するテキストを指定 */
                );
        struct EFI_DEVICE_PATH_PROTOCOL *(*ConvertTextToDevicePath) (
                const unsigned short *TextDevicePath);
};

以上を踏まえ、デバイスパスとデバイスノードを連結する例はリスト3.21の通りです。

リスト3.21: AppendDeviceNode()の使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 7:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;
 8:     struct EFI_DEVICE_PATH_PROTOCOL *dev_node;              /* 追加 */
 9:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path_merged;       /* 追加 */
10:     unsigned long long status;
11: 
12:     efi_init(SystemTable);
13:     ST->ConOut->ClearScreen(ST->ConOut);
14: 
15:     /* ImageHandleのEFI_LOADED_IMAGE_PROTOCOL(lip)を取得 */
16:     status = ST->BootServices->OpenProtocol(
17:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
18:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
19:     assert(status, L"OpenProtocol(lip)");
20: 
21:     /* lip->DeviceHandleのEFI_DEVICE_PATH_PROTOCOL(dev_path)を取得 */
22:     status = ST->BootServices->OpenProtocol(
23:             lip->DeviceHandle, &dpp_guid, (void **)&dev_path, ImageHandle,
24:             NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
25:     assert(status, L"OpenProtocol(dpp)");
26: 
27:     /* 追加・変更(ここから) */
28:     /* "test.efi"のデバイスノードを作成 */
29:     dev_node = DPFTP->ConvertTextToDeviceNode(L"test.efi");
30: 
31:     /* dev_pathとdev_nodeを連結 */
32:     dev_path_merged = DPUP->AppendDeviceNode(dev_path, dev_node);
33: 
34:     /* dev_path_mergedをテキストへ変換し表示 */
35:     puts(L"dev_path_merged: ");
36:     puts(DPTTP->ConvertDevicePathToText(dev_path_merged, FALSE, FALSE));
37:     puts(L"\r\n");
38:     /* 追加・変更(ここまで) */
39: 
40:     while (TRUE);
41: }

実行すると図3.5の様に表示されます。

AppendDeviceNode()の実行例

図3.5: AppendDeviceNode()の実行例

デバイスパスとデバイスノードを表すEFI_DEVICE_PATH_PROTOCOLについて

デバイスパスとデバイスノードは型は分かれておらず、共にEFI_DEVICE_PATH_PROTOCOLです。

どういうことかというと、EFI_DEVICE_PATH_PROTOCOLがデバイスノードで、EFI_DEVICE_PATH_PROTOCOLが連結することでデバイスパスとなります。

それでは、EFI_DEVICE_PATH_PROTOCOLはどういう定義になっているかというと、リスト3.22の様になっています。

リスト3.22: EFI_DEVICE_PATH_PROTOCOLの定義(efi.hより)

struct EFI_DEVICE_PATH_PROTOCOL {
        unsigned char Type;
        unsigned char SubType;
        unsigned char Length[2];
};

リスト3.22を見ると、リンクリストを構成するようなメンバはありません。EFI_DEVICE_PATH_PROTOCOLはメモリ上に連続に並ぶことでパスを構成します。

また、リスト3.22を見ると、ファイルパスで必要となる"ファイル名"といった要素を格納するようなメンバがありません。実は、EFI_DEVICE_PATH_PROTOCOLは各種デバイスノードのヘッダ部分のみで、ボディに当たる部分はEFI_DEVICE_PATH_PROTOCOLに続くメモリ領域へ配置します。そのため、デバイスノードのタイプを"Type"・"SubType"メンバで指定し、ヘッダ・ボディ含めたサイズを"Length"で指定します。

デバイスパス内のノード数を示す要素はどこにもありません。ノード終端を示すデバイスノードを配置することでデバイスノードの終わりを示します。

UEFIでは、デバイスパスやノードの構造やメモリ上の配置は意識せずに使用できるよう、EFI_DEVICE_PATH_FROM_TEXT_PROTOCOLやEFI_DEVICE_PATH_UTILITIES_PROTOCOLといったプロトコルを用意しています。AppendDeviceNode()でデバイスパスへデバイスノードを追加する際も、ノード終端の要素は自動で配置してくれます。そのため、UEFIのファームウェアを仕様書通りに叩く上では特に意識する必要はありません。

3.6 デバイスパスをロードしてみる(その2)

それでは、再びデバイスパスのロードを試してみます。

サンプルのディレクトリは"035_load_devpath_2"です。

前節のサンプル(リスト3.21)へリスト3.13で紹介したLoadImage()の処理を追加するだけです(リスト3.23)。

リスト3.23: フルパスのデバイスパスをロードする例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 7:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;
 8:     struct EFI_DEVICE_PATH_PROTOCOL *dev_node;
 9:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path_merged;
10:     unsigned long long status;
11:     void *image;    /* 追加 */
12: 
13:     efi_init(SystemTable);
14:     ST->ConOut->ClearScreen(ST->ConOut);
15: 
16:     /* ImageHandleのEFI_LOADED_IMAGE_PROTOCOL(lip)を取得 */
17:     status = ST->BootServices->OpenProtocol(
18:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
19:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
20:     assert(status, L"OpenProtocol(lip)");
21: 
22:     /* lip->DeviceHandleのEFI_DEVICE_PATH_PROTOCOL(dev_path)を取得 */
23:     status = ST->BootServices->OpenProtocol(
24:             lip->DeviceHandle, &dpp_guid, (void **)&dev_path, ImageHandle,
25:             NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
26:     assert(status, L"OpenProtocol(dpp)");
27: 
28:     /* "test.efi"のデバイスノードを作成 */
29:     dev_node = DPFTP->ConvertTextToDeviceNode(L"test.efi");
30: 
31:     /* dev_pathとdev_nodeを連結 */
32:     dev_path_merged = DPUP->AppendDeviceNode(dev_path, dev_node);
33: 
34:     /* dev_path_mergedをテキストへ変換し表示 */
35:     puts(L"dev_path_merged: ");
36:     puts(DPTTP->ConvertDevicePathToText(dev_path_merged, FALSE, FALSE));
37:     puts(L"\r\n");
38: 
39:     /* 追加(ここから) */
40:     /* dev_path_mergedをロード */
41:     status = ST->BootServices->LoadImage(FALSE, ImageHandle,
42:                                          dev_path_merged, NULL, 0, &image);
43:     assert(status, L"LoadImage");
44:     puts(L"LoadImage: Success!\r\n");
45:     /* 追加(ここまで) */
46: 
47:     while (TRUE);
48: }

実行してみると、今度はロードに成功しました(図3.6)。

フルパスのデバイスパスをロードする実行例

図3.6: フルパスのデバイスパスをロードする実行例

3.7 ロードしたイメージを実行してみる

ロードが成功しましたので、いよいよ実行してみます。

サンプルのディレクトリは"036_start_devpath"です。

ロードしたイメージを実行するにはEFI_BOOT_SERVICESのStartImage()を使用します(リスト3.24)。

リスト3.24: StartImage()の定義(efi.hより)

struct EFI_SYSTEM_TABLE {
        ・・・
        struct EFI_BOOT_SERVICES {
                ・・・
                //
                // Image Services
                //
                unsigned long long (*LoadImage)(
                        unsigned char BootPolicy,
                        void *ParentImageHandle,
                        struct EFI_DEVICE_PATH_PROTOCOL *DevicePath,
                        void *SourceBuffer,
                        unsigned long long SourceSize,
                        void **ImageHandle);
                unsigned long long (*StartImage)(
                        void *ImageHandle,
                                /* 実行するイメージハンドル */
                        unsigned long long *ExitDataSize,
                                /* 第3引数ExitDataのサイズを指定
                                 * ExitDataがNULLの場合、こちらもNULLを指定 */
                        unsigned short **ExitData
                                /* 呼びだされたイメージが
                                 * EFI_BOOT_SERVICES.Exit()関数で終了した場合に
                                 * 呼び出し元へ返されるデータのポインタのポインタ
                                 * 使用しないため今回はNULL */
                        );
                ・・・
        } *BootServices;
};

StartImage()の使用例はリスト3.25の通りです。

リスト3.25: StartImage()の使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 7:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;
 8:     struct EFI_DEVICE_PATH_PROTOCOL *dev_node;
 9:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path_merged;
10:     unsigned long long status;
11:     void *image;
12: 
13:     efi_init(SystemTable);
14:     ST->ConOut->ClearScreen(ST->ConOut);
15: 
16:     /* ImageHandleのEFI_LOADED_IMAGE_PROTOCOL(lip)を取得 */
17:     status = ST->BootServices->OpenProtocol(
18:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
19:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
20:     assert(status, L"OpenProtocol(lip)");
21: 
22:     /* lip->DeviceHandleのEFI_DEVICE_PATH_PROTOCOL(dev_path)を取得 */
23:     status = ST->BootServices->OpenProtocol(
24:             lip->DeviceHandle, &dpp_guid, (void **)&dev_path, ImageHandle,
25:             NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
26:     assert(status, L"OpenProtocol(dpp)");
27: 
28:     /* "test.efi"のデバイスノードを作成 */
29:     dev_node = DPFTP->ConvertTextToDeviceNode(L"test.efi");
30: 
31:     /* dev_pathとdev_nodeを連結 */
32:     dev_path_merged = DPUP->AppendDeviceNode(dev_path, dev_node);
33: 
34:     /* dev_path_mergedをテキストへ変換し表示 */
35:     puts(L"dev_path_merged: ");
36:     puts(DPTTP->ConvertDevicePathToText(dev_path_merged, FALSE, FALSE));
37:     puts(L"\r\n");
38: 
39:     /* dev_path_mergedをロード */
40:     status = ST->BootServices->LoadImage(FALSE, ImageHandle,
41:                                          dev_path_merged, NULL, 0, &image);
42:     assert(status, L"LoadImage");
43:     puts(L"LoadImage: Success!\r\n");
44: 
45:     /* 追加(ここから) */
46:     /* imageの実行を開始する */
47:     status = ST->BootServices->StartImage(image, NULL, NULL);
48:     assert(status, L"StartImage");
49:     puts(L"StartImage: Success!\r\n");
50:     /* 追加(ここまで) */
51: 
52:     while (TRUE);
53: }

実行例は図3.7の通りです。

StartImage()の実行例

図3.7: StartImage()の実行例

無事に実行できました!これで、今後はUEFIアプリケーションをバイナリ単位で分割し、呼び出す事ができます。

そう言えば、前著(パート1)のsample1_1_hello_uefiでは、CR('\r')が入っていないので、今回のように、別のUEFIアプリケーションから呼び出された場合、改行はしますが、行頭は戻ってないですね。

3.8 Linuxを起動してみる: カーネルビルド

現在のLinuxカーネルはビルド時の設定(menuconfig)でUEFIバイナリを生成するように設定できます。LinuxカーネルをUEFIバイナリとしてビルドすることで、Linuxのイメージ(bzImage)をこれまで説明したUEFIアプリケーションの実行方法で実行させることができます。

サンプルのディレクトリは"037_start_bzImage"です。

3.8.1 ビルド環境を準備

Debian、Ubuntu等のパッケージ管理システムでAPTが使用できる事を前提に説明します。

APTでは"apt-get build-dep <パッケージ名>"というコマンドで、指定したパッケージ名をビルドするために必要な環境をインストールすることができます。

カーネルイメージは"linux-image-<バージョン>-<アーキテクチャ>"というパッケージ名です。現在使用しているカーネルバージョンとアーキテクチャは"uname -r"コマンドで取得できるので、結果としては、以下のコマンドでLinuxカーネルをビルドする環境をインストールできます。

$ sudo apt-get build-dep linux-image-$(uname -r)

なお、menuconfigを使用するための"libncurses5-dev"パッケージがbuild-depでインストールされないので、別途インストールする必要があります。

$ sudo apt install libncurses5-dev

3.8.2 Linuxカーネルのソースコードを準備

ソースコードもAPTでは"apt-get source <パッケージ名>"というコマンドで取得できます。しかし、せっかくなのでここでは本家であるkernel.orgから最新の安定版をダウンロードして使用してみることにします。

https://www.kernel.org/へアクセスし、"Latest Stable Kernel:"からダウンロードしてください(図3.8)。

kernel.orgのウェブサイト

図3.8: kernel.orgのウェブサイト

ダウンロード後は、展開しておいてください。

$ tar Jxf linux-<バージョン>.tar.xz

3.8.3 ビルド設定を行う

Linuxカーネルのビルドの設定を行います。

まず、展開したLinuxカーネルのソースコードディレクトリへ移動し、x86_64向けのデフォルト設定を反映させます。

$ cd linux-<バージョン>
$ make x86_64_defconfig

そして、menuconfigで、UEFIバイナリを生成する設定(コンフィグシンボル名"CONFIG_EFI_STUB")を有効化します。

menuconfigを起動させます。

$ make menuconfig

すると、図3.9の画面になります。

リスト3.26の場所にあるCONFIG_EFI_STUBを有効化します。

リスト3.26: CONFIG_EFI_STUBの場所

Processor type and features  --->
  [*] EFI runtime service support
  [*]   EFI stub support  <== 有効化

3.8.4 ビルドする

makeコマンドでビルドします。-jオプションでビルドのスレッド数を指定できます。ここではnprocコマンドで取得したCPUコア数を-jオプションに渡しています。

$ make -j $(nproc)

ビルドには時間がかかりますので、のんびりと待ちましょう。

ビルドが完了すると、"bzImage"というイメージファイルができあがっています。

$ ls arch/x86/boot/bzImage
arch/x86/boot/bzImage

3.8.5 起動してみる

arch/x86/boot/bzImageをUSBフラッシュメモリ等のこれまで"test.efi"を配置していた場所に"bzImage.efi"という名前で配置し、リスト3.25の"test.efi"を"bzImage.efi"へ変更するだけで、Linuxカーネルを起動できます図3.10

Linux起動。。ただし失敗

図3.10: Linux起動。。ただし失敗

Linuxカーネルの起動は始まりますが、ルートファイルシステムを何も準備していないのでカーネルパニックで止まります。

補足: menuconfigでは検索が使えます

もし、前述の場所に設定項目が無い場合は、menuconfigの検索機能を使ってみてください。

menuconfig画面内で"/(スラッシュ)"キーを押下すると図3.11の画面になります。

この画面で"efi_stub"の様に検索したいキーワードを入力*4し、Enterを押下すると、コンフィギュレーションの依存関係や設定項目の場所等の情報が表示されます(図3.12)。なお、検索結果の画面ではコンフィグシンボル名の"CONFIG_"は省略されています。

図3.12では、"Location"の欄にコンフィグの場所が、"Prompt"の欄に設定項目名が記載されています。

"Location"と"Prompt"が示す場所にも設定項目が無い場合は、特にコンフィグの依存関係("Depends on")を見てみると良いです。図3.12の場合、"Depends on"は、"CONFIG_EFIが有効で、かつCONFIG_X86_USE_3DNOWが無効であること"を示しており、"[]"の中は現在の設定値です。現在の設定値が要求している依存関係と一致していない場合、その設定項目はmenuconfig画面内に現れませんので、先に依存するコンフィグを変更する必要があります。

3.9 Linuxを起動してみる: カーネル起動オプション指定

前節では、Linuxカーネルが起動時に参照するルートファイルシステム("root=")等のオプションを指定していなかったため、カーネルパニックに陥ってしまっていました。そこで、カーネル起動時のオプションを設定してみます。

サンプルのディレクトリは"038_start_bzImage_options"です。

UEFIアプリケーション実行時のオプション(引数)は、EFI_LOADED_IMAGE_PROTOCOLのunsigned int LoadOptionsSizeメンバとvoid *LoadOptionsメンバへ行います(リスト3.27)。

リスト3.27: (再掲)EFI_LOADED_IMAGE_PROTOCOLの定義(efi.hより)

struct EFI_LOADED_IMAGE_PROTOCOL {
        unsigned int Revision;
        void *ParentHandle;
        struct EFI_SYSTEM_TABLE *SystemTable;
        // Source location of the image
        void *DeviceHandle;
        struct EFI_DEVICE_PATH_PROTOCOL *FilePath;
        void *Reserved;
        // Image’s load options
        unsigned int LoadOptionsSize;
                /* LoadOptionsメンバのサイズを指定(バイト) */
        void *LoadOptions;
                /* イメージバイナリのロードオプションへのポインタを指定 */
        // Location where image was loaded
        void *ImageBase;
        unsigned long long ImageSize;
        enum EFI_MEMORY_TYPE ImageCodeType;
        enum EFI_MEMORY_TYPE ImageDataType;
        unsigned long long (*Unload)(void *ImageHandle);
};

使用例はリスト3.28の通りです。

リスト3.28: LoadOptionsSizeとLoadOptionsの使用例(main.cより)

 1: #include "efi.h"
 2: #include "common.h"
 3: 
 4: void efi_main(void *ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     struct EFI_LOADED_IMAGE_PROTOCOL *lip;
 7:     struct EFI_LOADED_IMAGE_PROTOCOL *lip_bzimage;  /* 追加 */
 8:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path;
 9:     struct EFI_DEVICE_PATH_PROTOCOL *dev_node;
10:     struct EFI_DEVICE_PATH_PROTOCOL *dev_path_merged;
11:     unsigned long long status;
12:     void *image;
13:     unsigned short options[] = L"root=/dev/sdb2 init=/bin/sh rootwait";
14:                                                             /* 追加 */
15: 
16:     efi_init(SystemTable);
17:     ST->ConOut->ClearScreen(ST->ConOut);
18: 
19:     /* ImageHandleのEFI_LOADED_IMAGE_PROTOCOL(lip)を取得 */
20:     status = ST->BootServices->OpenProtocol(
21:             ImageHandle, &lip_guid, (void **)&lip, ImageHandle, NULL,
22:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
23:     assert(status, L"OpenProtocol(lip)");
24: 
25:     /* lip->DeviceHandleのEFI_DEVICE_PATH_PROTOCOL(dev_path)を取得 */
26:     status = ST->BootServices->OpenProtocol(
27:             lip->DeviceHandle, &dpp_guid, (void **)&dev_path, ImageHandle,
28:             NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
29:     assert(status, L"OpenProtocol(dpp)");
30: 
31:     /* "bzImage.efi"のデバイスノードを作成 */
32:     dev_node = DPFTP->ConvertTextToDeviceNode(L"bzImage.efi");
33: 
34:     /* dev_pathとdev_nodeを連結 */
35:     dev_path_merged = DPUP->AppendDeviceNode(dev_path, dev_node);
36: 
37:     /* dev_path_mergedをテキストへ変換し表示 */
38:     puts(L"dev_path_merged: ");
39:     puts(DPTTP->ConvertDevicePathToText(dev_path_merged, FALSE, FALSE));
40:     puts(L"\r\n");
41: 
42:     /* dev_path_mergedをロード */
43:     status = ST->BootServices->LoadImage(FALSE, ImageHandle,
44:                                          dev_path_merged, NULL, 0, &image);
45:     assert(status, L"LoadImage");
46:     puts(L"LoadImage: Success!\r\n");
47: 
48:     /* 追加(ここから) */
49:     /* カーネル起動オプションを設定 */
50:     status = ST->BootServices->OpenProtocol(
51:             image, &lip_guid, (void **)&lip_bzimage, ImageHandle, NULL,
52:             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
53:     assert(status, L"OpenProtocol(lip_bzimage)");
54:     lip_bzimage->LoadOptions = options;
55:     lip_bzimage->LoadOptionsSize =
56:             (strlen(options) + 1) * sizeof(unsigned short);
57:     /* 追加(ここまで) */
58: 
59:     /* imageの実行を開始する */
60:     status = ST->BootServices->StartImage(image, NULL, NULL);
61:     assert(status, L"StartImage");
62:     puts(L"StartImage: Success!\r\n");
63: 
64:     while (TRUE);
65: }

リスト3.28では、カーネル起動オプションとして"root=/dev/sdb2 init=/bin/sh rootwait"を指定しています。

"root=/dev/sdb2"は、ルートファイルシステムを配置しているパーティションへのデバイスファイルの指定です。筆者が実験で使用しているPCの場合、内蔵のHDDを"sda"として認識し、接続したUSBフラッシュメモリを"sdb"として認識します。そのため、USBフラッシュメモリの第2パーティションを指定するため、"/dev/sdb2"としています。

"init=/bin/sh"は起動時にカーネルが最初に実行する実行バイナリの指定です。何も指定しない場合、"/sbin/init"が実行されますが、起動直後にシェルを立ち上げてしまおうと、"/bin/sh"を指定しています。そのため、後述しますが、USBフラッシュメモリの第2パーティションへ/bin/shを配置しておく必要があります。

"rootwait"は、Linuxカーネルがルートファイルシステムを検出するタイミングを遅らせるオプションです。USBフラッシュメモリ等の場合、デバイスが検出されるタイミングは非同期です。デバドラの検出より先にLinuxカーネルのルートファイルシステム検出を行おうとするとルートファイルシステムの検出に失敗してしまうので、ルートファイルシステムの検出を遅延させるためのオプションです。

実行例は図3.13の通りです。

起動オプションを指定した実行例

図3.13: 起動オプションを指定した実行例

晴れて、シェルを起動させることができました。

補足: ルートファイルシステムの作り方

今回の場合の様に「シェルさえ動けば良い」のであれば、ルートファイルシステムの構築には"BusyBox"が便利です。BusyBoxは"組み込みLinuxのスイスアーミーナイフ"と呼ばれるもので、一つの"busybox"という実行バイナリへシンボリックリンクを張ることで、色々なコマンドが使用できるようになるというものです。

Debian、あるいはUbuntu等のAPTを使用できる環境では、"busybox-static"というパッケージをインストールすることで、必要なライブラリが静的に埋め込まれたbusyboxバイナリを取得できます。

$ sudo apt install busybox-static
・・・
$ ls /bin/busybox
/bin/busybox

インストールすると、/bin/busyboxへbusyboxの実行バイナリが配置されます。この実行バイナリをUSBフラッシュメモリ第2パーティションへ以下の様に配置すれば良いです。("sh"は"busybox"へのシンボリックリンクです。)

busyboxバイナリの配置

USBフラッシュメモリ第2パーティション
└── bin/
    ├── busybox
    └── sh -> busybox

なお、基本的なコマンドはBusyBoxで事足りますが、「aptを使用したい」等の場合はルートファイルシステムの構築が面倒になってきます。

Debianから公開されているdebootstrapというコマンドを使用すると、apt等が使用できる最低限のDebianのルートファイルシステムをコマンド一つで構築できます。

$ sudo debootstrap <debianのバージョン> <作成先のディレクトリ>

例えば、USBフラッシュメモリの第2パーティションを/mnt/storageへマウントしている時、sid(unstable)のDebianルートファイルシステムを/mnt/storageへ構築するコマンドは以下の通りです。

$ sudo debootstrap sid /mnt/storage

Top