24. Dynamic/DynamiEx PCDs

지금까지 우리가 조사한 모든 PCD 유형은 모듈에 대해 로컬이다. PcdsFixedAtBuildPcdsFeatureFlag들은 간단한 정의이며, PcdsPatchableInModule은 단지 지역 변수일 뿐이다.

이런 PCD들만을 이용해서는 모듈 간의 정보를 전달할 수 없다. 만약, 완전한 UEFI Firmware 이미지에 이러한 기능이 있으면 매우 유용할 것이라 생각된다. 이 문제를 해결하는 데 도움이 될 수 있는 다른 종류의 PCD가 있다.

이 클래스를 PcdsDynamic이라고 부른다. 여기서 잠깐 생각해보자.

우리가 이런 PCD 값을 어디에 저장하더라? 이 데이터들은 플랫폼(모든 모듈)에 속하나, 하나의 특정 모듈에 국한되어 저장되지는 않는다. 따라서 모든 모듈이 액세스할 수 있는 알려진 인터페이스가 있는 플랫폼에 공통 데이터베이스가 필요하다. 또한, DEC/INF/DSC 파일에서 모든 동적 PCD 설정을 읽어 이 데이터베이스의 초기 값을 구성해야 한다.

일반적인 PCD 데이터베이스 모듈은 https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Universal/PCD 나와 있다.

PEI와 DXE 단계 모두에서 PCD 데이터베이스에 대한 액세스 권한이 필요하므로, 두 가지가 필요하다.

하나는 PEI 단계, https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Universal/PCD/Pei 하나는 DXE https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Universal/PCD/Dxe

동적 PCDs 플랫폼 Intergator의 기능을 사용하려면, 이러한 모듈을 최종 이미지에 포함하고, 해당 단계의 맨 처음에 로드해야 한다. 일반적으로 우리는 우리의 장에서 OVMF 플랫폼을 사용하여 이러한 모듈이 포함되어 있다.

이제 조금(?) 흥미로운 부분이다. 플랫폼에서 모든 DEC/INF/DSC를 파싱한 결과, 생성된 PCD 데이터베이스 파일을 살펴보겠다.

다음 경로에서 이런파일을 찾을 수 있다.

  • Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/PEIPcdDataBase.raw

  • Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/OUTPUT/DXEPcdDataBase.raw

위 파일들은 INF 파일에서 다음의 정의를 가진 모듈이 있기 때문에 만들어진다.

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Pei/Pcd.inf

[Defines]
  ...
  PCD_IS_DRIVER                  = PEI_PCD_DRIVER

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Dxe/Pcd.inf

[Defines]
  ...
  PCD_IS_DRIVER                  = DXE_PCD_DRIVER

PEI PCD 데이터베이스 파일에는 PEI 모듈만 사용되거나 PEI와 DXE 모듈 모두에서 사용되는 PCD가 사용포함된다.

DXE PCD 데이터베이스 파일에는 DXE 모듈에서만 사용되는 PCD가 포함되어 있다.

그러나, PEI 데이터베이스 모듈은 자체 데이터베이스에서만 접근할 수 있고, DXE 데이터베이스 모듈은 두 데이터베이스 모두에 대해 접근할 수 있다.

이렇게 하면, PEI 단계에 필요한 PCD만 있고 중복 없이 DXE 단계에 모든 PCD가 있다. 최적화를 위해 필요한 PCD만 PEI 단계에 유지한다. PEI 단계에서와 마찬가지로, 메모리 사용량을 가능한 많이 줄여야 한다.

[PEI|DXE]PcdDataBase.raw 형식

Hexdump유틸리티를 사용하면, 다음과 같은 파일의 내용을 볼 수 있다.

