8. HandleProtocol API 함수 & ImageHandle 프로토콜을 통한 정보

HandleProtocol 함수에 대한 기본 사용 및 ImageHandle 프로토콜로부터의 정보 얻기

지난 장에서 발견한 ImageHandle과 관련된 프로토콜에 대해서 알아보자.

이전과 동일하게 grep명령어를 통해서 EDKII 소스 코드에서 해당 GUID가 어디서 사용되는지 확인해보겠다.

$ grep -i bc62157e -r ./ --exclude-dir=Build
./MdePkg/Include/Protocol/LoadedImage.h:    0xbc62157e, 0x3e33, 0x4fec, {0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf } \
./MdePkg/MdePkg.dec:  gEfiLoadedImageDevicePathProtocolGuid = { 0xbc62157e, 0x3e33, 0x4fec, {0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf }}
$ grep -i 5B1B31A1 -r ./ --exclude-dir=Build
./MdePkg/Include/Protocol/LoadedImage.h:    0x5B1B31A1, 0x9562, 0x11d2, {0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B } \
./MdePkg/MdePkg.dec:  gEfiLoadedImageProtocolGuid    = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}

두가지 중 LoadedImageProtocol은 아래의 링크에서 찾아볼 수 있다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/LoadedImage.h)

///
/// Can be used on any image handle to obtain information about the loaded image.
///
typedef struct {
  UINT32            Revision;       ///< Defines the revision of the EFI_LOADED_IMAGE_PROTOCOL structure.
                                    ///< All future revisions will be backward compatible to the current revision.
  EFI_HANDLE        ParentHandle;   ///< Parent image's image handle. NULL if the image is loaded directly from
                                    ///< the firmware's boot manager.
  EFI_SYSTEM_TABLE  *SystemTable;   ///< the image's EFI system table pointer.

  //
  // Source location of image
  //
  EFI_HANDLE        DeviceHandle;   ///< The device handle that the EFI Image was loaded from.
  EFI_DEVICE_PATH_PROTOCOL  *FilePath;  ///< A pointer to the file path portion specific to DeviceHandle
                                        ///< that the EFI Image was loaded from.
  VOID              *Reserved;      ///< Reserved. DO NOT USE.

  //
  // Images load options
  //
  UINT32            LoadOptionsSize;///< The size in bytes of LoadOptions.
  VOID              *LoadOptions;   ///< A pointer to the image's binary load options.

  //
  // Location of where image was loaded
  //
  VOID              *ImageBase;     ///< The base address at which the image was loaded.
  UINT64            ImageSize;      ///< The size in bytes of the loaded image.
  EFI_MEMORY_TYPE   ImageCodeType;  ///< The memory type that the code sections were loaded as.
  EFI_MEMORY_TYPE   ImageDataType;  ///< The memory type that the data sections were loaded as.
  EFI_IMAGE_UNLOAD  Unload;
} EFI_LOADED_IMAGE_PROTOCOL;

UEFI 스펙(specification)에 따르면 아래와 같이 설명이 되어 있는 것을 볼 수 있다.

로드된 각 이미지에는 EFI_LOADED_IMAGE_PROTOCOL을 지원하는 이미지 핸들이 있습니다. 이미지가 시작되면 자체 이미지 핸들이 전달됩니다. 이미지는 로드 옵션과 같은 EFI_LOADED_IMAGE_PROTOCOL 구조에 저장된 관련 이미지 데이터를 얻기 위해 핸들을 사용할 수 있습니다.

나머지 하나의 GUID는 EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL을 나타낸다. - 설치된 경우 로드된 Loaded Image Device Path Protocol은 PE/COFF 이미지가 EFI 부팅 서비스 LoadImage()를 통해 로드될 때 사용된 장치 경로를 지정한다. Loaded Image Device Path ProtocolDevice Path Protocol과 동일한 프로토콜 인터페이스를 사용한다. 두가지의 유일한 차이점은 GUID 값이 다르다는 것이다.

Loaded Image Device Path Protocol은 EFI 부팅 서비스 LoadImage()를 통해 로드된 PE/COFF 이미지의 이미지 핸들에 설치해야만하며 이미지 핸들이 설치되기 전에 EFI 부팅 서비스 LoadImage()에 대한 DevicePath의 매개변수로 지정된 장치 경로의 복사본이 만들어진다.

