12. EFI 메모리 맵을 리눅스 커널 스타일로 바꾸기

EFI_SHELL_PARAMETERS_PROTOCOL

https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h

typedef struct _EFI_SHELL_PARAMETERS_PROTOCOL {
  ///
  /// Points to an Argc-element array of points to NULL-terminated strings containing
  /// the command-line parameters. The first entry in the array is always the full file
  /// path of the executable. Any quotation marks that were used to preserve
  /// whitespace have been removed.
  ///
  CHAR16 **Argv;

  ///
  /// The number of elements in the Argv array.
  ///
  UINTN Argc;

  ///
  /// The file handle for the standard input for this executable. This may be different
  /// from the ConInHandle in EFI_SYSTEM_TABLE.
  ///
  SHELL_FILE_HANDLE StdIn;

  ///
  /// The file handle for the standard output for this executable. This may be different
  /// from the ConOutHandle in EFI_SYSTEM_TABLE.
  ///
  SHELL_FILE_HANDLE StdOut;

  ///
  /// The file handle for the standard error output for this executable. This may be
  /// different from the StdErrHandle in EFI_SYSTEM_TABLE.
  ///
  SHELL_FILE_HANDLE StdErr;
} EFI_SHELL_PARAMETERS_PROTOCOL;

보다시피 이 프로토콜을 통해 프로그램에 전달된 명령줄 인수에 액세스할 수 있다. 이 프로토콜을 우리가 만든 MemoryInfo 프로그램에서 사용하자.

지난 Lesson에서 우리는 EFI 메모리 맵을 출력해보았다. 그런데 항목이 100개 이상이었다. 우리가 Linux 커널을 부팅할 때 현재 메모리 맵에 대한 정보를 볼 수 있지만 이 때는 길이가 훨씬 짧다.

이것은 두 가지 이유 때문에 그런데,

  • 커널은 EFI 메모리 type을 덜 세분화해서 구분한다. EfiReservedMemoryType/EfiLoaderCode/EfiLoaderData/... 등등 유형을 구분하지 않고 usable/ACPI NVS/ACPI data/reserved4가지 type만 있다.

  • 커널은 인접한 영역을 붙여버린다.

buildroot를 사용해서 EFI x86-64용 커널 이미지를 빌드해보겠다.

cd ~
git clone https://github.com/buildroot/buildroot.git
cd buildroot
make pc_x86_64_efi_defconfig
make

이 커널을 부팅하려면 다음과 같은 명령어를 쓰면 된다.

qemu-system-x86_64 -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
                   -drive format=raw,file=fat:rw:~/UEFI_disk \
                   -nographic \
                   -kernel ~/buildroot/output/images/bzImage \
                   -append "console=ttyS0"

커널이 부팅된다면 다음과 같은 부트로그를 볼 수 있다.

BIOS-provided physical RAM map:
BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
BIOS-e820: [mem 0x0000000000100000-0x00000000007fffff] usable
BIOS-e820: [mem 0x0000000000800000-0x0000000000807fff] ACPI NVS
BIOS-e820: [mem 0x0000000000808000-0x000000000080ffff] usable
BIOS-e820: [mem 0x0000000000810000-0x00000000008fffff] ACPI NVS
BIOS-e820: [mem 0x0000000000900000-0x00000000078eefff] usable
BIOS-e820: [mem 0x00000000078ef000-0x0000000007b6efff] reserved
BIOS-e820: [mem 0x0000000007b6f000-0x0000000007b7efff] ACPI data
BIOS-e820: [mem 0x0000000007b7f000-0x0000000007bfefff] ACPI NVS
BIOS-e820: [mem 0x0000000007bff000-0x0000000007ef3fff] usable
BIOS-e820: [mem 0x0000000007ef4000-0x0000000007f77fff] reserved
BIOS-e820: [mem 0x0000000007f78000-0x0000000007ffffff] ACPI NVS
BIOS-e820: [mem 0x00000000ffc00000-0x00000000ffffffff] reserved

이제 우리의 MemoryInfo프로그램을 수정해보자.

  • full이라는 옵션이 전달되면 현재와 같은 방법으로 출력한다.

  • 옵션이 아무것도 전달되지 않는 다면 리눅스 커널 스타일로 출력한다.

먼저 full이라는 부울 플래그를 생성한다. 인자 full이 우리 프로그램에 전달되면 플래그가 설정되고 그렇지 않다면 False와 같게 한다.

EFI_SHELL_PARAMETERS_PROTOCOL* ShellParameters;

Status = gBS->HandleProtocol(
  ImageHandle,
  &gEfiShellParametersProtocolGuid,
  (VOID **) &ShellParameters
);

BOOLEAN full=FALSE;
if (Status == EFI_SUCCESS) {
  if (ShellParameters->Argc == 2) {
    if (!StrCmp(ShellParameters->Argv[1], L"full")) {
      full=TRUE;
    }
  }
}

EFI_SHELL_PARAMETERS_PROTOCOL을 사용하려면 헤더 파일을 추가해야한다.

#include <Protocol/ShellParameters.h>

그리고 애플리케이션 *.inf 파일에 GUID를 추가한다.

[Protocols]
  gEfiShellParametersProtocolGuid

다음 문제로 넘어가서, OS 메모리 유형 매핑을 위한 함수를 만든다.

