💻
UEFI 프로젝트
  • 🧑‍🏫프로젝트 개요
  • 📖UEFI 개념
    • 1. BIOS의 과거
    • 2. UEFI 개념
    • 3. BIOS vs UEFI
    • 4. UEFI 부팅 단계
  • 🖥️UEFI 개발
    • UEFI 개발 시작하기
      • 0. EDK II 빌드 환경 구성
      • 1. 간단한 EFI application 만들기
      • 2. 간단한 Pkg 만들기
      • 3. Hello World 출력하기
      • 4. 라이브러리와 Hello World
      • 5. Conf를 통한 Build 단순화
    • 핸들 및 프로토콜
      • 6. 핸들/프로토콜 데이터 베이스 구조 - Part 1
      • 7. 핸들/프로토콜 데이터 베이스 구조 - Part 2
      • 8. HandleProtocol API 함수 & ImageHandle 프로토콜을 통한 정보
      • 9. ProtocolsPerHandle API를 통한 ImageHandle 프로토콜 가져오기
      • 10. EFI_STATUS 타입 과 EFI_ERROR 매크로
    • 메모리 맵
      • 11. EFI 메모리 맵 정보 얻기
      • 12. EFI 메모리 맵을 리눅스 커널 스타일로 바꾸기
    • 명령줄 인수를 받는 간단한 앱 만들기
      • 13.ShellAppMain Entry point
      • 14.gRT->GetNextVariableName API를 사용하여 모든 변수 이름 및 GUID 가져오기
    • 부팅 옵션
      • 15. gRT->GetVariable API를 사용하여 부팅 변수 가져오기 및 구문 분석
      • 16. OVMF 이미지 내에 부팅 옵션 추가
      • 17. 부팅 옵션에 WaitForEvent 함수 추가
      • 18. ReadKeyStroke 함수로 사용자 입력 처리
      • 19. bcfg 명령어를 사용한 부팅 옵션 수정
    • PCD
      • 20. PCD 소개
      • 21. PCD 변수에 대한 Overriding
      • 22. Feature Flag PCD와 BOOLEAN FixedAtBuild PCD의 비교
      • 23. PatchableInModule PCD 및 GenPatchPcdTable/PatchPcdValue 유틸리티를 통해 PCD를 변경하는 방법
      • 24. Dynamic/DynamiEx PCDs
      • 25. PCD 더 알아보기
    • 테이블
      • 26. EFI_CONFIGURATION_TABLE에서 참조되는 테이블
      • 27. dmem/EFI_SMBIOS_PROTOCOL/smbiosview를 통해서 SMBIOS 정보 가져오기
      • 28. EFI_SHELL_PROTOCOL을 통하여 ACPI 테이블을 파일에 저장하기
      • 29. EFI_ACPI_SDT_PROTOCOL 및 ShellLib를 사용하여 ACPI BGRT 테이블에서 BMP 이미지 저장하기
    • PCI
      • 30. PCI 루트 브리지 찾은 후 시스템의 모든 PCI 기능 가져오기
      • 31. ShellLib/PrintLib 함수를 사용해 PCI Vendor/Device 정보 가져오기
      • 32. EFI_PCI_IO_PROTOCOL을 사용해 PCI Option ROM 이미지 표시
      • 33. EfiRom 유틸리티를 사용한 PCI Option ROM 이미지 파싱 및 생성
    • 드라이버 및 라이브러리
      • 34. 간단한 UEFI 드라이버 생성
      • 35. 애플리케이션에서 사용할 간단한 라이브러리 생성
      • 36. Library의 constructor와 destructor, NULL Library
      • 37. Shell에 acpiview 명령을 추가하는 방법 조사
      • 38. 사용자 지정 프로토콜을 만들고 사용하기
      • 39. RegisterKeyNotify / UnrigisterKeyNotify 함수를 사용해 단축키 기능을 추가하는 드라이버 만들기
      • 40. Key #### NVRAM 변수
    • 디버그
      • 41. DEBUG 출력문 내부 구조와 DEBUG 문 제어를 위한 PCD 분석, 그리고 OVMF 부트 로그 가져오기
      • 42. GDB를 이용한 Driver/Application 및 OVMF Debug
    • HII
      • 43. HII 데이터베이스 개념 및 출력
      • 44. HII 데이터베이스 내부
      • 45. EFI_HII_DATABASE_PROTOCOL의 NewPackageList를 사용하여 문자열 패키지가 포함된 문자열 목록 게시
      • 46. EFI_HII_DATABASE_PROTOCOL의 NewPackageList를 사용하여 문자열 패키지가 포함된 HII 패키지 목록 게시
      • 47. EFI_HII_DATABASE_PROTOCOL의 NewPackageList를 사용하여 문자열 패키지가 포함된 HII 패키지 목록 게시
      • 48. UNI 파일 및 HiiLib를 사용하여 HII String 패키지 게시 및 작업하기
      • 49.MODULE_UNI_FILE/PACKAGE_UNI_FILE/[UserExtensions.TianoCore."ExtraFiles"]의 도움으로 UNI 파일 선언하기
      • 50.UEFI_HII_RESOURCE_SECTION을 사용하여 문자열 패키지와 함께 HII 패키지 목록 게시하기
      • 51. UEFI APP에 메뉴얼 추가하기(shell의 -?와 help 옵션)
      • 52. Russian 글꼴 추가 - Part 1.
      • 53. Russian 글꼴 추가 - Part 2.
      • 54. EFI_HII_STRING_PROTOCOL의 NewString 및 SetString 함수를 사용하여 다른 언어에 대한 문자열 패키지를 동적으로 추가
      • 55. PlatformLangCodes EFI 변수 수정 및 다른 언어를 동적 추가하기
      • 56. 코드에서 FILE_GUID 및 BASE_NAME을 가져오기
    • VFR
      • 57. VFR을 사용해 간단한 폼 생성 및 EFI_FORM_BROWSER2_PROTOCOL.SendForm()를 통해 화면에 폼 표시하기
      • 58. VFR 요소 : subtitle 및 text
      • 59. 간단한 폼 애플리케이션을 UEFI 드라이버 Form으로 변환하기
      • 60. gRT->SetVariable() 함수를 사용한 UEFI 변수 생성, 변경 및 삭제
      • 61.dmpstore 명령을 사용하여 변수를 파일에 저장/로드하기
      • 62. UEFI Device path의 구조
      • 63. checkbox를 가진 HII 폼 만들기
      • 64. checkbox를 가진 HII폼 만들기
      • 65. VFR 추가 입력 요소 Part 1: number
      • 66. VFR 추가 입력 요소 Part 2: string
      • 67. VFR 추가 입력 요소 Part 3: date & time
      • 68. VFR 추가 입력 요소 Part 3: oneof & orderedlist
      • 69. VFR의 조건부 키워드
      • 70. VFR의 상수 및 연산자가 내장된 기본 조건문
      • 71. 기본 VFR 내장 문자열용 함수
      • 72. label 키워드를 이용하여 HII 양식에 동적 요소 추가하기
      • 73. VFR question 기본값 설정
  • 🔐UEFI 보안
    • 1. 개요
    • 2. 공격 벡터
    • 3. mitigation
    • 4. 정적 분석 방법
    • 5. 동적 분석 방법
Powered by GitBook
On this page
  1. UEFI 개발
  2. 메모리 맵

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

EFI_SHELL_PARAMETERS_PROTOCOL

Previous11. EFI 메모리 맵 정보 얻기Next명령줄 인수를 받는 간단한 앱 만들기

Last updated 2 years ago

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
🖥️
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h