💻
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. 부팅 옵션

15. gRT->GetVariable API를 사용하여 부팅 변수 가져오기 및 구문 분석

이번에는 UEFI에서 부팅 옵션을 정의하는 변수의 내용을 출력해보자.

이를 위해서는 EFI Runtime Services에서 제공하는 GetVariable()API 함수를 사용해야한다.

GetVariable()

Summary:
Returns the value of a variable.

Prototype:
typedef
EFI_STATUS
GetVariable (
 IN CHAR16 *VariableName,
 IN EFI_GUID *VendorGuid,
 OUT UINT32 *Attributes OPTIONAL,
 IN OUT UINTN *DataSize,
 OUT VOID *Data OPTIONAL
 );

Parameters:
VariableName    A Null-terminated string that is the name of the vendor’s variable.
VendorGuid      A unique identifier for the vendor
Attributes      If not NULL, a pointer to the memory location to return the
                attributes bitmask for the variable.
                If not NULL, then Attributes is set on output both when
                EFI_SUCCESS and when EFI_BUFFER_TOO_SMALL is returned.
DataSize        On input, the size in bytes of the return Data buffer.
                On output the size of data returned in Data.
Data            The buffer to return the contents of the variable. May be NULL
                with a zero DataSize in order to determine the size buffer needed.

Description:
Each vendor may create and manage its own variables without the risk of name conflicts by using a
unique VendorGuid. When a variable is set its Attributes are supplied to indicate how the data variable
should be stored and maintained by the system. The attributes affect when the variable may be accessed
and volatility of the data

If the Data buffer is too small to hold the contents of the variable, the error EFI_BUFFER_TOO_SMALL is
returned and DataSize is set to the required buffer size to obtain the data.

Status Codes Returned:
EFI_SUCCESS 		The function completed successfully.
EFI_NOT_FOUND 		The variable was not found.
EFI_BUFFER_TOO_SMALL 	The DataSize is too small for the result. DataSize has been
			updated with the size needed to complete the request. If
			Attributes is not NULL, then the attributes bitmask for the
			variable has been stored to the memory location pointed-to by
			Attributes.
...

이전 장에서 다음 옵션들이 GUID EFI_GLOBAL_VARIABLE(gEfiGlobalVariableGuid) 아래 환경에 있음을 발견했다.

Boot0000
Boot0001
Boot0002
Boot0003
Boot0004
BootCurrent
BootOrder

UEFI 스펙을 찾아보면 이러한 옵션들에 대한 보다 자세한 설명을 찾을 수 있다.

Each Boot#### variable contains an EFI_LOAD_OPTION. Each Boot#### variable is the name “Boot”
appended with a unique four digit hexadecimal number. For example, Boot0001, Boot0002, Boot0A02,
etc.

...

The BootOrder variable contains an array of UINT16’s that make up an ordered list of the Boot####
options. The first element in the array is the value for the first logical boot option, the second element is
the value for the second logical boot option, etc. The BootOrder order list is used by the firmware’s
boot manager as the default boot order.

...

The BootCurrent variable is a single UINT16 that defines the Boot#### option that was selected on
the current boot.

ShowBootVariables애플리케이션을 만들어보자.

먼저 간단한 UINT16 BootCurrent 옵션을 가져와 보는 것을 목표로 한다.

이 프로그램에서 변수를 많이 가져올 것이므로 GetVariable()함수를 호출할 때 필요한 사항을 추상화하여 함수를 만드는 것이 가장 좋은 방법이다.

