💻
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. 드라이버 및 라이브러리

34. 간단한 UEFI 드라이버 생성

이번 장에서는 UEFI 드라이버를 생성한다.

지금까지는 UEFI 애플리케이션만 만들었다. 애플리케이션과 드라이버의 주요 차이점은 애플리케이션이 실행된 후 메모리에 언로드된다는 점이다. 하지만 드라이버는 메모리에 남아있다. 또한 다른 애플리케이션이 사용할 유용한 프로토콜을 제공하기도 한다.

가장 간단한 드라이버로 UefiLessonsPkg/SimpleDriver/SimpleDriver.inf를 생성한다.

[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = SimpleDriver
  FILE_GUID                      = 384aeb18-105d-4af1-bf17-5e349e8f4d4c
  MODULE_TYPE                    = UEFI_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = SimpleDriverEntryPoint

[Sources]
  SimpleDriver.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiDriverEntryPoint
  UefiLib

기존에 애플리케이션을 생성할 때 사용했던 application.inf 파일과 드라이버를 생성할 때 사용하는 driver.inf 파일의 차이는 아래와 같다.

  • MODULE_TYPE은 UEFI_DRIVER이다. (이전에는 항상 UEFI_APPLICATION을 사용했다.

  • ENTRY_POINT는 SimpleDriverEntryPoint이다. (이전에는 항상 UefiMain을 사용했지만, 드라이버는 Entry/Unload 함수로 작동하므로 적절한 이름을 지정한다.)

이제 UefiLessonsPkg/SimpleDriver/SimpleDriver.c 소스 파일을 생성한다. 구현해야 할 유일한 함수는 entry 함수인 SimpleDriverEntryPoint 이다.

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

EFI_STATUS
EFIAPI
SimpleDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"Hello from driver!\n");

  return EFI_SUCCESS;
}

드라이버를 생성하기 위해 UefiLessonsPkg/UefiLessonsPkg.dsc의 Components에 추가한다.

[Components]
  ...
  UefiLessonsPkg/SimpleDriver/SimpleDriver.inf

아직은빌드를 시도하면 아래와 같은 이유로 실패한다.

build.py...
/home/aladyshev/tiano/edk2/UefiLessonsPkg/UefiLessonsPkg.dsc(...): error 4000: Instance of library class [UefiDriverEntryPoint] is not found
        in [/home/aladyshev/tiano/edk2/UefiLessonsPkg/SimpleDriver/SimpleDriver.inf] [X64]
        consumed by module [/home/aladyshev/tiano/edk2/UefiLessonsPkg/SimpleDriver/SimpleDriver.inf]

애플리케이션을 만들 때와 마찬가지로 필요한 라이브러리를 찾아야 한다. 2개의 결과가 나오지만 필요한 라이브러리는 MdePkg의 것이므로 이것을 사용한다.

$ grep UefiDriverEntryPoint -r ./ --exclude-dir=Build | grep LIBRARY_CLASS
./MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf:  LIBRARY_CLASS                  = UefiDriverEntryPoint|DXE_DRIVER DXE_RUNTIME_DRIVER UEFI_DRIVER SMM_CORE DXE_SMM_DRIVER

확인한 UefiDriverEntryPoint.int 파일의 경로를 UefiLessonsPkg/UefiLessonsPkg.dsc에 추가한다.

[LibraryClasses]
  ...
  UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf

빌드 후 SimpleDriver.efi를 실행하면 아래와 같이 애플리케이션이 아니라는 메시지를 확인할 수 있다.

FS0:\> SimpleDriver.efi
The image is not an application.

위 메시지를 통해서 알 수 있듯이 드라이버는 실행이 불가능하다. 그래서 드라이버를 로드하기 위해서 Shell에서 load명령어를 사용해야 한다.

FS0:\> load -? -b
Loads a UEFI driver into memory.

LOAD [-nc] file [file...]

  -nc  - Loads the driver, but does not connect the driver.
  File - Specifies a file that contains the image of the UEFI driver (wildcards are
         permitted).

NOTES:
  1. This command loads a driver into memory. It can load multiple files at
     one time. The file name supports wildcards.
  2. If the -nc flag is not specified, this command attempts to connect the
     driver to a proper device. It might also cause previously loaded drivers
     to be connected to their corresponding devices.
  3. Use the 'UNLOAD' command to unload a driver.