const CHAR16 *memory_types_OS_view[] = {
    L"reserved", // L"EfiReservedMemoryType",
    L"usable",   // L"EfiLoaderCode",
    L"usable",   // L"EfiLoaderData",
    L"usable",   // L"EfiBootServicesCode",
    L"usable",   // L"EfiBootServicesData",
    L"reserved", // L"EfiRuntimeServicesCode",
    L"reserved", // L"EfiRuntimeServicesData",
    L"usable",   // L"EfiConventionalMemory",
    L"reserved", // L"EfiUnusableMemory",
    L"ACPI data",// L"EfiACPIReclaimMemory",
    L"ACPI NVS", // L"EfiACPIMemoryNVS",
    L"reserved", // L"EfiMemoryMappedIO",
    L"reserved", // L"EfiMemoryMappedIOPortSpace",
    L"reserved", // L"EfiPalCode",
    L"usable",   // L"EfiPersistentMemory",
    L"usable",   // L"EfiMaxMemoryType"
};

const CHAR16 *
memory_type_to_str_OS_view(UINT32 type)
{
    if (type > sizeof(memory_types_OS_view)/sizeof(CHAR16 *))
        return L"Unknown";

    return memory_types_OS_view[type];
}

마지막으로 full플래그가 설정되지 않은 경우 동일한 type의 인접 영역을 붙이도록 프로그램을 수정해야한다.

EFI_MEMORY_DESCRIPTOR* desc = MemoryMap;
EFI_MEMORY_DESCRIPTOR* next_desc;
int i = 0;
while ((UINT8 *)desc < (UINT8 *)MemoryMap + MemoryMapSize) {
  UINTN PAGE_SIZE = 4096;
  UINTN mapping_size =(UINTN) desc->NumberOfPages * PAGE_SIZE;

  UINT64 Start = desc->PhysicalStart;

  next_desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)desc + DescriptorSize);
  if (!full) {
    while ((UINT8 *)next_desc < (UINT8 *)MemoryMap + MemoryMapSize) {
      mapping_size =(UINTN) desc->NumberOfPages * PAGE_SIZE;
      if ((desc->PhysicalStart + mapping_size) == (next_desc->PhysicalStart)) {

        if (desc->Type != next_desc->Type) {
          if (StrCmp(memory_type_to_str_OS_view(desc->Type),
                     memory_type_to_str_OS_view(next_desc->Type)))
            break;
          }

          desc=next_desc;
          next_desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)next_desc + DescriptorSize);
      } else {
          break;
      }
    }
  }

  if (full) {
    CHAR16 str[ATTRIBUTE_STR_SIZE];
    Print(L"[#%02d] Type: %s  Attr: %s\n", i++,
      memory_type_to_str(desc->Type), memory_attrs_to_str(str, desc->Attribute));
    Print(L"      Phys: %016llx-%016llx\n", Start, Start + mapping_size - 1);
  }
  else {
    Print(L" [mem: %016llx-%016llx] %s\n", Start, desc->PhysicalStart + mapping_size - 1,
      memory_type_to_str_OS_view(desc->Type) );
  }

  desc = next_desc;
}

프로그램을 빌드 후에 UEFI_disk 폴더에 복사하자. full옵션을 주고 프로그램을 실행하면 전과 같은 방식으로 출력이 된다.

FS0:\> MemoryInfo.efi full
[#00] Type: EfiBootServicesCode  Attr:  UC WC WT WB
      Phys: 0000000000000000-0000000000000FFF
[#01] Type: EfiConventionalMemory  Attr:  UC WC WT WB
      Phys: 0000000000001000-000000000009FFFF
[#02] Type: EfiConventionalMemory  Attr:  UC WC WT WB
      Phys: 0000000000100000-00000000007FFFFF
[#03] Type: EfiACPIMemoryNVS  Attr:  UC WC WT WB
      Phys: 0000000000800000-0000000000807FFF
[#04] Type: EfiConventionalMemory  Attr:  UC WC WT WB
      Phys: 0000000000808000-000000000080FFFF
[#05] Type: EfiACPIMemoryNVS  Attr:  UC WC WT WB
      Phys: 0000000000810000-00000000008FFFFF
...

하지만 full옵션과 함께 실행 시키면 커널의 부트로그와 비슷한 메모리 맵이 출력되는 것을 볼 수 있다.

다음의 실제 커널 부트 로그와 비교해보자.

BIOS-provided physical RAM map:
BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
BIOS-e820: [mem 0x0000000000100000-0x00000000007fffff] usable
BIOS-e820: [mem 0x0000000000800000-0x0000000000807fff] ACPI NVS
BIOS-e820: [mem 0x0000000000808000-0x000000000080ffff] usable
BIOS-e820: [mem 0x0000000000810000-0x00000000008fffff] ACPI NVS
BIOS-e820: [mem 0x0000000000900000-0x00000000078eefff] usable
BIOS-e820: [mem 0x00000000078ef000-0x0000000007b6efff] reserved
BIOS-e820: [mem 0x0000000007b6f000-0x0000000007b7efff] ACPI data
BIOS-e820: [mem 0x0000000007b7f000-0x0000000007bfefff] ACPI NVS
BIOS-e820: [mem 0x0000000007bff000-0x0000000007ef3fff] usable
BIOS-e820: [mem 0x0000000007ef4000-0x0000000007f77fff] reserved
BIOS-e820: [mem 0x0000000007f78000-0x0000000007ffffff] ACPI NVS
BIOS-e820: [mem 0x00000000ffc00000-0x00000000ffffffff] reserved

Last updated