/home/tnishinaga/TechMEMO

日々行ったこと、面白かったことを書き留めます。

UEFIアプリでファイルを任意のメモリアドレスにロードする方法メモ

UEFIアプリケーションでファイルを任意のメモリアドレスにロードする方法についてのメモです. ブートローダーを作ったりするのに役立つと思います.

開発環境はEDK2(28f27af6f007c3794fcc9d098ef91713160f4e5b),OSはArch Linuxを使いました.

これを行うにあたって,そもそもUEFIって何? 環境構築の方法は? 仕様書の読み方は? プロトコルとハンドラって何? どうやって動作確認するの?...等々いろいろわからないことがあり調べたのですが,今回はメモなので記述を省略します. 気力があれば後日書きます.

まだまだ勉強途中なので,間違い等ありましたらコメントでの指摘をお願いします.

ファイルを任意メモリアドレスにロードする方法

ソースコード

コードはAppPkg/Application/Helloを改変して作りました.

まずはiniファイルを示します.

## @file
#  A simple, basic, EDK II native, "hello" application.
#
#   Copyright (c) 2010, Intel Corporation. All rights reserved.<BR>
#   This program and the accompanying materials
#   are licensed and made available under the terms and conditions of the BSD License
#   which accompanies this distribution. The full text of the license may be found at
#   http://opensource.org/licenses/bsd-license.
#
#   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Hello
  FILE_GUID                      = a912f198-7f0e-4803-b908-b757b806ec83
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain

#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  Hello.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[Guids]
  gEfiFileInfoGuid

[Protocols]
  gEfiSimpleFileSystemProtocolGuid

次にCのソースを示します.

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Protocol/DevicePath.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleFileSystem.h>


#define BUF_MAX_SIZE (512*1024) // 512KB

#define KERNEL_BASE  0x200000

EFI_SYSTEM_TABLE  *gST;
EFI_BOOT_SERVICES *gBS;

EFI_STATUS
EFIAPI
UefiMain (
      IN     EFI_HANDLE        ImageHandle,
      IN     EFI_SYSTEM_TABLE  *SystemTable
      )
{
  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");


  EFI_STATUS                       Status = EFI_SUCCESS;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *SimpleFile;
  EFI_FILE_PROTOCOL                *Root;
  EFI_FILE_PROTOCOL                *File;
  CHAR16 *Path = L"hoge.txt";

  gST = SystemTable;
  gBS = gST->BootServices;

  Status = gBS->LocateProtocol (
                &gEfiSimpleFileSystemProtocolGuid,
                NULL,
                (VOID **)&SimpleFile
                );
  if (EFI_ERROR (Status)) {
    Print(L"%r on Locate EFI Simple File System Protocol.\n", Status);
    return Status;
  }

  Status = SimpleFile->OpenVolume (SimpleFile, &Root);
  if (EFI_ERROR (Status)) {
    Print(L"%r on Open volume.\n", Status);
    return Status;
  }

  Status = Root->Open (Root, &File, Path, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
  if (EFI_ERROR (Status)) {
    Print(L"%r on Open file.\n", Status);
    return Status;
  }

  UINTN                            BufferSize = 0;
  EFI_PHYSICAL_ADDRESS Buffer;
  BufferSize = BUF_MAX_SIZE;
  Buffer = (EFI_PHYSICAL_ADDRESS)KERNEL_BASE;
  // if you want to know MemoryType List, please refer UEFI Spec2.5 document pp.152-153
  Status = gBS->AllocatePages(
                 AllocateAddress,
                 EfiLoaderData,
                 BufferSize / 4 / 1024, // 1page = 4KiB 
                 &Buffer
                 );
  if (EFI_ERROR (Status)) {
    Print(L"%Could not allocate memory pool %r\n", Status);
    return Status;
  }

    
  Status = File->Read(
              File,
              &BufferSize,
              (VOID *)Buffer
              );
  if (EFI_ERROR (Status)) {
    Print(L"%r on Open file.\n", Status);
    return Status;
  }
  Print(L"BufferSize = %d\n", BufferSize);
  Print(L"Address = %p\n", Buffer);

  for (UINTN i = 0; i < BufferSize; i++) {
    Print(L"%c", ((CHAR8 *)Buffer)[i]);
  }
  Print(L"\n");


  File->Close(File);
  Root->Close(Root);

  gBS->FreePages(Buffer, BUF_MAX_SIZE);

  Print(L"All success hoge.txt\n");

  return EFI_SUCCESS;
}

処理の流れ

このコードの流れを日本語で示すと,次のようになっています.

  1. BootService(以降BS)からLocateProtocol関数を呼び出し,ファイルアクセスするためのプロトコルEFI_SIMPLE_FILE_SYSTEM_Protocol(以降SimpleFile)を取得する
  2. SimpleFileのOpenVolume関数でデバイスのルートディレクトリを開き,ルートディレクトリのファイルハンドラ(Root)を取得する
  3. RootのOpen関数で指定したファイル(今回はhoge.txt)を開き,そのファイルハンドラ(File)を取得する
  4. BSからAllocatePages関数を呼び出し,任意アドレスからメモリを確保する
  5. ファイルハンドラFileのRead関数を呼び出し,ファイルの内容を確保したメモリ領域に読みこむ
  6. ファイルハンドラを閉じる
  7. メモリを開放する

ファイルを場所を気にせず読みこむだけであれば,AllocatePool関数を使うことで手軽にメモリを確保できます. しかし,今回は「任意」のメモリアドレスにファイルを読み込むため,AllocatePagesという関数を用いています.

AllocatePages関数は第一引数にAllocateAddressを指定することで,第4引数で指摘したアドレスから第3引数で指定したページ分(1ページは4KiB)メモリを確保しようと試みます.

メモリが確保できない場合は,NOT FOUNDエラーが返ります.どの領域でどの程度メモリが取れるのかは,調べているところなのでまだわかっていません.

実行結果

メモリの確保が行われ,正常に動作した場合のログは以下のようになります.

UEFI Interactive Shell v2.1
EDK II
UEFI v2.50 (EDK II, 0x00010000)
Mapping table
      FS0: Alias(s):HD7a1:;BLK3:
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK4: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)
     BLK1: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x1)
Press ESC in 5 seconds to skip startup.nsh or any other key to continue.
Shell> fs0:
FS0:\> Hello.efi  
Hello there fellow Programmer.
Welcome to the world of EDK II.
BufferSize = 5
Address = 200000
hoge

コードで指定したKERNEL_BASEのアドレス0x200000からメモリを取得し,hoge.txtの内容(hoge)を読み込み,出力できていることが確認できます.