EXAMPLES:
  * To load a driver:
    fs0:\> load Isabus.efi

  * To load multiple drivers:
    fs0:\> load Isabus.efi IsaSerial.efi

  * To load multiple drivers using file name wildcards:
    fs0:\> load Isa*.efi

  * To load a driver without connecting it to a device:
    fs0:\> load -nc IsaBus.efi

load 명령어를 사용해 드라이버를 로드한다.

FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6646000 - Success

이제 dh 명령어로 드라이버 핸들을 살펴본다.

FS0:\> dh -? -b
Displays the device handles in the UEFI environment.

DH [-l <lang>] [handle | -p <prot_id>] [-d] [-v]

  -p     - Dumps all handles of a protocol specified by the GUID.
  -d     - Dumps UEFI Driver Model-related information.
  -l     - Dumps information using the language codes (e.g. ISO 639-2).
  -sfo   - Displays information as described in Standard-Format Output.
  -v     - Dumps verbose information about a specific handle.
  handle - Specifies a handle to dump information about (a hexadecimal number).
           If not present, then all information will be dumped.

NOTES:
  1. When neither 'handle' nor 'prot_id' is specified, a list of all the
     device handles in the UEFI environment is displayed.
  2. The '-d' option displays UEFI Driver Model related information including
     parent handles, child handles, all drivers installed on the handle, etc.
  3. The '-v' option displays verbose information for the specified handle
     including all the protocols on the handle and their details.
  4. If the '-p' option is specified, all handles containing the specified
     protocol will be displayed. Otherwise, the 'handle' parameter has to be
     specified for display. In this case, the '-d' option will be enabled
     automatically if the '-v' option is not specified.

EXAMPLES:
  * To display all handles and display one screen at a time:
    Shell> dh -b

  * To display the detailed information on handle 0x30:
    Shell> dh 30

  * To display all handles with 'diskio' protocol:
    Shell> dh -p diskio

  * To display all handles with 'LoadedImage' protocol and break when the screen is
    full:
    Shell> dh -p LoadedImage -b

매개변수 없이 dh 명령어를 실행하면 시스템의 모든 핸들을 얻을 수 있다. 우리가 만든 드라이버는 맨 마지막에 확인할 수 있다.

FS0:\> dh
...
C0: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)

핸들을 더 상세하게 확인할 수도 있다.

FS0:\> dh -d -v c0
C6: 664C998
ImageDevicePath(664A018)
  PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\SimpleDriver.efi
LoadedImage(664A440)
  Revision......: 0x00001000
  ParentHandle..: 6EE5D18
  SystemTable...: 79EE018
  DeviceHandle..: 6E36798
  FilePath......: \SimpleDriver.efi
  PdbFileName...: /home/aladyshev/tiano/edk2/Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/SimpleDriver/SimpleDriver/DEBUG/SimpleDriver.dll
  OptionsSize...: 0
  LoadOptions...: 0
  ImageBase.....: 6646000
  ImageSize.....: 16C0
  CodeType......: EfiBootServicesCode
  DataType......: EfiBootServicesData
  Unload........: 0

생성한 드라이버로 더 많은 인스턴스를 생성할 수 있으며, 많이 생성한다고 해서 문제되지 않는다.

FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6619000 - Success
FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6617000 - Success
FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6613000 - Success

dh 명령어를 통해 확인하면 드라이버 핸들이 여러 개인 것을 확인할 수 있다.

FS0:\> dh
...
C0: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C1: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C2: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C3: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)

메모리에서 드라이버를 언로드하려면 unload 명령을 사용하면 된다.

FS0:\> unload -?
Unloads a driver image that was already loaded.

UNLOAD [-n] [-v|-verbose] Handle

  -n           - Skips all prompts during unloading, so that it can be used
                 in a script file.
  -v, -verbose - Dumps verbose status information before the image is unloaded.
  Handle       - Specifies the handle of driver to unload, always taken as hexadecimal number.

NOTES:
  1. The '-n' option can be used to skip all prompts during unloading.
  2. If the '-v' option is specified, verbose image information will be
     displayed before the image is unloaded.
  3. Only drivers that support unloading can be successfully unloaded.
  4. Use the 'LOAD' command to load a driver.

EXAMPLES:
  * To find the handle for the UEFI driver image to unload:
    Shell> dh -b

  * To unload the UEFI driver image with handle 27:
    Shell> unload 27

