61.dmpstore 명령을 사용하여 변수를 파일에 저장/로드하기

dmpstore 변수 덤프에서 CRC32 체크섬을 재계산하는 애플리케이션 작성하기

UEFI Shell에는 UEFI 변수의 내용을 확인하는 데 도움이 되는 dmpstore 명령이 있다.

이 dmpstore 명령의 도움말을 보면 이 명령이 제공하는 유용한 기능을 하나 더 볼 수 있다. 이 명령을 사용하면 UEFI 변수를 파일에 저장하고 해당 파일에서 다시 로드할 수 있다.

FS0:\> dmpstore -?
...
DMPSTORE [-all | ([variable] [-guid guid])] [-s file]
DMPSTORE [-all | ([variable] [-guid guid])] [-l file]
...
  -s       - Saves variables to a file.
  -l       - Loads and sets variables from a file.
...

이 메커니즘을 사용하여 기존 UEFI 변수의 내용을 수정해보자. 이것은 디버그에 유용한 기능이 될 수 있다.

이전 레슨에서는 UEFI 부팅 변수의 내용을 기반으로 부팅 소스를 표시하는 ShowBootVariables.efi 애플리케이션을 만들었었다.

FS0:\> ShowBootVariables.efi
Boot0000
UiApp
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)

Boot0001
UEFI QEMU DVD-ROM QM00003
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

Boot0002*
EFI Internal Shell
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4F1C-AD65-E05268D0B4D1)

Boot0003
UEFI QEMU HARDDISK QM00001
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

이 애플리케이션에서 구문 분석된 변수 중 하나는 BootOrder 변수였다.

잊어버린 경우에는 아래의 개념을 보자.

BootOrder 변수는 Boot####옵션의 정렬된 목록을 구성하는 UINT16의 배열을 포함한다. 배열의 첫 번째 요소는 첫 번째 논리적 부팅 옵션의 값이고 두 번째 요소는 두 번째 논리적 부팅 옵션의 값 등이다. BootOrder 순서 목록은 펌웨어의 부팅 관리자를 기본 부팅 순서로 지정한다.

BootOrder 변수의 내용을 출력해보자.

FS0:\> dmpstore BootOrder
Variable NV+RT+BS 'EFIGlobalVariable:BootOrder' DataSize = 0x08
  00000000: 00 00 01 00 02 00 03 00-                         *........*

현재 순서는 다음과 같다.

Boot0000
Boot0001
Boot0002
Boot0003

모든 것이 ShowBootVariables 애플리케이션이 보여주는 것과 같다.

dmpstore 명령을 사용하여 BootOrder 변수의 내용을 파일로 덤프할 수 있다.

FS0:\> dmpstore BootOrder -s BootOrder.bin
Save variable to file: BootOrder.bin.
Variable NV+RT+BS '8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootOrder' DataSize = 0x08

UEFI Shell에는 자체적으로 hexedit 명령이 포함되어 있기 때문에 이를 통해 생성된 파일의 내용을 볼 수 있다.

hexedit은 16진수 편집기이며 Ctrl+E 명령으로 도움말 메시지를 볼 수 있다.

Ctrl-W로 도움말을 종료할 수 있다.

dmpstore 명령은 파일에서 다음 구조체로 각 변수를 나타낸다.

{
  UINT32 NameSize;           // Size of the variable name in bytes
  UINT32 DataSize;           // Size of the variable data in bytes
  CHAR16 Name[NameSize/2];   // Variable name in CHAR16
  EFI_GUID Guid;             // Variable GUID
  UINT32 Attributes;         // Variable attributes
  UINT8 Data[DataSize];      // Variable data
  UINT32 Crc;                // CRC32 checksum for the record
}

다음은 구조체 각각의 필드에 대한 강조 표시가 된 파일이다.

부팅 순서를 다음으로 변경하여 파일 내용을 수정해 보자.

Boot0001
Boot0000
Boot0002
Boot0003

종료하려면 Ctrl+Q를 입력하고 우리의 수정 사항을 저장하기 위해 y를 입력하자.

만약 변경된 설정을 로드하려고 하면 오류가 발생한다.