NULL DevicePath 파라미터를 사용하여 메모리의 버퍼에 대해 LoadImage()를 호출하는 것이 가능하며 이와 같은 경우에는 Loaded Image Device Path Protocol은 NULL 인터페이스 포인터와 함께 설치가 된다.

따라서 해당 프로토콜에 대한 정의를 찾기 위해서 EFI_DEVICE_PATH_PROTOCOL을 검색해야만 한다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/DevicePath.h)

/**
  This protocol can be used on any device handle to obtain generic path/location
  information concerning the physical device or logical device. If the handle does
  not logically map to a physical device, the handle may not necessarily support
  the device path protocol. The device path describes the location of the device
  the handle is for. The size of the Device Path can be determined from the structures
  that make up the Device Path.
**/
typedef struct {
  UINT8 Type;       ///< 0x01 Hardware Device Path.
                    ///< 0x02 ACPI Device Path.
                    ///< 0x03 Messaging Device Path.
                    ///< 0x04 Media Device Path.
                    ///< 0x05 BIOS Boot Specification Device Path.
                    ///< 0x7F End of Hardware Device Path.

  UINT8 SubType;    ///< Varies by Type
                    ///< 0xFF End Entire Device Path, or
                    ///< 0x01 End This Instance of a Device Path and start a new
                    ///< Device Path.

  UINT8 Length[2];  ///< Specific Device Path data. Type and Sub-Type define
                    ///< type of data. Size of data is included in Length.

} EFI_DEVICE_PATH_PROTOCOL;

해당 구조는 Length 바이트 뒤에 오는 실제 경로 데이터의헤더와 같다고 생각하면 된다.

이제 모든 프로토콜의 구조에 대해서 이해를 했으니 프로토콜 구조를 직접 사용하는 방법을 알아야 할 때다. 지난 장에서 사용한 방법은 기초적인 원리를 이해하기 위해서 사용한 것이다. 실제로 UEFI에는 프로토콜 구조를 가져오는 여러가지 기능이 존재하기 때문에 이를 이용하겠다.

EFI_BOOT_SERVICES.HandleProtocol() - 핸들이 프로토콜을 지원하는지 확인하기 위해 핸들로 질의한다. UEFI 문서:

Prototype:

typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL) (
 IN EFI_HANDLE Handle,
 IN EFI_GUID *Protocol,
 OUT VOID **Interface
);

Parameters:
Handle       The handle being queried. If Handle isNULL, then EFI_INVALID_PARAMETER is returned.
Protocol     The published unique identifier of the protocol. It is the caller’s responsibility to pass in a valid GUID. 
Interface    Supplies the address where a pointer to the corresponding Protocol Interface is returned.
             NULL will be returned in *Interface if a structure is not associated with Protocol.

Description:
The HandleProtocol() function queries Handle to determine if it supports Protocol. If it does, then on return Interface points to a pointer to the corresponding Protocol Interface. Interface can then be passed to any protocol service to identify the context of the request.

Status Codes Returned:
EFI_SUCCESS           The interface information for the specified protocol was returned.
EFI_UNSUPPORTED       The device does not support the specified protocol.
EFI_INVALID_PARAMETER Handle is NULL..
EFI_INVALID_PARAMETER Protocol is NULL.
EFI_INVALID_PARAMETER Interface is NULL.

해당 정보들을 가지고 새로운 ImageInfo 앱의 만들어보겠다.

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include <Protocol/LoadedImage.h>
#include <Library/DevicePathLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS Status;
  EFI_LOADED_IMAGE_PROTOCOL* LoadedImage;

  Status = gBS->HandleProtocol(
    ImageHandle,
    &gEfiLoadedImageProtocolGuid,
    (VOID **) &LoadedImage
  );

  if (Status == EFI_SUCCESS) {
    EFI_DEVICE_PATH_PROTOCOL* DevicePath;

    Status = gBS->HandleProtocol(
      ImageHandle,
      &gEfiLoadedImageDevicePathProtocolGuid,
      (VOID**) &DevicePath
    );

    if (Status == EFI_SUCCESS) {
      Print(L"Image device: %s\n", ConvertDevicePathToText(DevicePath, FALSE, TRUE));
      Print(L"Image file: %s\n",  ConvertDevicePathToText(LoadedImage->FilePath, FALSE, TRUE));
      Print(L"Image Base: %X\n", LoadedImage->ImageBase);
      Print(L"Image Size: %X\n", LoadedImage->ImageSize);
    } else {
      Print(L"Can't get EFI_LOADED_IMAGE_PROTOCOL, Status=%r\n", Status);
    }
  } else {
    Print(L"Can't get EFI_DEVICE_PATH_PROTOCOL, Status=%r\n", Status);
  }
  return EFI_SUCCESS;
}

