23. PatchableInModule PCD 및 GenPatchPcdTable/PatchPcdValue 유틸리티를 통해 PCD를 변경하는 방법

PatchableInModule PCD class에 대해서 알아보자.

[PcdsPatchableInModule] 섹션에서 DEC(UefiLessonsPkg.dec) 파일에 새 PCD를 추가한다.

[PcdsPatchableInModule]
  gUefiLessonsPkgTokenSpaceGuid.PcdPatchableInt32|0x31313131|UINT32|0xFCDA11B5

INF(PCDLessons.inf)에 마찬가지로 추가해준다.

[PatchPcd]
  gUefiLessonsPkgTokenSpaceGuid.PcdPatchableInt32

PCDLesson.c 파일에서 PCD 값을 출력해내려면, PatchPcdGet 또는 Generic PcdGet을 사용해야 한다.

두 방법 모두 테스트 해보자.

Print(L"PcdPatchableInt32=0x%x\n", PatchPcdGet32(PcdPatchableInt32));
Print(L"PcdPatchableInt32=0x%x\n", PcdGet32(PcdPatchableInt32));
FS0:\> PCDLesson.efi
...
PcdPatchableInt32=0x31313131
PcdPatchableInt32=0x31313131

빌드 이후 AutoGen 파일은 각각 다음과 같다.

$ cat Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h
---
#define _PCD_TOKEN_PcdPatchableInt32  0U
#define _PCD_PATCHABLE_VALUE_PcdPatchableInt32  ((UINT32)0x31313131U)
extern volatile   UINT32  _gPcd_BinaryPatch_PcdPatchableInt32;
#define _PCD_GET_MODE_32_PcdPatchableInt32  _gPcd_BinaryPatch_PcdPatchableInt32
#define _PCD_PATCHABLE_PcdPatchableInt32_SIZE 4
#define _PCD_GET_MODE_SIZE_PcdPatchableInt32  _gPcd_BinaryPatch_Size_PcdPatchableInt32
extern UINTN _gPcd_BinaryPatch_Size_PcdPatchableInt32;
#define _PCD_SET_MODE_32_PcdPatchableInt32(Value)  (_gPcd_BinaryPatch_PcdPatchableInt32 = (Value))
#define _PCD_SET_MODE_32_S_PcdPatchableInt32(Value)  ((_gPcd_BinaryPatch_PcdPatchableInt32 = (Value)), RETURN_SUCCESS)
$ cat Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.c
---
volatile  UINT32 _gPcd_BinaryPatch_PcdPatchableInt32 = _PCD_PATCHABLE_VALUE_PcdPatchableInt32;
GLOBAL_REMOVE_IF_UNREFERENCED UINTN _gPcd_BinaryPatch_Size_PcdPatchableInt32 = 4;

https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h 따르면,

#define PatchPcdGet32(TokenName)  _gPcd_BinaryPatch_##TokenName
...
#define PcdGet32(TokenName)  _PCD_GET_MODE_32_##TokenName

전처리기 코드를 풀면, 두 호출이 동일한 변수로 해독되는 것을 확인할 수 있다.

PatchPcdGet32(PcdPatchableInt32) -> _gPcd_BinaryPatch_PcdPatchableInt32

PcdGet32(PcdPatchableInt32) -> _PCD_GET_MODE_32_PcdPatchableInt32 ->  _gPcd_BinaryPatch_PcdPatchableInt32

아래는 AutoGen.c에서 할당되는 volatile 변수이다.

volatile UINT32  _gPcd_BinaryPatch_PcdPatchableInt32 =  _PCD_PATCHABLE_VALUE_PcdPatchableInt32 // = ((UINT32)0x31313131U)

따라서, FixedAtBuild와 FeatureFlag PCD의 주요한 차이점은 volatile로 정의된 변수와 SET 함수가 차단되지 않는다는 것이다.

런타임 시 PCD 값 수정

지금부터는 PCD를 설정해보겠다. 우리가 알고 있는 바와 같이, 두 가지 방법이 있다.

PatchPcdSet<Type> 혹은 generic PcdSet<Type>S API 중 하나를 사용할 수 있다.