$ hexdump Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/PEIPcdDataBase.raw -C
00000000  3c 19 7d 3c 2c 68 14 4c  a6 8f 55 2d ea 4f 43 7e  |<.}<,h.L..U-.OC~|
00000010  07 00 00 00 f8 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  f8 00 00 00 60 00 00 00  84 00 00 00 6c 00 00 00  |....`.......l...|
00000030  dc 00 00 00 f0 00 00 00  f2 00 00 00 50 00 00 00  |............P...|
00000040  00 00 00 00 16 00 03 00  01 00 da da da da da da  |................|
00000050  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 08 00 00 00  40 00 00 00 05 00 03 00  |........@.......|
00000070  14 00 00 00 04 00 03 00  15 00 00 00 06 00 03 00  |................|
00000080  16 00 00 00 54 01 10 01  f8 00 00 08 00 01 00 08  |....T...........|
00000090  08 01 00 08 10 01 00 08  55 01 10 01 48 01 00 04  |........U...H...|
000000a0  4c 01 00 04 68 00 00 04  56 01 10 01 50 01 00 02  |L...h...V...P...|
000000b0  18 01 00 08 20 01 00 08  28 01 00 08 30 01 00 08  |.... ...(...0...|
000000c0  38 01 00 08 60 00 00 08  57 01 10 01 f6 00 00 02  |8...`...W.......|
000000d0  ec 00 00 10 52 01 00 02  40 01 00 08 49 f0 af a1  |....R...@...I...|
000000e0  eb fd 2a 44 b3 20 13 ab  4c b7 2b bc 00 00 00 00  |..*D. ..L.+.....|
000000f0  00 00 01 00 01 00 08 00                           |........|
000000f8

