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
필드의 ForwardLink
나 BackLink
가 필요한 것이 아닌 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)에서 확인할 수 있다.
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}}
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