자세한 사항은 PcdLib.h를 참고하자. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h

#define PatchPcdSet32(TokenName, Value)  (_gPcd_BinaryPatch_##TokenName = (Value))
...
#define PcdSet32S(TokenName, Value)         _PCD_SET_MODE_32_S_##TokenName    ((Value))

PcdSet32S는 값을 반환하는 매크로로 해독된다는 것을 기억하고 있도록 하자. 예를 들어,

PcdSet32S(PcdPatchableInt32, 44) -->  _PCD_SET_MODE_32_S_PcdPatchableInt32 ((44)) --> ((_gPcd_BinaryPatch_PcdPatchableInt32 = (44)), RETURN_SUCCESS)

따라서 다음과 같은 오류를 원하지 않는 경우에는,

/<...>/Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h:108:106: error: right-hand operand of comma expression has no effect [-Werror=unused-value]
  108 | #define _PCD_SET_MODE_32_S_PcdPatchableInt32(Value)  ((_gPcd_BinaryPatch_PcdPatchableInt32 = (Value)), RETURN_SUCCESS)
      |                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~.

아래 코드와 함께 사용해야 한다.

EFI_STATUS Status = PcdSet32S(PcdPatchableInt32, 44);
Print(L"Status=%r\n", Status);

PcdSet32S와 다르게 PatchPcdSet32 API 함수는 이러한 것이 필요치 않으므로, 다음처럼 간단하게 사용할 수 있다.

PatchPcdSet32(PcdPatchableInt32, 43);

PCDLesson.c 애플리케이션 코드에서 두 SET 메소드를 모두 테스트한다.

Print(L"PcdPatchableInt32=0x%x\n", PatchPcdGet32(PcdPatchableInt32));
Print(L"PcdPatchableInt32=0x%x\n", PcdGet32(PcdPatchableInt32));
PatchPcdSet32(PcdPatchableInt32, 43);
Print(L"PcdPatchableInt32=%d\n", PatchPcdGet32(PcdPatchableInt32));
EFI_STATUS Status = PcdSet32S(PcdPatchableInt32, 44);
Print(L"Status=%r\n", Status);
Print(L"PcdPatchableInt32=%d\n", PatchPcdGet32(PcdPatchableInt32));

위의 코드를 올바르게 빌드하고 실행하면, 다음의 결과가 표시된다.

FS0:\> PCDLesson.efi
...
PcdPatchableInt32=0x31313131
PcdPatchableInt32=0x31313131
PcdPatchableInt32=43
Status=Success
PcdPatchableInt32=44

PCD 패치

이 섹션에서는 PCD 기본값에 16진수(HEX)를 할당하거나 이 PCD 유형의 이름을 PatchableInModule로 지정한 이유에 대해 언급한다.

이 PCD 타입은 바이너리 PE/COFF 이미지(즉, 최종 *.efi 파일)에서 PCD의 값이 변경될 수 있기 때문에 그렇게 이름 붙였다고 말할 수 있다. 이를 위해 두 가지 유틸리티가 사용된다.

  • GenPatchPcdTable - 이 툴은 맵 파일을 파싱하여 EFI 이미지에 대한 패치 가능한 PCD 오프셋을 가져오는 데 사용된다.

  • PatchPcdValue - 이 툴은 실제로 PCD 값을 패치하는 데 사용된다.

EDKII에서 이러한 유틸리티에 대한 매뉴얼을 제공하고 있다. https://github.com/tianocore/edk2/blob/master/BaseTools/UserManuals/PatchPcdValue_Utility_Man_Page.rtf

GenPatchPcdTable Tool

GenPatchPcdTable부터 시작해보겠다. 먼저 이 도구에 대한 도움말을 확인해보자.

$ ./BaseTools/BinWrappers/PosixLike/GenPatchPcdTable -h
Usage: GenPatchPcdTable.py -m <MapFile> -e <EfiFile> -o <OutFile>

Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved.

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -m MAPFILE, --mapfile=MAPFILE
                        Absolute path of module map file.
  -e EFIFILE, --efifile=EFIFILE
                        Absolute path of EFI binary file.
  -o OUTFILE, --outputfile=OUTFILE
                        Absolute path of output file to store the got
                        patchable PCD table.

