7. 핸들/프로토콜 데이터 베이스 구조 - Part 2

애플리케이션 프로토콜에 대한 기본적인 사용

이번 장에서는 IMAGE_HANDLE에 존재하는 프로토콜의 GUID를 출력하는 것이 목표다. 시작하기 앞서서 EDKII 코드베이스에서 내부적으로 EFI_GUID가 무엇을 뜻하는지 이해해야 한다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiBaseType.h)

///
/// 128-bit buffer containing a unique identifier value.
///
typedef GUID                      EFI_GUID;

GUID 구조는 아래의 링크에 정의되어있다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Base.h)

///
/// 128 bit buffer containing a unique identifier value.
/// Unless otherwise specified, aligned on a 64 bit boundary.
///
typedef struct {
  UINT32  Data1;
  UINT16  Data2;
  UINT16  Data3;
  UINT8   Data4[8];
} GUID;

GUID는 위와 같이 4바이트-2바이트-2바이트-8바이트로 구성되어 있다. 다행히도 위와 같은 구조체의 필드 하나씩 직접 뽑아줄 필요가 없다는 것이다. Print 함수의 %g 옵션을 사용하면 GUIDs가 출력되기 때문에 간편하게 출력할 수 있다. Print 옵션에 대해서는 아래 링크에 자세히 설명되어 있다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PrintLib.h)

/// Printing Guid Using %g Option
Print("GUID=%g\n", myGUID);

우리는 PROTOCOL_INTERFACE 구조에서 참조되는 PROTOCOL_ENTRY의 EFI_GUID 필드를 출력할 것이다. 따라서 이전 챕터에서 언급한 것과 동일한 이유로 구조를 재정의 해야한다.

typedef struct {
  UINTN               Signature;
  /// Link Entry inserted to mProtocolDatabase
  LIST_ENTRY          AllEntries;
  /// ID of the protocol
  EFI_GUID            ProtocolID;
  /// All protocol interfaces
  LIST_ENTRY          Protocols;
  /// Registerd notification handlers
  LIST_ENTRY          Notify;
} PROTOCOL_ENTRY;

typedef struct {
  UINTN                       Signature;
  /// Link on IHANDLE.Protocols
  LIST_ENTRY                  Link;
  /// Back pointer
  IHANDLE                     *Handle;
  /// Link on PROTOCOL_ENTRY.Protocols
  LIST_ENTRY                  ByProtocol;
  /// The protocol ID
  PROTOCOL_ENTRY              *Protocol;
  /// The interface value
  VOID                        *Interface;
  /// OPEN_PROTOCOL_DATA list
  LIST_ENTRY                  OpenList;
  UINTN                       OpenListCount;

} PROTOCOL_INTERFACE;

이전 챕터에서 설명한 것과 동일하게 LINK는 다른 PROTOCOL_INTERFACE에 존재하는 LIST_ENTRY구조를 가리킨다(double-linked list). 아래는 이를 간략하게 표헌한 모습이다.