EFI_STATUS
GetNvramVariable( CHAR16   *VariableName,
                  EFI_GUID *VariableOwnerGuid,
                  VOID     **Buffer,
                  UINTN    *BufferSize)
{
    UINTN Size = 0;
    *BufferSize = 0;

    EFI_STATUS Status = gRT->GetVariable(VariableName, VariableOwnerGuid, NULL, &Size, NULL);
    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error! 'gRT->GetVariable' call returned %r\n", Status);
        return Status;
    }

    *Buffer = AllocateZeroPool(Size);
    if (!Buffer) {
        Print(L"Error! 'AllocateZeroPool' call returned %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gRT->GetVariable(VariableName, VariableOwnerGuid, NULL, &Size, *Buffer);
    if (Status == EFI_SUCCESS) {
        *BufferSize = Size;
    } else {
        FreePool( *Buffer );
        *Buffer = NULL;
    }

    return Status;
}

다른 많은 UEFI API와 마찬가지로 첫 번째 GetVariable 호출은 EFI_BUFFER_TOO_SMALL 오류를 발생시키지만 우리가 할당해야 할 배열의 크기로 Size변수를 초기화 시키고 올바른 실행을 위해 함수에 전달한다.

이전 레슨에서 했던 것처럼 AllocateZeroPool 호출로 필요한 크기를 할당한다. 그런 다음 EFI_SUCCESS 상태를 기대하면서 두 번째로 GetVariable 함수를 호출한다.

애플리케이션의 메인함수는 다음과 같다.

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

EFI_STATUS
GetNvramVariable( CHAR16   *VariableName,
                  EFI_GUID *VariableOwnerGuid,
                  VOID     **Buffer,
                  UINTN    *BufferSize)
{
  ...
}

INTN EFIAPI ShellAppMain(IN UINTN Argc, IN CHAR16 **Argv)
{
  UINTN OptionSize;
  EFI_STATUS Status;

  UINT16* BootCurrent;
  Status = GetNvramVariable(L"BootCurrent", &gEfiGlobalVariableGuid, (VOID**)&BootCurrent, &OptionSize);
  if (Status == EFI_SUCCESS) {
    Print(L"BootCurrent=%d\n", *BootCurrent);
  } else {
    Print(L"Can't get BootCurrent variable\n");
  }

  return EFI_SUCCESS;
}

빌드 후 OVMF에서 실행시키면 다음과 같은 결과가 나올 것이다.

FS0:\> ShowBootVariables.efi
3

이것은 Boot0003이 활성 상태임을 의미한다.

이제 UINT16 BootOrder[]변수를 가져와보자. 이것은 부팅 옵션 순서를 나타내는 XXXX숫자의 배열이다. 예를 들어 {0,2,4,1,3}은 다음 순서를 의미한다.

Boot0000
Boot0002
Boot0004
Boot0001
Boot0003

다음 코드는 위 배열을 출력하기 위한 코드이다.

UINT16* BootOrderArray;
Status = GetNvramVariable(L"BootOrder", &gEfiGlobalVariableGuid, (VOID**)&BootOrderArray, &OptionSize);
if (Status == EFI_SUCCESS) {
  for (UINTN i=0; i<(OptionSize/sizeof(UINT16)); i++) {
    Print(L"Boot%04d%s\n", BootOrderArray[i], (BootOrderArray[i] == *BootCurrent)? L"*" : L"" );
  }
} else {
  Print(L"Can't get BootOrder variable\n");
}

OVMF에서 실행시켜 보면 다음 과 같은 결과가 나온다.

Boot0000
Boot0001
Boot0002
Boot0003*
Boot0004

이제 모든 Boot####옵션에 대한 정보를 출력해보자.

UEFI 스펙에 따르면 이러한 옵션은 EFI_LOAD_OPTION 구조체에 저장된다.

typedef struct _EFI_LOAD_OPTION {
 UINT32 Attributes;
 UINT16 FilePathListLength;
 // CHAR16 Description[];
 // EFI_DEVICE_PATH_PROTOCOL FilePathList[];
 // UINT8 OptionalData[];
} EFI_LOAD_OPTION;

Parameters
Attributes 		The attributes for this load option entry. All unused bits must be zero
			and are reserved by the UEFI specification for future growth.
FilePathListLength	Length in bytes of the FilePathList. OptionalData starts at
			offset sizeof(UINT32) + sizeof(UINT16) +
			StrSize(Description) + FilePathListLength of the
			EFI_LOAD_OPTION descriptor.
Description 		The user readable description for the load option. This field ends
			with a Null character.
FilePathList 		A packed array of UEFI device paths. The first element of the array is
			a device path that describes the device and location of the Image for
			this load option. The FilePathList[0] is specific to the device
			type. Other device paths may optionally exist in the FilePathList,
			but their usage is OSV specific. Each element in the array is variable
			length, and ends at the device path end structure. Because the size
			of Description is arbitrary, this data structure is not guaranteed
			to be aligned on a natural boundary. This data structure may have to
			be copied to an aligned natural boundary before it is used.
OptionalData 		The remaining bytes in the load option descriptor are a binary data
			buffer that is passed to the loaded image. If the field is zero bytes
			long, a NULL pointer is passed to the loaded image. The number of
			bytes in OptionalData can be computed by subtracting the
			starting offset of OptionalData from total size in bytes of the
			EFI_LOAD_OPTION.

EDKII에서는 다음의 파일에 정의되어 있다.

이 구조체의 일부 필드가 주석처리되어 있는 것에 주의해야한다.

// CHAR16 Description[];
// EFI_DEVICE_PATH_PROTOCOL FilePathList[];
// UINT8 OptionalData[];

이 필드들은 가변 크기 배열이기 때문에 동적으로 오프셋을 구해야 한다.

Boot 변수의 이름(예: "Boot0003")을 받아들이고 이에 대한 모든 필요한 정보를 출력하는 함수를 만든다.

VOID PrintBootOption(CHAR16* BootOptionName)
{
  UINTN OptionSize;
  UINT8* Buffer;

  EFI_STATUS Status = GetNvramVariable(BootOptionName, &gEfiGlobalVariableGuid, (VOID**)&Buffer, &OptionSize);
  if (Status == EFI_SUCCESS) {
      EFI_LOAD_OPTION* LoadOption = (EFI_LOAD_OPTION*) Buffer;
      CHAR16* Description = (CHAR16*)(Buffer + sizeof (EFI_LOAD_OPTION));
      UINTN DescriptionSize = StrSize(Description);

      Print(L"%s\n", Description);
      if (LoadOption->FilePathListLength != 0) {
        VOID* FilePathList = (UINT8 *)Description + DescriptionSize;
        CHAR16* DevPathString = ConvertDevicePathToText(FilePathList, TRUE, FALSE);
        Print(L"%s\n", DevPathString);
      }
  } else {
    Print(L"Can't get %s variable\n", BootOptionName);
  }
}

이 코드는 위에서 논의한 포인터 산술을 사용한다. Description 필드의 크기를 구하려면 Description 필드가 항상 Null로 끝나므로 StrSize(Description)를 호출하여 구하면 된다.

DevicePath를 문자열로 출력하기 위해 ConvertDevicePathToText 호출을 사용한다(ImageInfo 애플리케이션에서 사용함). 이를 사용하려면 프로그램에 #include <Library/DevicePathLib.h>를 추가해야 한다.

이제 BootOrder 번호에서 "BootXXXX" 변수를 구성해야 한다.

/**
  Produces a Null-terminated Unicode string in an output buffer based on a Null-terminated
  Unicode format string and variable argument list.
  This function is similar as snprintf_s defined in C11.

  ...

  @param  StartOfBuffer   A pointer to the output buffer for the produced Null-terminated
                          Unicode string.
  @param  BufferSize      The size, in bytes, of the output buffer specified by StartOfBuffer.
  @param  FormatString    A Null-terminated Unicode format string.
  @param  ...             Variable argument list whose contents are accessed based on the
                          format string specified by FormatString.
  @return The number of Unicode characters in the produced output buffer not including the
          Null-terminator.
**/

UINTN
EFIAPI
UnicodeSPrint (
  OUT CHAR16        *StartOfBuffer,
  IN  UINTN         BufferSize,
  IN  CONST CHAR16  *FormatString,
  ...
  );

동일한 파일에 ASCII를 위한 비슷한 함수도 있다.

UINTN
EFIAPI
AsciiSPrint (
  OUT CHAR8        *StartOfBuffer,
  IN  UINTN        BufferSize,
  IN  CONST CHAR8  *FormatString,
  ...
  );

이제 BootOrder 반복문 내부의 코드는 다음과 같다.

CHAR16 BootOptionStr[sizeof("Boot####")+1];
UnicodeSPrint(BootOptionStr, (sizeof("Boot####")+1)*sizeof(CHAR16), L"Boot%04x", BootOrderArray[i]);
Print(L"%s%s\n", BootOptionStr, (BootOrderArray[i] == *BootCurrent)? L"*" : L"" );
PrintBootOption(BootOptionStr);
Print(L"\n");

파일 시작 부분에 #include <Library/PrintLib.h>를 추가하는 것을 잊지 말자.

애플리케이션을 컴파일 후 OVMF로 실행하면 다음과 같은 결과가 나온다.

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
UEFI QEMU HARDDISK QM00001
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

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

Boot0004
UEFI PXEv4 (MAC:525400123456)
PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0)

우리는 현재 UEFI Shell로 부팅했으므로 BootCurrent가 정확하게 표시(*)된 것을 알 수 있다.

Previous부팅 옵션Next16. OVMF 이미지 내에 부팅 옵션 추가

Last updated 2 years ago

이를 위해 의 UnicodeSPrint 함수를 사용한다.

🖥️
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiSpec.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PrintLib.h