FS0:\> dmpstore -l BootOrder.bin
Load and set variables from file: BootOrder.bin.
dmpstore: Incorrect file format.
dmpstore: No matching variables found. Guid 8BE4DF61-93CA-11D2-AA0D-00E098032B8C

이는 레코드의 UINT32 Crc 필드가 현재 레코드 내용에 대해 더 이상 유효하지 않기 때문에 발생한다.

dmpstore 덤프에서 CRC 필드를 업데이트하기 위해 UpdateDmpstoreDump 애플리케이션을 만들어 보자.

다시 한 번 명령 Shell 인수를 파싱할 것이므로 Shell 애플리케이션을 만드는게 좋다. 우리는 파일을 읽고 쓸 것이므로 LibraryClasses에 ShellLib를 포함해준다.

UefiLessonsPkg/UpdateDmpstoreDump/UpdateDmpstoreDump.inf
[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = UpdateDmpstoreDump
  FILE_GUID                      = d14fe21b-7dbf-40ff-96cb-5d6f5b63cda6
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = ShellCEntryLib

[Sources]
  UpdateDmpstoreDump.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiLib
  ShellCEntryLib
  ShellLib

UefiLessonsPkg/UpdateDmpstoreDump/UpdateDmpstoreDump.c에서 명령 인수에서 덤프 파일 이름을 읽고 읽기 및 쓰기 속성으로 파일을 여는 것부터 시작한다.

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

VOID Usage()
{
  Print(L"Recalculate CRCs for dmpstore command dump\n");
  Print(L"\n");
  Print(L"  UpdateDmpstoreDump <filename>\n");
}

INTN EFIAPI ShellAppMain(IN UINTN Argc, IN CHAR16 **Argv)
{
  if (Argc!=2) {
    Usage();
    return EFI_INVALID_PARAMETER;
  }

  SHELL_FILE_HANDLE FileHandle;

  CHAR16* Filename = Argv[1];
  EFI_STATUS Status = ShellOpenFileByName(
    Filename,
    &FileHandle,
    EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
    0
  );
  if (EFI_ERROR(Status)) {
    Print(L"Error! Can't open file %s\n", Filename);
    return Status;
  }

  ...

  Status = ShellCloseFile(&FileHandle);
  if (EFI_ERROR(Status)) {
    Print(L"Can't close file: %r\n", Status);
  }

  return EFI_SUCCESS;
}

덤프 파일은 자체적으로 많은 레코드를 가질 수 있으며 레코드의 크기는 일정하지 않지만 레코드 필드에 따라 다르다. 따라서 모든 레코드 CRC를 수정하는 유일한 방법은 파일이 끝날 때까지 파일 레코드를 단계별로 실행하는 것이다.

  UINT64 FileSize;
  Status = ShellGetFileSize(FileHandle, &FileSize);
  if (EFI_ERROR(Status)) {
    Status = ShellCloseFile(&FileHandle);
    return SHELL_DEVICE_ERROR;
  }

  UINT64 FilePos = 0;
  while (FilePos < FileSize) {
    ...
  }

다음은 레코드 데이터를 읽고 CRC32를 계산하는 코드이다. dmpstore 명령에 LoadVariablesFromFileFunction이 있는 것과 매우 유사하다. (https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellDebug1CommandsLib/DmpStore.c)

UINTN ToReadSize;
UINT32 NameSize;
ToReadSize = sizeof(NameSize);
Status = ShellReadFile(FileHandle, &ToReadSize, &NameSize);
if (EFI_ERROR(Status) || (ToReadSize != sizeof(NameSize))) {
  Status = SHELL_VOLUME_CORRUPTED;
  break;
}
FilePos += ToReadSize;

UINT32 DataSize;
ToReadSize = sizeof(DataSize);
Status = ShellReadFile(FileHandle, &ToReadSize, &DataSize);
if (EFI_ERROR(Status) || (ToReadSize != sizeof(DataSize))) {
  Status = SHELL_VOLUME_CORRUPTED;
  break;
}
FilePos += ToReadSize;

UINTN RemainingSize = NameSize +
                      sizeof(EFI_GUID) +
                      sizeof(UINT32) +
                      DataSize;
UINT8* Buffer = AllocatePool(sizeof(NameSize) + sizeof(DataSize) + RemainingSize);
if (Buffer == NULL) {
  Status = SHELL_OUT_OF_RESOURCES;
  break;
}

*(UINT32*)Buffer = NameSize;
*((UINT32*)Buffer + 1) = DataSize;

ToReadSize = RemainingSize;
Status = ShellReadFile(FileHandle, &ToReadSize, (UINT32*)Buffer + 2);
if (EFI_ERROR(Status) || (ToReadSize != RemainingSize)) {
  Status = SHELL_VOLUME_CORRUPTED;
  FreePool (Buffer);
  break;
}
FilePos += ToReadSize;


UINT32 Crc32;
gBS->CalculateCrc32 (
   Buffer,
   sizeof(NameSize) + sizeof(DataSize) + RemainingSize,
   &Crc32
);

...

FreePool(Buffer);

여기서 CRC32 체크섬을 계산하기 위해 EFI_BOOT_SERVICES.CalculateCrc32() 함수를 사용한다.

EFI_BOOT_SERVICES.CalculateCrc32()

Summary:
Computes and returns a 32-bit CRC for a data buffer.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_CALCULATE_CRC32)
 IN VOID *Data,
 IN UINTN DataSize,
 OUT UINT32 *Crc32
 );