typedef struct {                                         typedef struct {
  UINTN                       Signature;                   UINTN                       Signature;
  struct LIST_ENTRY {                          |---------> struct LIST_ENTRY {
    LIST_ENTRY  *ForwardLink; -----------------|             LIST_ENTRY  *ForwardLink;
    LIST_ENTRY  *BackLink;                                   LIST_ENTRY  *BackLink;
  } Link;                                                  } Link;
  IHANDLE                     *Handle;                     IHANDLE                     *Handle;
  LIST_ENTRY                  ByProtocol;                  LIST_ENTRY                  ByProtocol;
  PROTOCOL_ENTRY              *Protocol;                   PROTOCOL_ENTRY              *Protocol;
  VOID                        *Interface;                  VOID                        *Interface;
  LIST_ENTRY                  OpenList;                    LIST_ENTRY                  OpenList;
  UINTN                       OpenListCount;               UINTN                       OpenListCount;
} PROTOCOL_INTERFACE;   

하지만 우리는 Link 필드의 ForwardLinkBackLink가 필요한 것이 아닌 PROTOCOL_INTERFACE 구조 자체에 대한 포인터가 필요하다. 따라서 아래와 같이 몇가지 매크로에 대한 정의가 필요하다. 해당 매크로는 리눅스 커널 프로그래밍을 공부했다면 친숙하게 느껴질 수도 있다. 혹시라도 모를 사람을 위해서 자세히 설명된 링크를 아래에 첨부하겠다. (https://www.bhral.com/post/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90%EC%9D%98-container_of-%EB%A7%A4%ED%81%AC%EB%A1%9C)

#define offsetof(a,b) ((INTN)(&(((a*)(0))->b)))
c
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

간략하게 설명을 하면 container_of 매크로는 포인터(ptr)가 가리키는 구조체 멤버(member)를포함하는 구조체(type)의 주소를 반환한다고 생각하면 된다.

이 모든 점들을 가지고 UefiMain 함수의 최종코드를 작성한다.

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  IHANDLE* MyHandle = ImageHandle;
  Print(L"Signature: %c %c %c %c\n", (MyHandle->Signature >>  0) & 0xff,
                                     (MyHandle->Signature >>  8) & 0xff,
                                     (MyHandle->Signature >> 16) & 0xff,
                                     (MyHandle->Signature >> 24) & 0xff);

  Print(L"Back Protocol Interface Link: %p\n", MyHandle->Protocols.BackLink);
  Print(L"Forward Protocol Interface Link: %p\n", MyHandle->Protocols.ForwardLink);

  LIST_ENTRY *FirstLink = MyHandle->Protocols.ForwardLink;
  LIST_ENTRY *CurrentLink = FirstLink;
  do {
        PROTOCOL_INTERFACE* MyProtocolInterface = container_of(CurrentLink, PROTOCOL_INTERFACE, Link);

        Print(L"\n");
        Print(L"Current Link: %p\n", CurrentLink);
        Print(L"Signature: %c %c %c %c\n", (MyProtocolInterface->Signature >>  0) & 0xff,
                                           (MyProtocolInterface->Signature >>  8) & 0xff,
                                           (MyProtocolInterface->Signature >> 16) & 0xff,
                                           (MyProtocolInterface->Signature >> 24) & 0xff);

        Print(L"Back Link: %p\n", MyProtocolInterface->Link.BackLink);
        Print(L"Forward Link: %p\n", MyProtocolInterface->Link.ForwardLink);
        Print(L"GUID=%g\n", MyProtocolInterface->Protocol->ProtocolID);
        CurrentLink = MyProtocolInterface->Link.ForwardLink;
  } while (CurrentLink != FirstLink);

  return EFI_SUCCESS;
}

해당 코드를 컴파일 이후 실행시 아래와 같은 결과가 나타난다.

FS0:\> ImageHandle.efi
h n d l
Back Protocol Interface Link: 68D4320
Forward Protocol Interface Link: 6891520

Current Link: 6891520
p i f c
Back Link: 6891430
Forward Link: 6891B20
GUID=752F3136-4E16-4FDC-A22A-E5F46812F4CA

Current Link: 6891B20
p i f c
Back Link: 6891520
Forward Link: 68D4320
GUID=BC62157E-3E33-4FEC-9920-2D3B36D750DF

Current Link: 68D4320
p i f c
Back Link: 6891B20
Forward Link: 6891430
GUID=5B1B31A1-9562-11D2-8E3F-00A0C969723B

Current Link: 6891430
  ? ? ?
Back Link: 68D4320
Forward Link: 6891520
GUID=00000000-0000-0000-0000-000000000000

결과로 나온 GUID 값을 EDKII 소스코드에서 grep명령어를통해 찾아보자.

$ grep -i 752F3136 -r ./ --exclude-dir=Build
./MdePkg/Include/Protocol/ShellParameters.h:  0x752f3136, 0x4e16, 0x4fdc, { 0xa2, 0x2a, 0xe5, 0xf4, 0x68, 0x12, 0xf4, 0xca } \
./MdePkg/MdePkg.dec:  gEfiShellParametersProtocolGuid      = { 0x752f3136, 0x4e16, 0x4fdc, {0xa2, 0x2a, 0xe5, 0xf4, 0x68, 0x12, 0xf4, 0xca }}

두곳에서 사용되는 것을 볼 수 있다. 사용되는 곳에서의 GUID에 대한 정의는 아래의 링크들에 들어가면 볼 수 있다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h) (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h)

다음으로 남은 두개의 GUID는 UEFI 스펙(specification)에서 확인할 수 있다.

  1. EFI_LOADE_IMAGE_DEVICE_PATH_PROTOCOL - EFI_LOADE_IMAGE_DEVICE_PATH_PROTOCOL이 설치된 경우 Loaded Image Device Path Protocol은 PE/COFF 이미지가 EFI 부팅서비스인 LoadImage()를 통해 로드될 때 사용된 장치 경로를 지정해준다.

    #define EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID \
    {0xbc62157e,0x3e33,0x4fec,\
     {0x99,0x20,0x2d,0x3b,0x36,0xd7,0x50,0xdf}}
  2. EFI_LOAD_IMAGE_PROTOCOL - 로드된 이미지에 대한 정보를 얻기 위해 모든 이미지 핸들에서 사용할 수 있다.

    #define EFI_LOADED_IMAGE_PROTOCOL_GUID\
     {0x5B1B31A1,0x9562,0x11d2,\
     {0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B}}

마지막으로 출력된 PROTOCOL_INTERFACE 구조는 유효한 " p i f c" 서명이 없으므로 해당 GUID를 유심히 볼 필요는 없다.

Last updated