이제 PatchPcdTable를 만들어보겠다.

*.efi 파일의 경우, 다음 중 하나를 사용할 수 있다.

Build/UefiLessonsPkg/RELEASE_GCC5/X64/PCDLesson.efi 
Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/OUTPUT/PCDLesson.efi
Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.efi

*.map 파일의 경우, 다음 중 하나를 사용할 수 있다.

Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.map
Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/OUTPUT/PCDLesson.map

맵 파일에서 PCD를 어떻게 찾을 수 있을지 궁금한 경우, 다음 코드를 사용하면 PCD를 찾을 수 있다.

$ grep PatchableInt32 Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.map -A2
 .data._gPcd_BinaryPatch_PcdPatchableInt32
                0x0000000000005380        0x4 /tmp/PCDLesson.dll.sOAh1G.ltrans0.ltrans.o
 .data.rel.ro   0x0000000000005384        0x0 PCDLesson.obj (symbol from plugin)

보다시피 PCD의 기본값은 0x5380 오프셋 아래에 있다.(해당 값은 0x5380 값이 아닌 다른 값이 나올 수도 있다. 원문에서는 0x55A0을 기준으로 기술하고 있다.

이제 앱 빌드DEBUG 폴더에서 GenPatchPcdTable을 실행하여 PCDLessonPatchPcdTalbe을 생성해보자.

./BaseTools/BinWrappers/PosixLike/GenPatchPcdTable \
  -m Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.map \
  -e Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.efi \
  -o Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLessonPatchPcdTable

생성된 PCDLessonPatchPcdTable 파일을 체크하면 다음과 같다.

$ cat Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLessonPatchPcdTable
PCD Name                       Offset    Section Name
PcdPatchableInt32              0x5380     .data

보다시피, 맵 파일에서 본 것과 동일한 오프셋을 가진다.

이제 *.efl 파일의 기본 PCD 값을 마지막으로 살펴보겠다. (오프셋 값에 주의하도록 하자.)

hexdump -s 0x5380 Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.efi -n 4
0005380 3131 3131
00055a4

PatchPcdValue Tool

PatchPcdValue 툴에 대한 도움말은 다음과 같다.

$ ./BaseTools/BinWrappers/PosixLike/PatchPcdValue -h
Usage: PatchPcdValue.py -f Offset -u Value -t Type [-s MaxSize] <input_file>

Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.

Options:
  -f PCDOFFSET, --offset=PCDOFFSET
                        Start offset to the image is used to store PCD value.
  -u PCDVALUE, --value=PCDVALUE
                        PCD value will be updated into the image.
  -t PCDTYPENAME, --type=PCDTYPENAME
                        The name of PCD data type may be one of VOID*,BOOLEAN,
                        UINT8, UINT16, UINT32, UINT64.
  -s PCDMAXSIZE, --maxsize=PCDMAXSIZE
                        Max size of data buffer is taken by PCD value.It must
                        be set when PCD type is VOID*.
  -v, --verbose         Run verbosely
  -d LOGLEVEL, --debug=LOGLEVEL
                        Run with debug information
  -q, --quiet           Run quietly
  -?                    show this help message and exit
  --version             show program's version number and exit
  -h, --help            show this help message and exit

이제, 이를 사용하여 *.elf 파일에서 PCD를 패치해본다.

./BaseTools/BinWrappers/PosixLike/PatchPcdValue \
  --offset=0x5380 \
  --value=0xDEADDEAD \
  --type=UINT32 \
  Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.efi

hexdump 값을 다시 찾아보면,

$ hexdump -s 0x55A0 Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/PCDLesson.efi -n 4
0005380 dead dead                              
0005384

바이너리에서 PCD를 성공적으로 변경할 수 있었다.

이제 수정된 PCDLesson.efi 파일을 UEFI 공유 디스크에 복사하여 OVMF에서 앱을 실행해보자.

FS0:\> PCDLesson.efi
...
PcdPatchableInt32=0xDEADDEAD
PcdPatchableInt32=0xDEADDEAD
PcdPatchableInt32=43
Status=Success
PcdPatchableInt32=44

Last updated