Parameters:
Data 		A pointer to the buffer on which the 32-bit CRC is to be computed.
DataSize 	The number of bytes in the buffer Data.
Crc32 		The 32-bit CRC that was computed for the data buffer specified by Data and DataSize.

Description:
This function computes the 32-bit CRC for the data buffer specified by Data and DataSize. If the 32-bit CRC is computed, then it is returned in Crc32 and EFI_SUCCESS is returned.

CRC32 체크섬이 있으면 ShellWriteFile 함수를 사용하여 파일 내용을 업데이트할 수 있다.

UINTN ToWriteSize = sizeof(Crc32);
Status = ShellWriteFile(
  FileHandle,
  &ToWriteSize,
  &Crc32
);
if (EFI_ERROR(Status) || (ToWriteSize != sizeof(Crc32))) {
  Print(L"Error! Not all data was written\n");
  FreePool(Buffer);
  break;
}
FilePos += ToWriteSize;

애플리케이션을 빌드하고 dmpstore 덤프에 사용해보자.

FS0:\> UpdateDmpstoreDump.efi BootOrder.bin

파일 내용을 다시 보면 CRC 필드가 변경되었음을 알 수 있다.

이제 dmpstore -l은 오류 없이 완료되는 것을 확인할 수 있다.

FS0:\> dmpstore -l BootOrder.bin
Load and set variables from file: BootOrder.bin.
Variable NV+RT+BS '8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootOrder' DataSize = 0x08

또한 변수 내용이 수정된 것을 볼 수 있다.

FS0:\> dmpstore BootOrder
Variable NV+RT+BS 'EFIGlobalVariable:BootOrder' DataSize = 0x08
  00000000: 01 00 00 00 02 00 03 00-                         *........*

ShowBootVariables.efi 애플리케이션을 사용하여 변경 사항을 확인할 수도 있다.

FS0:\> ShowBootVariables.efi
Boot0001
UEFI QEMU DVD-ROM QM00003
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

Boot0000
UiApp
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)

Boot0002*
EFI Internal Shell
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4F1C-AD65-E05268D0B4D1)

Boot0003
UEFI QEMU HARDDISK QM00001
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

덤프 파일에 여러 변수가 있는 경우 프로그램이 작동하는지 확인할 수도 있다.

이전 레슨에 따라 일부 영구 변수가 있는 경우에 대비하여 dmpstore -d -guid 명령을 사용하여 GUID 아래의 모든 변수를 삭제해준다.

FS0:\> dmpstore -d -guid bb2a829f-7943-4691-a03a-f1f48519d7e6
dmpstore: No matching variables found. Guid 7C04A583-9E3E-4F1C-AD65-E05268D0B4D1

새 변수를 생성하고 파일에 저장한다.