unload 명령은 드라이버에 Unload 함수가 없기 때문에 지금은 사용할 수 없다. dh -d -v c0을 사용하면 Unload........: 0 부분을 확인할 수 있다.

따라서 드라이버를 언로드하려고 하면 오류가 발생한다.

FS0:\> unload c0
Unload - Handle [664C998].  [y/n]?
y
Unload - Handle [664C998] Result Unsupported.

dh 명령어를 실행하면 시스템에 드라이버 핸들이 여전히 존재하는 것을 볼 수 있다.

Unload 함수 추가

이제 언로드 함수를 드라이버에 추가한다. 아래 설정을 UefiLessonsPkg/SimpleDriver/SimpleDriver.inf에 추가한다.

[Defines]
  ...
  ENTRY_POINT                    = SimpleDriverEntryPoint
+ UNLOAD_IMAGE                   = SimpleDriverUnload

UefiLessonsPkg/SimpleDriver/SimpleDriver.c에 함수를 추가한다.

EFI_STATUS
EFIAPI
SimpleDriverUnload (
  EFI_HANDLE ImageHandle
  )
{
  Print(L"Bye-bye from driver!\n");

  return EFI_SUCCESS;
}

새로 빌드하고 드라이버를 로드한 후 dh -d -v 명령어를 사용하면 위에서 확인한 결과와는 달리 Unload에 값이 들어간 것을 볼 수 있다.

FS0:\> dh -d -v c0
C6: 664CA98
ImageDevicePath(664C618)
  PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\SimpleDriver.efi
LoadedImage(664A240)
  Revision......: 0x00001000
  ParentHandle..: 6EE5D18
  SystemTable...: 79EE018
  DeviceHandle..: 6E36798
  FilePath......: \SimpleDriver.efi
  PdbFileName...: /home/aladyshev/tiano/edk2/Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/SimpleDriver/SimpleDriver/DEBUG/SimpleDriver.dll
  OptionsSize...: 0
  LoadOptions...: 0
  ImageBase.....: 6646000
  ImageSize.....: 1780
  CodeType......: EfiBootServicesCode
  DataType......: EfiBootServicesData
  Unload........: 6647047

출력된 결과를 통해 알 수 있듯이 언로드 문자열이 드라이버 언로드 함수에 대한 포인터로 채워진다.

드라이버를 언로드하기 전에 dmem을 통해 ImageBase 주소를 확인한다.

FS0:\> dmem 6646000 A0
Memory Address 0000000006646000 A0 Bytes
  06646000: 4D 5A 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *MZ..............*
  06646010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646030: 00 00 00 00 00 00 00 00-00 00 00 00 80 00 00 00  *................*
  06646040: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646050: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646060: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646070: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646080: 50 45 00 00 64 86 02 00-00 00 00 00 00 00 00 00  *PE..d...........*
  06646090: 00 00 00 00 F0 00 2E 00-0B 02 00 00 40 14 00 00  *............@...*

MZ 시그니처는 PE/COFF 이미지(*.efi 파일)의 헤더를 나타낸다. 그래서 실제 드라이버는 ImageBase에 존재한다.

이제 드라이버를 언로드 한다.

FS0:\> unload c6
Unload - Handle [664CF18].  [y/n]?
y
Bye-bye from driver!
Unload - Handle [664CF18] Result Success.

메모리를 다시 확인하면 아래와 같이 비어진 것을 확인할 수 있다.

FS0:\> dmem 6646000 A0
Memory Address 0000000006646000 A0 Bytes
  06646000: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646010: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646020: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646030: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646040: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646050: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646060: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646070: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646080: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646090: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*

드라이버를 새로 로드하면 기존에 사용했던 C0은 넘어가고 C1으로 핸들이 존재하는 것을 볼 수 있다.

FS0:\> dh
...
C7: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
Previous드라이버 및 라이브러리Next35. 애플리케이션에서 사용할 간단한 라이브러리 생성

Last updated 2 years ago

UefiDriverEntryPoint 라이브러리 클래스가 사용된다. (이전에는 항상 UefiApplicationEntryPoint를 사용했다.) UefiApplicationEntryPoint 라이브러리에 대해 궁금하다면 아래 링크를 통해 확인할 수 있다.

🖥️
https://github.com/tianocore/edk2/tree/master/MdePkg/Library/UefiDriverEntryPoint