💻
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
  • Device Path/Device Node를 정적 및 동적으로 생성하기
  • 다중 노드 Device Path
  • Device Path 반복문
  • 텍스트에서 DevicePath/DeviceNode 만들기
  • 할당된 메모리 해제하는 것 잊지 말기
  1. UEFI 개발
  2. VFR

62. UEFI Device path의 구조

동적 및 정적 Device path. Device path를 통한 상호 작용

이번 장에서는 UEFI의 DevicePath 개념에 대해 설명한다.

Device path는 UEFI 환경에서 장치에 대한 프로그래밍 경로를 정의하는 데 사용한다.

내부적으로 Device Path는 메모리에서 서로를 따르고 있는 소위 Device Path nodes 로 구성되어 있다.

모든 Device Path의 맨 끝에는 특별한 End type의 Device Path node가 있다.

기본적으로 다음과 같이 생겼다.

Device path = 
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End type>

Device Path에서 소위 Device Path instances를 여러 개 인코딩할 가능성이 있다. 이 경우 장치 경로는 조금 더 복잡해진다.

Device path = 
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End instance type>
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End instance type>
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End type>

이제 <Device Path node>의 정의를 살펴보자. 각 <Device Path node>에는 EFI_DEVICE_PATH_PROTOCOL 헤더가 있다. 이 헤더의 크기는 4바이트에 불과하다. 그리고 헤더의 Type/SubType 필드 값에 의해 지시되는 형식의 데이터 바로 뒤에 오게 된다.

#pragma pack(1)

typedef struct {
  UINT8    Type;    ///< 0x01 Hardware Device Path.
                    ///< 0x02 ACPI Device Path.
                    ///< 0x03 Messaging Device Path.
                    ///< 0x04 Media Device Path.
                    ///< 0x05 BIOS Boot Specification Device Path.
                    ///< 0x7F End of Hardware Device Path.

  UINT8    SubType; ///< Varies by Type

  UINT8    Length[2]; ///< Specific Device Path data. Type and Sub-Type define
                      ///< type of data. Size of data is included in Length.
} EFI_DEVICE_PATH_PROTOCOL;

현재 6가지 주요 Device path type이 있다. 위의 Type 필드의 주석에서 확인 가능하다. 이러한 각 type에는 여러 하위 type이 존재한다. 그리고 이러한 각 하위 type에는 해당 데이터에 대해 잘 정의된 고유한 형식이 있고 그리고 그들 모두 UEFI 스펙에 정의되어 있다.

Length 필드는 헤더를 포함한 Device Node의 전체 크기를 정의한다. Length[0]은 하위 바이트를 정의하고 Length[1]은 상위 바이트를 정의한다.

예를 들어 PCI 장치에는 다음이 있다.

  • #define HARDWARE_DEVICE_PATH 0x01 유형

  • #define HW_PCI_DP 0x01 하위 유형

전체 경로는 다음과 같이 인코딩된다.

///
/// PCI Device Path.
///
typedef struct {
  EFI_DEVICE_PATH_PROTOCOL    Header;
  ///
  /// PCI Function Number.
  ///
  UINT8                       Function;
  ///
  /// PCI Device Number.
  ///
  UINT8                       Device;
} PCI_DEVICE_PATH;

EFI_DEVICE_PATH_PROTOCOL 구조를 풀면 다음과 같다.

{
  UINT8    Type      = HARDWARE_DEVICE_PATH = 0x01
  UINT8    SubType   = HW_PCI_DP = 0x01
  UINT8    Length[0] = 0x06
  UINT8    Length[1] = 0x00
  UINT8    Function;
  UINT8    Device;
}

Device Path/Device Node를 정적 및 동적으로 생성하기

UEFI에서 Device Path로 작업할 수 있는 방법을 조사하기 위해 애플리케이션을 만들어보자.

PCI_DEVICE_PATH 유형의 단일 노드만으로 PCI 장치에 대한 Device Path를 생성한다고 가정해 보자.

