💻
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. 메모리 맵

11. EFI 메모리 맵 정보 얻기

주요 BIOS/UEFI 작업 중 하나는 OS 메모리 맵을 표시하는 것이다. 운영 체제는 시스템에서 사용 가능한 RAM의 크기와 OS에서 사용할 수 있는 영역 및 사용할 수 없는 영역을 알아야 한다.

이를 위해 UEFI 스펙에서는 EFI_BOOT_SERVICES.GetMemoryMap() 함수를 정의해 주었다.

EFI_BOOT_SERVICES.GetMemoryMap()

Summary:
Returns the current memory map.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_GET_MEMORY_MAP) (
 IN OUT UINTN *MemoryMapSize,
 IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
 OUT UINTN *MapKey,
 OUT UINTN *DescriptorSize,
 OUT UINT32 *DescriptorVersion
 );

Parameters:
MemoryMapSize 		A pointer to the size, in bytes, of the MemoryMap buffer. On input,
			this is the size of the buffer allocated by the caller. On output, it is
			the size of the buffer returned by the firmware if the buffer was
			large enough, or the size of the buffer needed to contain the map if
			the buffer was too small.
MemoryMap		A pointer to the buffer in which firmware places the current memory
			map. The map is an array of EFI_MEMORY_DESCRIPTORs.
MapKey 			A pointer to the location in which firmware returns the key for the
			current memory map.
DescriptorSize 		A pointer to the location in which firmware returns the size, in bytes,
			of an individual EFI_MEMORY_DESCRIPTOR.
DescriptorVersion 	A pointer to the location in which firmware returns the version
			number associated with the EFI_MEMORY_DESCRIPTOR.


Status Codes Returned:
EFI_SUCCESS 		The memory map was returned in the MemoryMap buffer.
EFI_BUFFER_TOO_SMALL 	The MemoryMap buffer was too small. The current buffer size needed to
			hold the memory map is returned in MemoryMapSize.
EFI_INVALID_PARAMETER 	MemoryMapSize is NULL.
EFI_INVALID_PARAMETER 	The MemoryMap buffer is not too small and MemoryMap is NULL.
//*******************************************************
//EFI_MEMORY_DESCRIPTOR
//*******************************************************
typedef struct {
 UINT32 Type;
 EFI_PHYSICAL_ADDRESS PhysicalStart;
 EFI_VIRTUAL_ADDRESS VirtualStart;
 UINT64 NumberOfPages;
 UINT64 Attribute;
} EFI_MEMORY_DESCRIPTOR;

Type 		Type of the memory region.
		Type EFI_MEMORY_TYPE is defined in the AllocatePages()
		function description.
PhysicalStart 	Physical address of the first byte in the memory region.
		PhysicalStart must be aligned on a 4 KiB boundary, and must
		not be above 0xfffffffffffff000. Type EFI_PHYSICAL_ADDRESS is
		defined in the AllocatePages() function description.
VirtualStart 	Virtual address of the first byte in the memory region.
		VirtualStart must be aligned on a 4 KiB boundary, and must not
		be above 0xfffffffffffff000.
NumberOfPages 	Number of 4 KiB pages in the memory region.
		NumberOfPages must not be 0, and must not be any value that
		would represent a memory page with a start address, either physical
		or virtual, above 0xfffffffffffff000
Attribute 	Attributes of the memory region that describe the bit mask of
		capabilities for that memory region, and not necessarily the current
		settings for that memory region.

EFI_BOOT_SERVICES.AllocatePages()는 스펙에 따라 4KB 페이지를 할당한다.