FS0:\> SetVariableExample.efi HelloVar nb "Hello World"
Variable HelloVar was successfully changed
FS0:\> SetVariableExample.efi ByeVar nbr "Bye World"
Variable ByeVar was successfully changed
FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6 -s MyVar.bin
Save variable to file: MyVar.bin.
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x16
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x1A

hexedit를 사용하여 덤프 파일의 두 레코드에서 World 문자열을 수정해준다. 여기에서는 각 문자 코드에 1을 더했다.

전:

00000000 0E 00 00 00 14 00 00 00  42 00 79 00 65 00 56 00  ........B.y.e.V.
00000010 61 00 72 00 00 00 9F 82  2A BB 43 79 91 46 A0 3A  a.r...??*?Cy?F?:
00000020 F1 F4 85 19 D7 E6 07 00  00 00 42 00 79 00 65 00  ???.??....B.y.e.
00000030 20 00 57 00 6F 00 72 00  6C 00 64 00 00 00 EC 24   .W.o.r.l.d...?$
00000040 78 CD 12 00 00 00 18 00  00 00 48 00 65 00 6C 00  x?........H.e.l.
00000050 6C 00 6F 00 56 00 61 00  72 00 00 00 9F 82 2A BB  l.o.V.a.r...??*?
00000060 43 79 91 46 A0 3A F1 F4  85 19 D7 E6 03 00 00 00  Cy?F?:???.??....
00000070 48 00 65 00 6C 00 6C 00  6F 00 20 00 57 00 6F 00  H.e.l.l.o. .W.o.
00000080 72 00 6C 00 64 00 00 00  97 82 10 13              r.l.d...??..

후:

00000000 0E 00 00 00 14 00 00 00  42 00 79 00 65 00 56 00  ........B.y.e.V.
00000010 61 00 72 00 00 00 9F 82  2A BB 43 79 91 46 A0 3A  a.r...??*?Cy?F?:
00000020 F1 F4 85 19 D7 E6 07 00  00 00 42 00 79 00 65 00  ???.??....B.y.e.
00000030 20 00 58 00 70 00 73 00  6D 00 65 00 00 00 EC 24   .X.p.s.m.e...?$
00000040 78 CD 12 00 00 00 18 00  00 00 48 00 65 00 6C 00  x?........H.e.l.
00000050 6C 00 6F 00 56 00 61 00  72 00 00 00 9F 82 2A BB  l.o.V.a.r...??*?
00000060 43 79 91 46 A0 3A F1 F4  85 19 D7 E6 03 00 00 00  Cy?F?:???.??....
00000070 48 00 65 00 6C 00 6C 00  6F 00 20 00 58 00 70 00  H.e.l.l.o. .X.p.
00000080 73 00 6D 00 65 00 00 00  97 82 10 13              s.m.e...??..

우리의 프로그램을 사용해서 체크섬을 업데이트해준다.

FS0:\> UpdateDmpstoreDump.efi MyVar.bin

이제 새 덤프가 실제로 두 변수 콘텐츠 내용을 모두 변경했는지 확인할 수 있다.

FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x14
  00000000: 42 00 79 00 65 00 20 00-57 00 6F 00 72 00 6C 00  *B.y.e. .W.o.r.l.*
  00000010: 64 00 00 00                                      *d...*
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x18
  00000000: 48 00 65 00 6C 00 6C 00-6F 00 20 00 57 00 6F 00  *H.e.l.l.o. .W.o.*
  00000010: 72 00 6C 00 64 00 00 00-                         *r.l.d...*

FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6 -l MyVar.bin
Load and set variables from file: MyVar.bin.
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x14
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x18

FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x18
  00000000: 48 00 65 00 6C 00 6C 00-6F 00 20 00 58 00 70 00  *H.e.l.l.o. .X.p.*
  00000010: 73 00 6D 00 65 00 00 00-                         *s.m.e...*
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x14
  00000000: 42 00 79 00 65 00 20 00-58 00 70 00 73 00 6D 00  *B.y.e. .X.p.s.m.*
  00000010: 65 00 00 00                                      *e...*

여기서는 해당이 안되지만 데이터의 크기를 변경하면 UINT32 DataSize 필드도 변경해줘야 된다는 것을 명심해야 한다.

Last updated