이론적 경로는 PCI 장치의 Function=5/Device=3을 참조한다.

이러한 특성을 가진 정적 Device Node에 대한 코드는 다음과 같다.

#define EXAMPLE_PCI_FUNCTION 5
#define EXAMPLE_PCI_DEVICE 3

PCI_DEVICE_PATH PciDevicePathNodeStatic;
PciDevicePathNodeStatic.Header.Type = HARDWARE_DEVICE_PATH;
PciDevicePathNodeStatic.Header.SubType = HW_PCI_DP;
PciDevicePathNodeStatic.Header.Length[0] = sizeof(PCI_DEVICE_PATH);
PciDevicePathNodeStatic.Header.Length[1] = 0;
PciDevicePathNodeStatic.Function = EXAMPLE_PCI_FUNCTION;
PciDevicePathNodeStatic.Device = EXAMPLE_PCI_DEVICE;

또는 이와 같다.(사실상 기능은 동일하다.)

#define EXAMPLE_PCI_FUNCTION 5
#define EXAMPLE_PCI_DEVICE 3

PCI_DEVICE_PATH PciDevicePathNodeStatic = {
{
  {
    HARDWARE_DEVICE_PATH,
    HW_PCI_DP,
    {
      (UINT8) (sizeof (PCI_DEVICE_PATH)),
      (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
    }
  },
  EXAMPLE_PCI_FUNCTION,
  EXAMPLE_PCI_DEVICE;
};

DevicePathLib에는 사람이 읽을 수 있는 텍스트 형식으로 DeviceNodes를 출력해주는 함수가 존재한다.

/**
  Converts a device node to its string representation.
  @param DeviceNode        A Pointer to the device node to be converted.
  @param DisplayOnly       If DisplayOnly is TRUE, then the shorter text representation
                           of the display node is used, where applicable. If DisplayOnly
                           is FALSE, then the longer text representation of the display node
                           is used.
  @param AllowShortcuts    If AllowShortcuts is TRUE, then the shortcut forms of text
                           representation for a device node can be used, where applicable.
  @return A pointer to the allocated text representation of the device node or NULL if DeviceNode
          is NULL or there was insufficient memory.
**/
CHAR16 *
EFIAPI
ConvertDeviceNodeToText (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DeviceNode,
  IN BOOLEAN                         DisplayOnly,
  IN BOOLEAN                         AllowShortcuts
  );

다음과 같이 Device Node를 출력하는데 사용하면,

Print(L"PciDevicePathNodeStatic: %s\n", ConvertDeviceNodeToText((EFI_DEVICE_PATH_PROTOCOL*) &PciDevicePathNodeStatic, FALSE, FALSE));

아래와 같은 출력을 얻을 수 있다.

FS0:\> DevicePath.efi
PciDevicePathNodeStatic: Pci(0x3,0x5)

이제 Device Path 노드를 동적으로 생성해보자. 이를 위해 라이브러리에서 CreateDeviceNode함수를 사용할 수 있다.

/**
  Creates a device node.
  This function creates a new device node in a newly allocated buffer of size
  NodeLength and initializes the device path node header with NodeType and NodeSubType.
  The new device path node is returned.
  If NodeLength is smaller than a device path header, then NULL is returned.
  If there is not enough memory to allocate space for the new device path, then
  NULL is returned.
  The memory is allocated from EFI boot services memory. It is the responsibility
  of the caller to free the memory allocated.
  @param  NodeType                   The device node type for the new device node.
  @param  NodeSubType                The device node sub-type for the new device node.
  @param  NodeLength                 The length of the new device node.
  @return The new device path.
**/
EFI_DEVICE_PATH_PROTOCOL *
CreateDeviceNode (
   UINT8                           NodeType,
   UINT8                           NodeSubType,
   UINT16                          NodeLength
  )

코드는 다음과 같다.

EFI_DEVICE_PATH_PROTOCOL* PciDevicePathNodeDynamic = CreateDeviceNode(HARDWARE_DEVICE_PATH, HW_PCI_DP, sizeof(PCI_DEVICE_PATH));
((PCI_DEVICE_PATH*)PciDevicePathNodeDynamic)->Function = EXAMPLE_PCI_FUNCTION;
((PCI_DEVICE_PATH*)PciDevicePathNodeDynamic)->Device = EXAMPLE_PCI_DEVICE;
Print(L"PciDevicePathNodeDynamic: %s\n", ConvertDeviceNodeToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathNodeDynamic, FALSE, FALSE));

그리고 이 코드는 우리에게 정확히 같은 결과를 출력해준다.

FS0:\> DevicePath.efi
PciDevicePathNodeStatic: Pci(0x3,0x5)
PciDevicePathNodeDynamic: Pci(0x3,0x5)

이제 완전한 Device Path를 생성해 보자. 이를 위해 생성한 현재 항목 바로 뒤에 End 유형의 Device Node를 추가해야 한다.

다시 한 번 정적 선언 방법으로 시작하자. PCI_DEVICE_PATH 노드와 End 노드를 모두 포함하는 자체 구조체 type을 만들어야한다.

#pragma pack(1)
typedef struct {
  PCI_DEVICE_PATH             PciDevicePath;
  EFI_DEVICE_PATH_PROTOCOL    End;
} FULL_PCI_DEVICE_PATH;
#pragma pack()


FULL_PCI_DEVICE_PATH  PciDevicePathStatic = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_PCI_DP,
      {
        (UINT8) (sizeof (PCI_DEVICE_PATH)),
        (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
      }
    },
    EXAMPLE_PCI_FUNCTION,
    EXAMPLE_PCI_DEVICE
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      (UINT8) (END_DEVICE_PATH_LENGTH),
      (UINT8) ((END_DEVICE_PATH_LENGTH) >> 8)
    }
  }
};