아래는 이번 애플리케이션의 코드이다.

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  UINTN MemoryMapSize = 0;
  EFI_MEMORY_DESCRIPTOR* MemoryMap = NULL;
  UINTN MapKey;
  UINTN DescriptorSize;
  UINT32 DescriptorVersion;

  EFI_STATUS Status;
  Status = gBS->GetMemoryMap(
        &MemoryMapSize,
        MemoryMap,
        &MapKey,
        &DescriptorSize,
        &DescriptorVersion
  );

  if (Status == EFI_BUFFER_TOO_SMALL) {
    Status = gBS->AllocatePool(
          EfiBootServicesData,
          MemoryMapSize,
          (void**)&MemoryMap
    );

    if (EFI_ERROR(Status)) {
      Print(L"AllocatePool error: %r\n", Status);
      return Status;
    }

    Status = gBS->GetMemoryMap(
          &MemoryMapSize,
          MemoryMap,
          &MapKey,
          &DescriptorSize,
          &DescriptorVersion
    );

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


        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", desc->PhysicalStart, desc->PhysicalStart + mapping_size - 1);

        desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)desc + DescriptorSize);
      }

      gBS->FreePool(MemoryMap);
    } else {
      Print(L"GetMemoryMap with buffer error: %r\n", Status);
    }
  } else {
    Print(L"GetMemoryMap without buffer error: %r\n", Status);
  }

  return Status;
}

첫 번째 GetMemoryMap()함수의 호출은 MemoryMapSize=0이 모든 메모리 디스크립터를 저장하기 충분하지 않기 때문에 EFI_BUFFER_TOO_SMALL과 함께 실패할 것이다.

하지만 이 첫 번째 호출로 인해서 할당해야 하는 실제 크기로 MemoryMapSize를 채워준다.

그래서 바로 다음에 gBS->AllocatePool를 호출하여 필요한 크기를 할당한다.

EFI_BOOT_SERVICES.AllocatePool()

Summary:
Allocates pool memory

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_ALLOCATE_POOL) (
 IN EFI_MEMORY_TYPE PoolType,
 IN UINTN Size,
 OUT VOID **Buffer
 );

Parameters:
PoolType 	The type of pool to allocate. Type EFI_MEMORY_TYPE is defined in
		the EFI_BOOT_SERVICES.AllocatePages() function description.
Size 		The number of bytes to allocate from the pool.
Buffer 		A pointer to a pointer to the allocated buffer if the call succeeds;
		undefined otherwise.

Description:
The AllocatePool() function allocates a memory region of Size bytes from memory of type PoolType
and returns the address of the allocated memory in the location referenced by Buffer.

Status Codes Returned:
EFI_SUCCESS 		The requested number of bytes was allocated.
EFI_OUT_OF_RESOURCES 	The pool requested could not be allocated.
EFI_INVALID_PARAMETER 	PoolType is in the range EfiMaxMemoryType..0x6FFFFFFF.
EFI_INVALID_PARAMETER 	PoolType is EfiPersistentMemory.
EFI_INVALID_PARAMETER 	Buffer is NULL

아까 작성한 코드를 보면 AllocatePool()의 첫번째 인자인 EFI_MEMORY_TYPE으로 우리는 BootServices에 연결되고 OS가 실행을 시작한 후 해제될 데이터를 나타내는 EfiBootServicesData를 전달한다.

메모리가 성공적으로 할당되었으면 마지막에 gBS->FreePool 호출로 할당을 해제해야 한다.

EFI_BOOT_SERVICES.FreePool()

Summary:
Returns pool memory to the system.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_FREE_POOL) (
IN VOID *Buffer
);

Parameters:
Buffer 	Pointer to the buffer to free.

Description:
The FreePool() function returns the memory specified by Buffer to the system. The Buffer that is freed must have been allocated by AllocatePool().

Status Codes Returned:
EFI_SUCCESS 		The memory was returned to the system.
EFI_INVALID_PARAMETER 	Buffer was invalid. 

GetMemoryMap 함수에 대한 두 번째 호출이 성공하면 MemoryMap 주소에서 시작하는 EFI_MEMORY_DESCRIPTOR 개체 배열을 가져온다.

우리는 이 배열을 살펴보고 각각의 디스크립터에 대한 정보를 출력하려고 한다.

메모리 type 값을 문자열로 변환하기 위해 memory_type_to_str 도우미 함수를 만든다.