몇가지 새로운 지식:

/**
  Converts a device path to its text representation.
  @param DevicePath      A Pointer to the device to be converted.
  @param DisplayOnly     If DisplayOnly is TRUE, then the shorter text representation
                         of the display node is used, where applicable. If DisplayOnly
                         is FALSE, then the longer text representation of the display node
                         is used.
  @param AllowShortcuts  If AllowShortcuts is TRUE, then the shortcut forms of text
                         representation for a device node can be used, where applicable.
  @return A pointer to the allocated text representation of the device path or
          NULL if DeviceNode is NULL or there was insufficient memory.
**/
CHAR16 *
EFIAPI
ConvertDevicePathToText (
  IN CONST EFI_DEVICE_PATH_PROTOCOL   *DevicePath,
  IN BOOLEAN                          DisplayOnly,
  IN BOOLEAN                          AllowShortcuts
  );

해당 코드의 빌드를 진행하면 아래와 같은 오류가 뜰 것이다.

/root/workspace/edk2/UefiLessonsPkg/ImageInfo/ImageInfo.c:23: undefined reference to `gEfiLoadedImageProtocolGuid'
/usr/bin/ld: /tmp/ImageInfo.dll.vzPH6S.ltrans0.ltrans.o:/root/workspace/edk2/UefiLessonsPkg/ImageInfo/ImageInfo.c:32: undefined reference to `gEfiLoadedImageDevicePathProtocolGuid

에러를 해결하려면 .inf 파일에 프로토콜 섹션을 추가해야한다.

[Protocols]
  gEfiLoadedImageProtocolGuid                   ## CONSUMES
  gEfiLoadedImageDevicePathProtocolGuid         ## CONSUMES

EDKII INF 파일의 [Protocols] 섹션은 모듈 개발자가 사용하는 전역 프로토콜 "C Names" 목록이다. "C Names"는 주로 EDKII 패키지 DEC 파일에 있는 프로토콜의 실제 GUID 값을 조회하여 파싱한 후 데이터 구조를 모듈의 AutoGen.c 파일로 내보낸다.(https://edk2-docs.gitbook.io/edk-ii-inf-specification/2_inf_overview/29_-protocols-_section)

## CONSUMES는 프로토콜의 사용을 나타내기 위해서 사용된다. 해당 값은 아무 정보도 가지고 있지 않으며 EDKII codebase를 확인하면 아래와 같은 예시를 확인할 수 있다.

## CONSUMES
## PRODUCES
## ALWAYS_CONSUMES
## ALWAYS_PRODUCES
## SOMETIMES_PRODUCES
## SOMETIMES_PRODUCES
## NOTIFY

프로토콜 섹션을 추가한 뒤 실행을 하면 정상적으로 빌드가 되는 것을 볼 수 있다. (GUID 변수는 빌드 시스템에서자동으로 생성되는 Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/ImageInfo/ImageInfo/DEBUG/AutoGen.c 파일로 이동한다.)

// Protocols
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID gEfiLoadedImageProtocolGuid = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID gEfiLoadedImageDevicePathProtocolGuid = { 0xbc62157e, 0x3e33, 0x4fec, {0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf }};

실행 모습:

UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
Mapping table
      FS0: Alias(s):HD0a1:;BLK1:
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
Press ESC in 5 seconds to skip startup.nsh or any other key to continue.
Shell> fs0:
FS0:\> ImageInfo.efi
Image device: PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\ImageInfo.efi
Image file: \ImageInfo.efi
Image Base: 6885000
Image Size: 5140

Last updated