Device Path를 문자열로 출력하려면 ConvertDevicePathToText 함수를 사용하면 된다.

이 함수는 ConvertDeviceNodeToText와 매우 유사하기 때문에 혼동하기 쉽다. Device Path가 아닌 Device Node에서 ConvertDevicePathToText를 수행하려고 하면 시스템이 중단될 수 있다.

/**
  Converts a device path to its text representation.
  @param DevicePath      A Pointer to the device to be converted.
  @param DisplayOnly     If DisplayOnly is TRUE, then the shorter text representation
                         of the display node is used, where applicable. If DisplayOnly
                         is FALSE, then the longer text representation of the display node
                         is used.
  @param AllowShortcuts  If AllowShortcuts is TRUE, then the shortcut forms of text
                         representation for a device node can be used, where applicable.
  @return A pointer to the allocated text representation of the device path or
          NULL if DeviceNode is NULL or there was insufficient memory.
**/
CHAR16 *
EFIAPI
ConvertDevicePathToText (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN BOOLEAN                         DisplayOnly,
  IN BOOLEAN                         AllowShortcuts
  );

이전처럼 간단하게 사용할 수 있다.

Print(L"PciDevicePathStatic: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) &PciDevicePathStatic, FALSE, FALSE));

실행해보면 다시 한 번 동일한 결과를 출력한다(중요한 문자열 표현이 없는 End 노드만 추가했기 때문).

PciDevicePathStatic: Pci(0x3,0x5)

마지막으로 Device Path를 동적으로 생성해보자. 이 작업을 위해 AppendDevicePathNode 함수를 활용하면 된다.

/**
  Creates a new path by appending the device node to the device path.
  This function creates a new device path by appending a copy of the device node specified by
  DevicePathNode to a copy of the device path specified by DevicePath in an allocated buffer.
  The end-of-device-path device node is moved after the end of the appended device node.
  If DevicePathNode is NULL then a copy of DevicePath is returned.
  If DevicePath is NULL then a copy of DevicePathNode, followed by an end-of-device path device
  node is returned.
  If both DevicePathNode and DevicePath are NULL then a copy of an end-of-device-path device node
  is returned.
  If there is not enough memory to allocate space for the new device path, then NULL is returned.
  The memory is allocated from EFI boot services memory. It is the responsibility of the caller to
  free the memory allocated.
  @param  DevicePath                 A pointer to a device path data structure.
  @param  DevicePathNode             A pointer to a single device path node.
  @retval NULL      There is not enough memory for the new device path.
  @retval Others    A pointer to the new device path if success.
                    A copy of DevicePathNode followed by an end-of-device-path node
                    if both FirstDevicePath and SecondDevicePath are NULL.
                    A copy of an end-of-device-path node if both FirstDevicePath and SecondDevicePath are NULL.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
AppendDevicePathNode (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePath      OPTIONAL,
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePathNode  OPTIONAL
  );

주석에서 알 수 있듯이 첫 번째 인수 대신 NULL을 제공하면 함수는 필요한 Device Node End를 Device Node에 추가한다. 따라서 Device Node에서 Device Path를 효과적으로 생성한다.

EFI_DEVICE_PATH_PROTOCOL* PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)NULL, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);

출력하려는 경우 다음과 같은 코드를 쓸 수 있다.

Print(L"PciDevicePathDynamic: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamic, FALSE, FALSE));

또 한번 같은 결과를 출력한다.

PciDevicePathDynamic: Pci(0x3,0x5)

다중 노드 Device Path

UEFI Shell을 부팅할때 자세히 보면 매핑을 위한 Device Path를 보여주는 매핑 테이블이 출력되는 것을 알 수 있다.

UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
Mapping table
      FS0: Alias(s):HD0a1:;BLK1:
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
Press ESC in 5 seconds to skip startup.nsh or any other key to continue.

PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1) 경로에서 / 사이의 각 텍스트는 Device Path node에 해당한다. 예를 들어 이 특정 경로는 5개의 Device Node로 구성된다(Device End 노드를 잊으면 안된다).

PCI 장치의 정상적인 경로에는 Pci 노드 앞에 PciRoot 노드가 있다. 그러나 이러한 것들을 확인하는 것은 UEFI 스펙의 작업이 아니다. UEFI 스펙은 레슨 시작 부분에서 분석된 Device Path에 대한 구조만 정의해준다.

따라서 모든 Device node에서 Device Path를 생성할 수 있다.

이미 알고 있는 AppendDevicePathNode 함수를 사용해서 우리의 path에 3개의 PCI 노드를 더 추가해 보자.

PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
Print(L"Complicated DevicePath (AppendDevicePathNode): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamic, FALSE, FALSE));

Print 문이 다음과 같이 출력할 것이라고 짐작할 수 있다.

Complicated DevicePath (AppendDevicePathNode): Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)

AppendDevicePath 함수를 사용하여 두 개의 Device Path를 연결할 수도 있다.

이 함수는 Device Path 데이터 노드를 올바르게 연결하므로 최종 경로에는 하나의 Device Path End 노드만 존재한다.

/**
  Creates a new device path by appending a second device path to a first device path.
  This function creates a new device path by appending a copy of SecondDevicePath to a copy of
  FirstDevicePath in a newly allocated buffer.  Only the end-of-device-path device node from
  SecondDevicePath is retained. The newly created device path is returned.
  If FirstDevicePath is NULL, then it is ignored, and a duplicate of SecondDevicePath is returned.
  If SecondDevicePath is NULL, then it is ignored, and a duplicate of FirstDevicePath is returned.
  If both FirstDevicePath and SecondDevicePath are NULL, then a copy of an end-of-device-path is
  returned.
  If there is not enough memory for the newly allocated buffer, then NULL is returned.
  The memory for the new device path is allocated from EFI boot services memory. It is the
  responsibility of the caller to free the memory allocated.
  @param  FirstDevicePath            A pointer to a device path data structure.
  @param  SecondDevicePath           A pointer to a device path data structure.
  @retval NULL      If there is not enough memory for the newly allocated buffer.
  @retval NULL      If FirstDevicePath or SecondDevicePath is invalid.
  @retval Others    A pointer to the new device path if success.
                    Or a copy an end-of-device-path if both FirstDevicePath and SecondDevicePath are NULL.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
AppendDevicePath (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *FirstDevicePath   OPTIONAL,
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *SecondDevicePath  OPTIONAL
  );

우리의 경로에서 데이터 노드를 두 배로 늘리기 위해 이와 같이 사용할 수 있다.

PciDevicePathDynamic = AppendDevicePath((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic);
Print(L"Complicated DevicePath (AppendDevicePath): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamic, FALSE, FALSE));

그러면 4개가 아닌 8개의 PCI 노드가 제공된다.

Complicated DevicePath (AppendDevicePath): Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)

AppendDevicePathNode/AppendDevicePath 함수의 본질을 잊으면 안된다. 모든 호출은 새 버퍼를 생성하지만 더 이상 필요하지 않은 이전 버퍼를 해제하는 것을 잊지 말아야한다. 그렇지 않으면 호출할 때마다 메모리 누수가 발생하게 된다. 따라서 더 정확한 코드는 다음과 같다.

EFI_DEVICE_PATH_PROTOCOL* PciDevicePathDynamicMulti;
EFI_DEVICE_PATH_PROTOCOL* TempPath;

PciDevicePathDynamicMulti = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
TempPath = PciDevicePathDynamicMulti;
PciDevicePathDynamicMulti = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)TempPath, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
FreePool(TempPath);
TempPath = PciDevicePathDynamicMulti;
PciDevicePathDynamicMulti = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)TempPath, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
FreePool(TempPath);
Print(L"Complicated DevicePath (AppendDevicePathNode): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamicMulti, FALSE, FALSE));

TempPath = PciDevicePathDynamicMulti;
PciDevicePathDynamicMulti = AppendDevicePath((EFI_DEVICE_PATH_PROTOCOL*)TempPath, (EFI_DEVICE_PATH_PROTOCOL*)TempPath);
FreePool(TempPath);
Print(L"Complicated DevicePath (AppendDevicePath): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamicMulti, FALSE, FALSE));

Device Path 반복문

라이브러리는 Device Paths를 반복하는 여러 함수를 제공한다. Device Path를 반복하는 유일한 방법은 Device End 노드를 찾을 때까지 Device Node를 하나씩 살펴보는 것이므로 라이브러리는 IsDevicePathEnd 및 NextDevicePathNode라는 두 가지 함수를 제공한다.

/**
  Determines if a device path node is an end node of an entire device path.
  Determines if a device path node specified by Node is an end node of an entire device path.
  If Node represents the end of an entire device path, then TRUE is returned.
  Otherwise, FALSE is returned.
  If Node is NULL, then ASSERT().
  @param  Node      A pointer to a device path node data structure.
  @retval TRUE      The device path node specified by Node is the end of an entire device path.
  @retval FALSE     The device path node specified by Node is not the end of an entire device path.
**/
BOOLEAN
EFIAPI
IsDevicePathEnd (
  IN CONST VOID  *Node
  );
/**
  Returns a pointer to the next node in a device path.
  Returns a pointer to the device path node that follows the device path node specified by Node.
  If Node is NULL, then ASSERT().
  @param  Node      A pointer to a device path node data structure.
  @return a pointer to the device path node that follows the device path node specified by Node.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
NextDevicePathNode (
  IN CONST VOID  *Node
  );

현재 Device Path에 있는 PCI 노드 수를 계산해보자.

EFI_DEVICE_PATH_PROTOCOL* TempDevicePathNode = PciDevicePathDynamicMulti;
UINT8 PciNodeCount = 0;
while (!IsDevicePathEnd(TempDevicePathNode)) {
  if ( (DevicePathType(TempDevicePathNode) == HARDWARE_DEVICE_PATH) && (DevicePathSubType(TempDevicePathNode) == HW_PCI_DP) )
    PciNodeCount++;
  TempDevicePathNode = NextDevicePathNode(TempDevicePathNode);
}
Print(L"Last device path has %d PCI nodes\n", PciNodeCount);

여기에서는 노드 필드에 액세스하는 간단한 함수인 DevicePathType 및 DevicePathSubType 함수를 사용하였다.

이 코드를 실행하면 이전에 계산한 것과 동일한 결과를 볼 수 있다.

Last device path has 8 PCI nodes

텍스트에서 DevicePath/DeviceNode 만들기

이 레슨에서는 계속 ConvertDevicePathToText/ConvertDeviceNodeToText 함수를 사용하여 Device Path 와 노드를 텍스트 문자열로 변환했다.

그러나 실제로 라이브러리에서는 정반대의 방식을 나타내고 있다. 텍스트 문자열에서 Device Path/Device Node를 초기화할 수 있다. 이 작업을 위해 라이브러리에는 ConvertTextToDevicePath 및 ConvertTextToDeviceNode 함수가 존재한다.

/**
  Convert text to the binary representation of a device path.
  @param TextDevicePath  TextDevicePath points to the text representation of a device
                         path. Conversion starts with the first character and continues
                         until the first non-device node character.
  @return A pointer to the allocated device path or NULL if TextDeviceNode is NULL or
          there was insufficient memory.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ConvertTextToDevicePath (
  IN CONST CHAR16  *TextDevicePath
  );
/**
  Convert text to the binary representation of a device node.
  @param TextDeviceNode  TextDeviceNode points to the text representation of a device
                         node. Conversion starts with the first character and continues
                         until the first non-device node character.
  @return A pointer to the EFI device node or NULL if TextDeviceNode is NULL or there was
          insufficient memory or text unsupported.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ConvertTextToDeviceNode (
  IN CONST CHAR16  *TextDeviceNode
  );

UEFI 스펙을 확인할 필요가 없다. 문자열 표현에서 경로가 어떻게 보이는지 이미 알고 있으므로 우리의지식을 활용해보자.

EFI_DEVICE_PATH_PROTOCOL*  PciDevicePathNodeFromText = ConvertTextToDeviceNode(L"Pci(0x3,0x5)");
Print(L"PciDevicePathNodeFromText: %s\n", ConvertDeviceNodeToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathNodeFromText, FALSE, FALSE));
EFI_DEVICE_PATH_PROTOCOL*  PciDevicePathFromText = ConvertTextToDevicePath(L"Pci(0x3,0x5)");
Print(L"PciDevicePathFromText: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathFromText, FALSE, FALSE));

코드를 실행해보면 예상되는 결과가 출력된다.

PciDevicePathNodeFromText: Pci(0x3,0x5)
PciDevicePathFromText: Pci(0x3,0x5)

할당된 메모리 해제하는 것 잊지 말기

이 섹션은 동적으로 생성된 Path 및 Node에 할당된 메모리를 해제하는 것을 잊지 말아야 하는 것을 다시 한번 상기 시키기 위해 만들었다.

FreePool(PciDevicePathNodeDynamic);
FreePool(PciDevicePathDynamic);
FreePool(PciDevicePathNodeFromText);
FreePool(PciDevicePathFromText);
Previous61.dmpstore 명령을 사용하여 변수를 파일에 저장/로드하기Next63. checkbox를 가진 HII 폼 만들기

Last updated 2 years ago

에서 구조를 한 눈에 볼 수 있다. 이 파일에 있는 대부분의 구조체들은 Type/SubType 필드를 기반으로 하는 다양한 데이터 형식에 해당한다.

애플리케이션을 만들고 소스 코드에 <Library/DevicePathLib.h> 헤더를 포함시켜준다. 이것은 UEFI Device Path 라이브러리의 헤더이다.()

🖥️
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/DevicePath.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/DevicePath.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DevicePathLib.h