const CHAR16 *memory_types[] = {
    L"EfiReservedMemoryType",
    L"EfiLoaderCode",
    L"EfiLoaderData",
    L"EfiBootServicesCode",
    L"EfiBootServicesData",
    L"EfiRuntimeServicesCode",
    L"EfiRuntimeServicesData",
    L"EfiConventionalMemory",
    L"EfiUnusableMemory",
    L"EfiACPIReclaimMemory",
    L"EfiACPIMemoryNVS",
    L"EfiMemoryMappedIO",
    L"EfiMemoryMappedIOPortSpace",
    L"EfiPalCode",
    L"EfiPersistentMemory",
    L"EfiMaxMemoryType"
};

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

    return memory_types[type];
}

또한 메모리 속성을 문자열로 변환하기 위해 memory_attrs_to_str 도우미 함수를 만든다.

매크로는 일반적으로 오류가 발생하기 쉬운 디자인으로 간주되기 때문에 이 매크로가 좋은 방법은 아니지만 EDKII 코드 베이스에서 EFI_MEMORY_XXX로 정의된 가능한 모든 특성을 잇는 쉬운 방법을 제공한다.

#define ATTRIBUTE_STR_SIZE 50

#define CHECK_EFI_MEMORY_ATTRIBUTE(attr) if (attrs & EFI_MEMORY_##attr) { \
                                           StrCpyS(&str[i], ATTRIBUTE_STR_SIZE, L" "#attr); \
                                           i+=StrLen(L" "#attr); \
                                         }

const CHAR16 *
memory_attrs_to_str(CHAR16* str, UINT64 attrs)
{
  int i=0;
  SetMem((VOID *)str, sizeof(str), 0);

  CHECK_EFI_MEMORY_ATTRIBUTE(UC)
  CHECK_EFI_MEMORY_ATTRIBUTE(WC)
  CHECK_EFI_MEMORY_ATTRIBUTE(WT)
  CHECK_EFI_MEMORY_ATTRIBUTE(WB)
  CHECK_EFI_MEMORY_ATTRIBUTE(UCE)
  CHECK_EFI_MEMORY_ATTRIBUTE(WP)
  CHECK_EFI_MEMORY_ATTRIBUTE(RP)
  CHECK_EFI_MEMORY_ATTRIBUTE(XP)
  CHECK_EFI_MEMORY_ATTRIBUTE(NV)
  CHECK_EFI_MEMORY_ATTRIBUTE(MORE_RELIABLE)
  CHECK_EFI_MEMORY_ATTRIBUTE(RO)
  CHECK_EFI_MEMORY_ATTRIBUTE(SP)
  CHECK_EFI_MEMORY_ATTRIBUTE(CPU_CRYPTO)
  CHECK_EFI_MEMORY_ATTRIBUTE(RUNTIME)

  return str;
}

이 매크로에서는 일부 EDKII 문자열 조작 함수를 사용했다.

이러한 문자열 함수를 쓸 경우 헤더 파일을 하나 더 추가해야 한다.

#include <Library/BaseMemoryLib.h>

OVMF에서 앱을 빌드하고 실행하면 다음과 같은 내용이 표시된다.

efi Shell에서 memmap명령어를 실행하여 결과가 맞는지 확인이 가능하다.

보다시피 메모리 구역이 우리가 만든 프로그램과 일치하는 것을 알 수 있다.

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

Last updated 2 years ago

EFI_MEMORY_TYPE에 대한 다른 가능한 값은 UEFI 스펙 문서나 여기에서 찾을 수 있다. (

StrCpyS: ( 에 따르면 StrCpy는 더 이상 사용되지 않는 API이므로 StrCpyS로 교체해야한다.)

StrLen: 이 함수는 표준 C 라이브러리의 함수와 유사한 함수이다.

🖥️
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiMultiPhase.h)
https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/SafeString.c
https://edk2-docs.gitbook.io/edk-ii-secure-coding-guide/secure_coding_guidelines_general
https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/String.c
MemoryInfo.efi
memmap result