$ hexdump Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/OUTPUT/DXEPcdDataBase.raw -C
00000000  3c 19 7d 3c 2c 68 14 4c  a6 8f 55 2d ea 4f 43 7e  |<.}<,h.L..U-.OC~|
00000010  07 00 00 00 e0 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  e0 00 00 00 2e 00 00 00  78 00 00 00 70 00 00 00  |........x...p...|
00000030  c0 00 00 00 d0 00 00 00  d1 00 00 00 50 00 00 00  |............P...|
00000040  00 00 00 00 12 00 00 00  01 00 da da da da da da  |................|
00000050  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  80 02 00 00 e0 01 00 00  20 03 00 00 58 02 00 00  |........ ...X...|
00000070  00 00 00 00 00 00 00 00  00 01 00 04 04 01 00 04  |................|
00000080  e0 00 00 08 e8 00 00 08  f0 00 00 08 60 00 00 04  |............`...|
00000090  64 00 00 04 0a 01 00 01  d5 00 00 02 0b 01 10 01  |d...............|
000000a0  68 00 00 04 6c 00 00 04  08 01 00 02 d7 00 00 01  |h...l...........|
000000b0  d8 00 00 01 f8 00 00 08  0c 01 10 01 0d 01 10 01  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  00 00 00 00 00 08 02 01  01 da da da da da da da  |................|
000000e0

이런 데이터베이스는 https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Include/Guid/PcdDataBaseSignatureGuid.h 에 정의된 헤더에 존재하는 구조체로부터 시작한다.

typedef struct {
  GUID            Signature;                    // PcdDataBaseGuid.
  UINT32          BuildVersion;
  UINT32          Length;                       // Length of DEFAULT SKU PCD DB
  SKU_ID          SystemSkuId;                  // Current SkuId value.
  UINT32          LengthForAllSkus;             // Length of all SKU PCD DB
  UINT32          UninitDataBaseSize;           // Total size for PCD those default value with 0.
  TABLE_OFFSET    LocalTokenNumberTableOffset;
  TABLE_OFFSET    ExMapTableOffset;
  TABLE_OFFSET    GuidTableOffset;
  TABLE_OFFSET    StringTableOffset;
  TABLE_OFFSET    SizeTableOffset;
  TABLE_OFFSET    SkuIdTableOffset;
  TABLE_OFFSET    PcdNameTableOffset;
  UINT16          LocalTokenCount;              // LOCAL_TOKEN_NUMBER for all.
  UINT16          ExTokenCount;                 // EX_TOKEN_NUMBER for DynamicEx.
  UINT16          GuidTableCount;               // The Number of Guid in GuidTable.
  UINT8           Pad[6];                       // Pad bytes to satisfy the alignment.
} PCD_DATABASE_INIT;

이 구조체의 각 필드에 대해서 알아보도록 하자.

Signature

GUID            Signature;

첫 번째 필드는 GUID이며, 다음에 할당된다.

#define PCD_DATA_BASE_SIGNATURE_GUID \
{ 0x3c7d193c, 0x682c, 0x4c14, { 0xa6, 0x8f, 0x55, 0x2d, 0xea, 0x4f, 0x43, 0x7e } }

hexdump의 출력값을 보면, 이 값으로 어떻게 출력을 시작하는 지 그 방법을 알 수 있다. PCD 데이터베이스 코드는 이 값을 확인하여 데이터가 실제로 PCD 데이터베이스 파일인지 확인한다.

Build version

UINT32          BuildVersion;

PCD 데이터베이스의 형식은 EDKII 개발을 통해 변경될 수 있다. 만약 변경된다면, BuildVersion 값을 증가시켜야 한다. 현재 버전은 7이다. 물론 PEI PCD DB 모듈, DXE PCD DB 모듈 그리고 빌드 도구들은 모두 이 부분에서 동기화가 일어나야 한다. DB 모듈 코드는 이것이 올바르지 확인하는 작업을 한다.

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Pei/Service.h

/
// Please make sure the PCD Serivce PEIM Version is consistent with
// the version of the generated PEIM PCD Database by build tool.
//
#define PCD_SERVICE_PEIM_VERSION  7

//
// PCD_PEI_SERVICE_DRIVER_VERSION is defined in Autogen.h.
//
#if (PCD_SERVICE_PEIM_VERSION != PCD_PEI_SERVICE_DRIVER_VERSION)
  #error "Please make sure the version of PCD PEIM Service and the generated PCD PEI Database match."
#endif

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Dxe/Service.h

//
// Please make sure the PCD Serivce DXE Version is consistent with
// the version of the generated DXE PCD Database by build tool.
//
#define PCD_SERVICE_DXE_VERSION  7

//
// PCD_DXE_SERVICE_DRIVER_VERSION is defined in Autogen.h.
//
#if (PCD_SERVICE_DXE_VERSION != PCD_DXE_SERVICE_DRIVER_VERSION)
  #error "Please make sure the version of PCD DXE Service and the generated PCD DXE Database match."
#endif

버전 변경에 대한 예를 보고 싶다면 다음 링크의 커밋을 확인하면 된다. 버전 6에서 7로의 업데이트이다.

https://github.com/tianocore/edk2/commit/7c73626513238176bdd16dca14fcf3f9e10bcc81

Length

UINT32          Length;
UINT32          UninitDataBaseSize;

EDKII 빌드 계산의 끝에서 일부 PCD는 0의 값을 가지고 있을 것이다. 이러한 PCD가 많이 있을 수 있으며, 플래시 이미지에 모두 저장하는 것은 가장 좋은 방법은 아니다.

따라서, *.raw 이미지에서 0이 아닌 PCD만 저장하도록 한다. 이 Length는 이미지의 크기를 나타낸다.

|.........| 0
|.........|
|.........|
___________ Length

그러나 실제로 PEI/DXE DB 모듈에서 PCD로 작동할 때, 우리가 직접 값을 할당할 수 있기 때문에, 모든 PCD가 실제 메모리에 존재해야 한다. 또한, UninitDataBaseSize 필드를 이용하면 이를 위해 얼마나 많은 공간을 준비해야 하는지도 알 수 있다.

|.........| 0
|.........|
|.........|
___________ Length
|000000000|
|000000000|
|000000000|
___________ Length + UninitDataBaseSize

LocalTokenNumberTable

TABLE_OFFSET    LocalTokenNumberTableOffset;
...
UINT16          LocalTokenCount;

이는 테이블에서 가장 중요한 부분이다.

TABLE_OFFSET은 단순히 UINT32 값이라고 보면 된다. 이 값은 LocalToken 테이블에 대한 오프셋인데, LocalTokenCount 기록을 가지고 있다.

각 기록(=LocalToken)은 UINT32 값이다. 이는 다음과 같이 나타낼 수 있다.

union {
  UINT32 LocalToken;
  struct {
    UINT32 pcd_type : 4;
    UINT32 pcd_datum_type : 8;
    UINT32 offset : 20;
  }
}
  • pcd_type : 직관적으로 말하면, PCD의 유형이다. PCD에는 여러 클래스들이 존재할 수 있다. 이에 대해서는 나중에 언급하도록 하겠다. PCD는 DATA, HII 혹은 VPD가 될 수 있다.

  • pcd_datum_type : PCD에 존재하는 데이터 유형은 UINT8, UINT16, UINT32, UINT64, VOID*이다.

  • offset : 데이터베이스 이미지에서 이 PCD의 초기 데이터에 대한 offset

LocalToken은 암묵적으로 1부터 숫자를 매긴다. 그리고 일부 펌웨어 모듈이 PCD와 함께 동작하려할 때, "야, 나 있잖아.. LocalToken №xxx에서 PCD를 가져오거나 설정하고 싶다."라고 말하는 데이터베이스와 통신한다. PCD 데이터베이스는 이 LocalToken을 읽은 다음 pcd_type 및 pcd_datum_type에 따라 오프셋 필드에서 실제 데이터를 해석한다.

넘버링하는 것은 PEI 및 DXE 데이터베이스에 대해 순차적이프라는 점에 유의해야 한다. 즉, PEI 데이터베이스의 LocalToken에는 숫자 1 .. (PEI_DB.LocalTokenCount)가 있는 것으로 간주되고 DXE 데이터베이스의 LocalToken에는 (PEI_DB.LocalTokenCount + 1) .. (PEI_DB.LocalTokenCount + 1 + DXE_DB.LocalTokenCount)가 있는 것으로 간주된다.

ExMapTable과 GuidTable. [PcdsDynamic]과 [PcdsDynamicEx]의 필요성

TABLE_OFFSET    ExMapTableOffset;
TABLE_OFFSET    GuidTableOffset;
UINT16          ExTokenCount;
UINT16          GuidTableCount;

LocalToken No.로 PCD는 쉽게 참조할 수 있다. 하지만, 이를 위해서는 실제 매핑된 구조(PCD <--> LocalToken)를 알아야 한다. 플래시 이미지의 일부와 동시에 모듈 및 PCD 데이터베이스를 구축할 때도 마찬가지이다(이 경우 OVMF).

그러나 때로는, 플래시 이미지가 이미 구축되어 있고 일부 이미지 PCD를 사용하는 별도의 모듈을 작성해야하는 경우가 있다. 이때, EDKII는 [PcdsDynamicEx] 방식을 제공한다. 이 방식은 GUID+ExTokenNumber의 조합으로 dynamic PCD를 참조한다.

해당 방법으로 dynamic PCD를 참조할 경우 ExTokenNumberUINT32 값이며, 두 값은 모든 PCD에 대하여 고유한 값으로 지정되어야 한다. PCD 데이터베이스는 GUID+ExTokenNumber의 요청을 수신하면, 우선 GUID가 데이터베이스에 존재하는지 확인한다.

PCD 데이터베이스 안에는 완전(totally) GuidTableCount GUID가 존재하며, 데이터베이스 파일의 GuidTableOffset에서 시작하여 배치된다. 요청된 GUID가 데이터베이스의 일부 GUID와 동일한 경우, 이 GUID의 인덱스를 기억한다. 그 다음, 코드는 ExMapTableOffset 아래에 있는 ExMapTable의 기록을 확인한다.

완전 ExTokenCount의 기록과 각 기록형식은 다음과 같다.

typedef struct  {
  UINT32    ExTokenNumber;
  UINT16    TokenNumber;        // local token index in the LocalTokenNumberTable
  UINT16    ExGuidIndex;        // GUID index in the GuidTable
} DYNAMICEX_MAPPING;

일부 기록에는 요청된 ExTokenNumber가 있고, 요청된 GUID에 해당하는 ExGuidIndex가 있는데, 우리는 우리의 LocalToken 숫자를 찾은 셈이다. 이제 나머지는 [PcdDynamic]의 경우와 동일하게 진행된다. 그리고 ExTokenCount는 항상 LocalTokenCount보다 작거나 같다. (ExTokenCount ≤ LocalTokenCount)

StringTable and SizeTable

TABLE_OFFSET    StringTableOffset;
TABLE_OFFSET    SizeTableOffset;

데이터베이스 코드가 PCD 값을 반환해야 하는 경우, 오프셋 이외의 PCD 자체의 크기를 알아야 한다. 단순한 데이터 유형인 경우에는 크기가 고정된다. 예를 들어, PCD의 pcd_datum_type의 UINT64의 경우 PCD 데이터의 크기는 8바이트이다.

하지만, PCD 데이터는 구조 또는 문자열일 수 있다. 이 경우, StringTable에 대한 인덱스로 해석되는 LocalToken의 오프셋은 StringTableOffset 아래에 나와있다.

이 테이블은 UINT32(데이터에 대한 실제 오프셋)으로 구성된다. 이 경우 데이터의 크기는 SizeTable에 기록되며 이는 SizeTableOffset 아래에 위치하고 다음과 같은 요소로 구성된다.

struct {
  UINT16 MaxSize;
  UINT16 CurrentSize;
}

다시 한 번 말하지만, 숫자는 암묵적인 방식을 따라 붙인다. 대상 LocalToken의 경우 처음부터 LocalTokenNumberTable을 검사하고, 대상 LocalToken 이전에 몇 개의 문자열/포인터 PCD가 있었는지 계산하여야하며 이 값은 SizeTable 기록의 인덱스가 된다.

AutoGen files

이전 섹션을 기반으로, 데이터베이스 내용을 파싱하는 것이 쉽지 않았다는 것을 알 수 있었다. 이를 위해 EDKII는 AutoGen 파일에서 도움을 제공한다. 예를 들어, 이 내용은 PEI PCD DB AutoGen 파일에 작성된다.

$ cat Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/DEBUG/AutoGen.h
---
//
// External PCD database debug information
//
#if 0
#define PEI_GUID_TABLE_SIZE                1U
#define PEI_STRING_TABLE_SIZE              2U
#define PEI_SKUID_TABLE_SIZE               1U
#define PEI_LOCAL_TOKEN_NUMBER_TABLE_SIZE  22
#define PEI_LOCAL_TOKEN_NUMBER             22
#define PEI_EXMAPPING_TABLE_SIZE           3U
#define PEI_EX_TOKEN_NUMBER                3U
#define PEI_SIZE_TABLE_SIZE                2U
#define PEI_GUID_TABLE_EMPTY               FALSE
#define PEI_STRING_TABLE_EMPTY             FALSE
#define PEI_SKUID_TABLE_EMPTY              TRUE
#define PEI_DATABASE_EMPTY                 FALSE
#define PEI_EXMAP_TABLE_EMPTY              FALSE

typedef struct {
  ...
} PEI_PCD_DATABASE_INIT;

typedef struct {
  ...
} PEI_PCD_DATABASE_UNINIT;

typedef struct {
  PEI_PCD_DATABASE_INIT    Init;
  PEI_PCD_DATABASE_UNINIT  Uninit;
} PEI_PCD_DATABASE;
#endif
$ cat Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/DEBUG/AutoGen.c
---
//
// External PCD database debug information
//
#if 0
PEI_PCD_DATABASE_INIT gPEIPcdDbInit = {
  ...
};
#endif

해당 파일의 코드를 읽어보면, PEI_PCD_DATABASE_INIT / PEI_PCD_DATABASE_UNINIT 구조 내부에 주석이 있는 PCD의 내용을 찾을 수 있다. 보다시피 모든 코드가 빌드에 포함되어있지 않으므로, 단지 이것은 개발자를 위한 도움말이라고 생각하면 된다.

DXE PCD DB AutoGen 파일에서 비슷한 설명을 찾아볼 수 있다.

  • Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/DEBUG/AutoGen.h

  • Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/DEBUG/AutoGen.c

PCD_TABLE_parser

AutoGen 파일의 정보는 꽤나 유용하지만, 검색하기가 다소 어렵다. 이를 대비해 PCD_TABLE_parser 유틸리티를 사용할 수 있다.

Clone & Build

$ git clone https://github.com/Kostr/PCD_TABLE_parser.git ~/PCD_TABLE_parser
$ cd ~/PCD_TABLE_parser
$ make
cc    -c -o main.o main.c
cc    -c -o guids.o guids.c
cc    -c -o utils.o utils.c
gcc main.o guids.o utils.o -o parse_pcd_db

--help 명령어를 사용하면, 도움말을 해결할 수 있다.

$ ./parse_pcd_db --help
Usage: parse_pcd_db [--peidb <PEI_PCD_DB.raw>] [--dxedb <DXE_PCD_DB.raw>] [--vpd <VPD.bin>]
Program to parse PCD Database raw files

--peidb <PEI_PCD_DB.raw>     - provide PEI PCD database
--dxedb <DXE_PCD_DB.raw>     - provide DXE PCD database
--vpd   <VPD.bin>            - provide VPD binary

Example:
parse_pcd_db \
 --peidb "Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/PEIPcdDataBase.raw" \
 --dxedb "Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/OUTPUT/DXEPcdDataBase.raw" \
 --vpd   "Build/OvmfX64/RELEASE_GCC5/FV/8C3D856A-9BE6-468E-850A-24F7A8D38E08.bin"

지금은 --vpd 옵션에 대해서 언급하지는 않겠다. --peidb와 --dxedb 인수를 제공하는 프로그램을 시작한다.

$ parse_pcd_db \
 --peidb "/home/user/edk2/Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/PEIPcdDataBase.raw" \
 --dxedb "/home/user/edk2/Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/OUTPUT/DXEPcdDataBase.raw" | less

이어지는 내용은 출력에 대한 예이다. 아마 방금 언급했던 PCD 테이블에 대한 설명에서 더 잘 설명되어 있다고 생각한다. 출력에 대한 몇 가지 설명을 해보자면,

  • PEI와 DXE 데이터베이스 사이의 순차적 넘버링을 볼 수 있다.

  • 0-unitialized는 PCD가 Length ~ (Length + UninitDataBaseSize) 영역에 있음을 의미한다.

  • 일부 PCD에는 DynamicExToken/GUID 정보가 포함되어있다. 이는 메소드를 통해 선언된 PCD이다.

  • 포인터 유형의 PCD에는 크기 정보가 포함되어 있다.

PEI PCD DB
LocalTokenNumberTable:

1:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized

2:
Token type = Data
Datum type = UINT64
0 - unitialized

3:
Token type = Data
Datum type = UINT64
0 - unitialized

4:
Token type = Data
Datum type = UINT64
0 - unitialized

5:
Token type = Data
Datum type = UINT64
0 - unitialized

6:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized

7:
Token type = Data
Datum type = UINT32
0 - unitialized

8:
Token type = Data
Datum type = UINT32
0 - unitialized

9:
Token type = Data
Datum type = UINT32
Value:
0x00000040 (=64)

10:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized

11:
Token type = Data
Datum type = UINT16
0 - unitialized

12:
Token type = Data
Datum type = UINT64
0 - unitialized

13:
Token type = Data
Datum type = UINT64
0 - unitialized

14:
Token type = Data
Datum type = UINT64
0 - unitialized

15:
Token type = Data
Datum type = UINT64
0 - unitialized

16:
Token type = Data
Datum type = UINT64
0 - unitialized

17:
Token type = Data
Datum type = UINT64
Value:
0x0000000800000000 (=34359738368)

18:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized

19:
Token type = Data
Datum type = UINT16
Value:
0x0008 (=8)

20:
Token type = String
Datum type = Pointer
DynamicEx Token = 0x00030005
DynamicEx GUID  = a1aff049-fdeb-442a-b32013ab4cb72bbc [gEfiMdeModulePkgTokenSpaceGuid]
CurrentSize = 1
MaxSize     = 1
Value:
00                                               | .

21:
Token type = Data
Datum type = UINT16
DynamicEx Token = 0x00030004
DynamicEx GUID  = a1aff049-fdeb-442a-b32013ab4cb72bbc [gEfiMdeModulePkgTokenSpaceGuid]
0 - unitialized

22:
Token type = Data
Datum type = UINT64
DynamicEx Token = 0x00030006
DynamicEx GUID  = a1aff049-fdeb-442a-b32013ab4cb72bbc [gEfiMdeModulePkgTokenSpaceGuid]
0 - unitialized
_____

DXE PCD DB
LocalTokenNumberTable:

23:
Token type = Data
Datum type = UINT32
0 - unitialized

24:
Token type = Data
Datum type = UINT32
0 - unitialized

25:
Token type = Data
Datum type = UINT64
0 - unitialized

26:
Token type = Data
Datum type = UINT64
0 - unitialized

27:
Token type = Data
Datum type = UINT64
0 - unitialized

28:
Token type = Data
Datum type = UINT32
Value:
0x00000280 (=640)

29:
Token type = Data
Datum type = UINT32
Value:
0x000001e0 (=480)

30:
Token type = Data
Datum type = UINT8
0 - unitialized

31:
Token type = Data
Datum type = UINT16
Value:
0x0208 (=520)

32:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized

33:
Token type = Data
Datum type = UINT32
Value:
0x00000320 (=800)

34:
Token type = Data
Datum type = UINT32
Value:
0x00000258 (=600)

35:
Token type = Data
Datum type = UINT16
0 - unitialized

36:
Token type = Data
Datum type = UINT8
Value:
0x01 (=1)

37:
Token type = Data
Datum type = UINT8
Value:
0x01 (=1)

38:
Token type = Data
Datum type = UINT64
0 - unitialized

39:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized

40:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
_____

UEFI Tool

우리가 바이너리 UEFI 이미지의 PCD DB 내용을 보고자 하는 상황을 생각해보자.

-> 해당 주소에서는 단순 다운로드, 혹은 소스코드를 다운받고 컴파일할 수 있다.

-> 여기서는 이미 컴파일 되어있는 이미지를 다운 받을 수 있다.

프로그램을 로드하면 다음과 같은 프로그램이 실행된다.

여태까지 리눅스 환경에서 작업하였던 OVMF.fd를 가져와서 UEFITool을 통하여 UEFI 펌웨어 파일을 열어보자.

이제 OVMF.fd에 대해 UEFI 이미지 섹션의 내용을 열어보면서 분석을 진행할 수 있다.

하지만, 지금은 이미지에서 PEI/DXE PCD 데이터베이스를 찾아야 한다. 각 데이터베이스는 시그니처 GUID로 시작되는 것을 기억해두자.

#define PCD_DATA_BASE_SIGNATURE_GUID \
{ 0x3c7d193c, 0x682c, 0x4c14, { 0xa6, 0x8f, 0x55, 0x2d, 0xea, 0x4f, 0x43, 0x7e } }

Action > Search 메뉴를 선택하고 GUID 탭에 다음 값을 입력해보자.

3c7d193c-682c-4c14-a68f-552dea4f437e

여기서 프로그램은 두 Raw 섹션(PcdPeim/Raw, PcdDxe/Raw)을 찾아야 한다고 한다.

첫 번째 결과값을 클릭하면, 프로그램이 이미지 내에 어떤 부분에 해당 섹션이 위치해 있는지를 표시해준다. 클릭한 결과, PcdPeim 모듈 아래에 배치되어 있음을 알 수 있다.

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Pei/Pcd.inf

이는 Pei/Pcd.inf 파일의 모듈 이름이다. 오른쪽에 보이는 Information 부분에서 Body Size는 어느 정도 OVMF.fd에 구현되어있는지에 따라 다르지만, 우리가 실제로 빌드하여 qemu로 실행한 것과 동일한 크기이다. (hexdump를 통해 본 것과 같다.)

섹션 이름에 마우스 오른쪽 버튼을 클릭하고, Extract Body...를 선택하면섹션 데이터를 저장할 수 있다. 기본적으로 프로그램은 Section_Raw_PcdPeim_PcdPeim_body.raw를 새 파일의 이름으로 제안한다.

이제 두 번째 검색 결과를 확인할 수 있다. 해당 섹션은 Dxe/Pcd.inf로, PcdDxe 모듈 내부에 존재한다. https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Dxe/Pcd.inf

마찬가지로 Body size는 출력 크기에 해당하고, 이 섹션의 Raw 파일에 대해 프로그램은 Section_Raw_PcdDxe_PcdDxe_body.raw를 제안한다.

지금까지 본 섹션에서 한 것은, OVMF.fd 파일에서 PEI와 DXE의 데이터베이스를 추출해낸 것이며 지금부터는 parse_pcd_db 유틸리티를 사용하여 구문 분석을 진행할 것이다.

추출된 파일을 리눅스 홈 폴더에 저장하고 프로그램을 실행한다. 다음의 명령어를 실행해준다.

$ ./parse_pcd_db \
 --peidb ~/Section_Raw_PcdPeim_PcdPeim_body.raw \
 --dxedb ~/Section_Raw_PcdDxe_PcdDxe_body.raw

이에 대한 출력은 이전과 같아야만 한다.

Last updated