12. EFI 메모리 맵을 리눅스 커널 스타일로 바꾸기 EFI_SHELL_PARAMETERS_PROTOCOL
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h
Copy 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/reserved
4가지 type만 있다.
buildroot를 사용해서 EFI x86-64용 커널 이미지를 빌드해보겠다.
Copy cd ~
git clone https://github.com/buildroot/buildroot.git
cd buildroot
make pc_x86_64_efi_defconfig
make
이 커널을 부팅하려면 다음과 같은 명령어를 쓰면 된다.
Copy 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"
커널이 부팅된다면 다음과 같은 부트로그를 볼 수 있다.
Copy 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
와 같게 한다.
Copy 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
을 사용하려면 헤더 파일을 추가해야한다.
Copy #include <Protocol/ShellParameters.h>
그리고 애플리케이션 *.inf 파일에 GUID를 추가한다.
Copy [Protocols]
gEfiShellParametersProtocolGuid
다음 문제로 넘어가서, OS 메모리 유형 매핑을 위한 함수를 만든다.
Copy 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의 인접 영역을 붙이도록 프로그램을 수정해야한다.
Copy 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
옵션을 주고 프로그램을 실행하면 전과 같은 방식으로 출력이 된다.
Copy 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
옵션과 함께 실행 시키면 커널의 부트로그와 비슷한 메모리 맵이 출력되는 것을 볼 수 있다.
다음의 실제 커널 부트 로그와 비교해보자.
Copy 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