💻
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. 테이블

28. EFI_SHELL_PROTOCOL을 통하여 ACPI 테이블을 파일에 저장하기

Previous27. dmem/EFI_SMBIOS_PROTOCOL/smbiosview를 통해서 SMBIOS 정보 가져오기Next29. EFI_ACPI_SDT_PROTOCOL 및 ShellLib를 사용하여 ACPI BGRT 테이블에서 BMP 이미지 저장하기

Last updated 2 years ago

최신 ACPI 스펙은 UEFI 스펙 페이지()에서 찾을 수 있다. 현재 최신 사양은 "ACPI Specification Version 6.4 (released January 2021"()이다.

SMBIOS 테이블에 사용한 것과 동일한 방법을 사용하여 ACPI Entry point 테이블 주소를 출력하겠다.

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

#include <Library/BaseMemoryLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  for (UINTN i=0; i<SystemTable->NumberOfTableEntries; i++) {
    if (CompareGuid(&(SystemTable->ConfigurationTable[i].VendorGuid), &gEfiAcpi20TableGuid)) {
      Print(L"ACPI table is placed at %p\n\n", SystemTable->ConfigurationTable[i].VendorTable);
    }
  }
  return EFI_SUCCESS;
}

출력된 ACPI 테이블 주소를 바탕으로 dmem을 사용하여 메모리 내부 값을 확인하겠다.

FS0:\> AcpiInfo.efi
ACPI table is placed at 7B7E014

FS0:\> dmem 7B7E014 30
Memory Address 0000000007B7E014 30 Bytes
  07B7E014: 52 53 44 20 50 54 52 20-4E 42 4F 43 48 53 20 02  *RSD PTR NBOCHS .*
  07B7E024: 74 D0 B7 07 24 00 00 00-E8 D0 B7 07 00 00 00 00  *t...$...........*
  07B7E034: 66 00 00 00 AF AF AF AF-AF AF AF AF AF AF AF AF  *f...............*
FS0:\>

여기에는 RSDT 및 XSDT 테이블에 대한 주소가 포함되며 오프셋을 계산시 메모리 덤프에서 다음과 같은 주소를 얻을 수 있다.

XSDT=0x07B7D0E8
RSDT=0x07B7D074

이러한 테이블은 실제로 OS에 유용한 데이터를 포함하는 다른 ACPI 테이블에 대한 포인터를 포함한다.

규격에 따르면 "플랫폼은 ACPI 1.0 운영체제와 호환성을 가능하게 하는 RSDT를 제공합니다. XSDT는 RSDT 기능을 대체합니다." 따라서 dmem을 사용하여 이러한 주소를 출력하면 테이블 내용은 테이블 서명을 제외하고는 거의 동일하다. 따라서 XSDT 테이블 데이터를 파싱하겠다.

이제 코드를 작성해보겠다. ACPI 구조에 대한 정의는 아래의 헤더 파일들에 존재하니 확인하는 것도 좋다.

$ ls -1 MdePkg/Include/IndustryStandard/Acpi*
MdePkg/Include/IndustryStandard/Acpi.h
MdePkg/Include/IndustryStandard/Acpi10.h
MdePkg/Include/IndustryStandard/Acpi20.h
MdePkg/Include/IndustryStandard/Acpi30.h
MdePkg/Include/IndustryStandard/Acpi40.h
MdePkg/Include/IndustryStandard/Acpi50.h
MdePkg/Include/IndustryStandard/Acpi51.h
MdePkg/Include/IndustryStandard/Acpi60.h
MdePkg/Include/IndustryStandard/Acpi61.h
MdePkg/Include/IndustryStandard/Acpi62.h
MdePkg/Include/IndustryStandard/Acpi63.h
MdePkg/Include/IndustryStandard/AcpiAml.h

최신 헤더에는 이전 헤더들이 포함되어 있음을 명심하자.

Acpi.h > Acpi63.h > Acpi62.h > ... > Acpi10.h > AcpiAml.h
///
/// Root System Description Pointer Structure
///
typedef struct {
  UINT64  Signature;
  UINT8   Checksum;
  UINT8   OemId[6];
  UINT8   Revision;
  UINT32  RsdtAddress;
  UINT32  Length;
  UINT64  XsdtAddress;
  UINT8   ExtendedChecksum;
  UINT8   Reserved[3];
} EFI_ACPI_6_3_ROOT_SYSTEM_DESCRIPTION_POINTER;

위의 구조를 이용하여 RSDT/XSDT 테이블의 주소를 출력이 가능하다.

EFI_ACPI_6_3_ROOT_SYSTEM_DESCRIPTION_POINTER* RSDP = NULL;

for (UINTN i=0; i<SystemTable->NumberOfTableEntries; i++) {
  if (CompareGuid(&(SystemTable->ConfigurationTable[i].VendorGuid), &gEfiAcpi20TableGuid)) {
    Print(L"RSDP table is placed at %p\n\n", SystemTable->ConfigurationTable[i].VendorTable);
    RSDP = SystemTable->ConfigurationTable[i].VendorTable;
  }
}

if (!RSDP) {
  Print(L"No ACPI2.0 table was found in the system\n");
  return EFI_SUCCESS;
}

if (((CHAR8)((RSDP->Signature >>  0) & 0xFF) != 'R') ||
    ((CHAR8)((RSDP->Signature >>  8) & 0xFF) != 'S') ||
    ((CHAR8)((RSDP->Signature >> 16) & 0xFF) != 'D') ||
    ((CHAR8)((RSDP->Signature >> 24) & 0xFF) != ' ') ||
    ((CHAR8)((RSDP->Signature >> 32) & 0xFF) != 'P') ||
    ((CHAR8)((RSDP->Signature >> 40) & 0xFF) != 'T') ||
    ((CHAR8)((RSDP->Signature >> 48) & 0xFF) != 'R') ||
    ((CHAR8)((RSDP->Signature >> 56) & 0xFF) != ' ')) {
  Print(L"Error! RSDP signature is not valid!\n");
  return EFI_SUCCESS;
}

Print(L"System description tables:\n");
Print(L"\tRSDT table is placed at address %p\n", RSDP->RsdtAddress);
Print(L"\tXSDT table is placed at address %p\n", RSDP->XsdtAddress);
Print(L"\n");

위의 구조가 설명되어 있는 부분에서 XSDT에 대한 설명도 또한찾아볼 수 있다.

//
// Extended System Description Table
// No definition needed as it is a common description table header, the same with
// EFI_ACPI_DESCRIPTION_HEADER, followed by a variable number of UINT64 table pointers.
//
#pragma pack(1)
///
/// The common ACPI description table header.  This structure prefaces most ACPI tables.
///
typedef struct {
  UINT32  Signature;
  UINT32  Length;
  UINT8   Revision;
  UINT8   Checksum;
  UINT8   OemId[6];
  UINT64  OemTableId;
  UINT32  OemRevision;
  UINT32  CreatorId;
  UINT32  CreatorRevision;
} EFI_ACPI_DESCRIPTION_HEADER;
#pragma pack()

이제 시스템에 있는 다른 ACPI 테이블에 대한 정보를 확인해 보겠다.

EFI_ACPI_DESCRIPTION_HEADER* XSDT = (EFI_ACPI_DESCRIPTION_HEADER*)RSDP->XsdtAddress;
if (((CHAR8)((XSDT->Signature >>  0) & 0xFF) != 'X') ||
    ((CHAR8)((XSDT->Signature >>  8) & 0xFF) != 'S') ||
    ((CHAR8)((XSDT->Signature >> 16) & 0xFF) != 'D') ||
    ((CHAR8)((XSDT->Signature >> 24) & 0xFF) != 'T')) {
  Print(L"Error! XSDT signature is not valid!\n");
  return EFI_SUCCESS;
}

Print(L"Main ACPI tables:\n");
UINT64 offset = sizeof(EFI_ACPI_DESCRIPTION_HEADER);
while (offset < XSDT->Length) {
  UINT64* table_address = (UINT64*)((UINT8*)XSDT + offset);
  EFI_ACPI_6_3_COMMON_HEADER* table = (EFI_ACPI_6_3_COMMON_HEADER*)(*table_address);

  Print(L"\t%c%c%c%c table is placed at address %p with length 0x%x\n",
                                           (CHAR8)((table->Signature>> 0)&0xFF),
                                           (CHAR8)((table->Signature>> 8)&0xFF),
                                           (CHAR8)((table->Signature>>16)&0xFF),
                                           (CHAR8)((table->Signature>>24)&0xFF),
                                           table,
                                           table->Length);
  offset += sizeof(UINT64);
}

여기서 한가지 확인하고 넘어가야 할 것이 있다. 일부 ACPi 테이블은 다른 ACPI 테이블에 대한 포인터를 포함할 수 있다. 예를들어 Fixed ACPI Description Table(FADT)에는 DSDT 및 FACS 테이블에 대한 포인터가 포함될 수 있다.

아래에서 나올 설명은 위의 FADT 구조의 필드이기 때문에 구조를 확인하는 것을 권장한다.

FADT의 FirmwareCtrl 필드에는 FACS 테이블에 대한 포인터가 포함되며 Dsdt 필드에는 DSDT 테이블에 대한 포인터가 포함된다. CheckSubtables 함수를 작성하여 해당 ACPI 테이블이 FADT인지 그리고 하위 테이블을 찾을 수 있는지 확인해 보겠다.

VOID CheckSubtables(EFI_ACPI_6_3_COMMON_HEADER* table)
{
  if (((CHAR8)((table->Signature >>  0) & 0xFF) == 'F') &&
      ((CHAR8)((table->Signature >>  8) & 0xFF) == 'A') &&
      ((CHAR8)((table->Signature >> 16) & 0xFF) == 'C') &&
      ((CHAR8)((table->Signature >> 24) & 0xFF) == 'P')) {
    EFI_ACPI_6_3_FIXED_ACPI_DESCRIPTION_TABLE* FADT = (EFI_ACPI_6_3_FIXED_ACPI_DESCRIPTION_TABLE*)table;

    EFI_ACPI_6_3_COMMON_HEADER* DSDT = (EFI_ACPI_6_3_COMMON_HEADER*)(UINT64)(FADT->Dsdt);
    if (((CHAR8)((DSDT->Signature >>  0) & 0xFF) == 'D') &&
        ((CHAR8)((DSDT->Signature >>  8) & 0xFF) == 'S') &&
        ((CHAR8)((DSDT->Signature >> 16) & 0xFF) == 'D') &&
        ((CHAR8)((DSDT->Signature >> 24) & 0xFF) == 'T')) {
      Print(L"\tDSDT table is placed at address %p with length 0x%x\n", DSDT, DSDT->Length);
    } else {
      Print(L"\tError! DSDT signature is not valid!\n");
    }

    EFI_ACPI_6_3_COMMON_HEADER* FACS = (EFI_ACPI_6_3_COMMON_HEADER*)(UINT64)(FADT->FirmwareCtrl);
    if (((CHAR8)((FACS->Signature >>  0) & 0xFF) == 'F') &&
        ((CHAR8)((FACS->Signature >>  8) & 0xFF) == 'A') &&
        ((CHAR8)((FACS->Signature >> 16) & 0xFF) == 'C') &&
        ((CHAR8)((FACS->Signature >> 24) & 0xFF) == 'S')) {
      Print(L"\tFACS table is placed at address %p with length 0x%x\n", FACS, FACS->Length);
    } else {
      Print(L"\tError! FACS signature is not valid!\n");
    }
  }
}

위에서 만든 함수를 Print문 바로 뒤에 존재하는 while문에서 호출하게 만든다.

CheckSubtables(table);

이후 빌드 후 실행을 하면 아래와 같은 결과가 나온다.

FS0:\> AcpiInfo.efi
RSDP table is placed at 7B7E014

System description tables:
        RSDT table is placed at address 7B7D074
        XSDT table is placed at address 7B7D0E8

Main ACPI tables:
        FACP table is placed at address 7B7A000 with length 0x74
        DSDT table is placed at address 7B7B000 with length 0x140B
        FACS table is placed at address 7BDD000 with length 0x40
        APIC table is placed at address 7B79000 with length 0x78
        HPET table is placed at address 7B78000 with length 0x38
        BGRT table is placed at address 7B77000 with length 0x38

결과를 보다시피 우리 시스템에는 ACPI 데이터 테이블이 여러개가 존재한다.

EFI_SHELL_PROTOCOL을 통한 테이블 데이터 저장

typedef struct _EFI_SHELL_PROTOCOL {
  EFI_SHELL_EXECUTE                         Execute;
  EFI_SHELL_GET_ENV                         GetEnv;
  EFI_SHELL_SET_ENV                         SetEnv;
  EFI_SHELL_GET_ALIAS                       GetAlias;
  EFI_SHELL_SET_ALIAS                       SetAlias;
  EFI_SHELL_GET_HELP_TEXT                   GetHelpText;
  EFI_SHELL_GET_DEVICE_PATH_FROM_MAP        GetDevicePathFromMap;
  EFI_SHELL_GET_MAP_FROM_DEVICE_PATH        GetMapFromDevicePath;
  EFI_SHELL_GET_DEVICE_PATH_FROM_FILE_PATH  GetDevicePathFromFilePath;
  EFI_SHELL_GET_FILE_PATH_FROM_DEVICE_PATH  GetFilePathFromDevicePath;
  EFI_SHELL_SET_MAP                         SetMap;
  EFI_SHELL_GET_CUR_DIR                     GetCurDir;
  EFI_SHELL_SET_CUR_DIR                     SetCurDir;
  EFI_SHELL_OPEN_FILE_LIST                  OpenFileList;
  EFI_SHELL_FREE_FILE_LIST                  FreeFileList;
  EFI_SHELL_REMOVE_DUP_IN_FILE_LIST         RemoveDupInFileList;
  EFI_SHELL_BATCH_IS_ACTIVE                 BatchIsActive;
  EFI_SHELL_IS_ROOT_SHELL                   IsRootShell;
  EFI_SHELL_ENABLE_PAGE_BREAK               EnablePageBreak;
  EFI_SHELL_DISABLE_PAGE_BREAK              DisablePageBreak;
  EFI_SHELL_GET_PAGE_BREAK                  GetPageBreak;
  EFI_SHELL_GET_DEVICE_NAME                 GetDeviceName;
  EFI_SHELL_GET_FILE_INFO                   GetFileInfo;
  EFI_SHELL_SET_FILE_INFO                   SetFileInfo;
  EFI_SHELL_OPEN_FILE_BY_NAME               OpenFileByName;
  EFI_SHELL_CLOSE_FILE                      CloseFile;
  EFI_SHELL_CREATE_FILE                     CreateFile;
  EFI_SHELL_READ_FILE                       ReadFile;
  EFI_SHELL_WRITE_FILE                      WriteFile;
  EFI_SHELL_DELETE_FILE                     DeleteFile;
  EFI_SHELL_DELETE_FILE_BY_NAME             DeleteFileByName;
  EFI_SHELL_GET_FILE_POSITION               GetFilePosition;
  EFI_SHELL_SET_FILE_POSITION               SetFilePosition;
  EFI_SHELL_FLUSH_FILE                      FlushFile;
  EFI_SHELL_FIND_FILES                      FindFiles;
  EFI_SHELL_FIND_FILES_IN_DIR               FindFilesInDir;
  EFI_SHELL_GET_FILE_SIZE                   GetFileSize;
  EFI_SHELL_OPEN_ROOT                       OpenRoot;
  EFI_SHELL_OPEN_ROOT_BY_HANDLE             OpenRootByHandle;
  EFI_EVENT                                 ExecutionBreak;
  UINT32                                    MajorVersion;
  UINT32                                    MinorVersion;
  // Added for Shell 2.1
  EFI_SHELL_REGISTER_GUID_NAME              RegisterGuidName;
  EFI_SHELL_GET_GUID_NAME                   GetGuidName;
  EFI_SHELL_GET_GUID_FROM_NAME              GetGuidFromName;
  EFI_SHELL_GET_ENV_EX                      GetEnvEx;
} EFI_SHELL_PROTOCOL;

해당 프로토콜에서 우리는 OpenFileByName/WriteFile/CloseFile 3가지 함수를 사용한다.

EFI_SHELL_PROTOCOL.OpenFileByName()

Summary:
Opens a file or a directory by file name.

Prototype:i
typdef
EFI_STATUS
(EFIAPI *EFI_SHELL_OPEN_FILE_BY_NAME) (
 IN CONST CHAR16 *FileName,
 OUT SHELL_FILE_HANDLE *FileHandle,
 IN UINT64 OpenMode
 );

Parameters:
FileName	Points to the null-terminated UCS-2 encoded file name.
FileHandle	On return, points to the file handle.
OpenMode	File open mode.

Description:
This function opens the specified file in the specified OpenMode and returns a file handle.
EFI_SHELL_PROTOCOL.WriteFile()

Summary:
Writes data to the file.

Prototype:
typedef
EFI_STATUS
(EFIAPI EFI_SHELL_WRITE_FILE)(
 IN SHELL_FILE_HANDLE FileHandle,
 IN OUT UINTN *BufferSize,
 OUT VOID *Buffer
 );

Parameters:
FileHandle 	The opened file handle for writing.
BufferSize	On input, size of Buffer.
Buffer		The buffer in which data to write.

Description:
This function writes the specified number of bytes to the file at the current file position. The current file position is advanced the actual number of bytes
written, which is returned in BufferSize. Partial writes only occur when there has been a data error during the write attempt (such as “volume space full”).
The file automatically grows to hold the data, if required.
EFI_SHELL_PROTOCOL.CloseFile()

Summary:
Closes the file handle.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_SHELL_CLOSE_FILE)(
 IN SHELL_FILE_HANDLE FileHandle
 );

Parameters:
FileHandle	The file handle to be closed
Description	This function closes a specified file handle. All “dirty” cached file data is flushed
		to the device, and the file is closed. In all cases, the handle is closed.

해당 함수들을 이용하기 위해서 c 파일에 헤더를 추가 및 inf 파일에 프로토콜 GUID 값을 넣어야 한다.

#include <Protocol/Shell.h>
[Protocols]
  gEfiShellProtocolGuid

이후 프로그램에서 EFI_SHELLPROTOCOL을 사용하기 위해서 BootServices의 LocateProtocol 기능을 통해 프로토콜을획득한다.

EFI_SHELL_PROTOCOL* ShellProtocol;
EFI_STATUS Status = gBS->LocateProtocol(
  &gEfiShellProtocolGuid,
  NULL,
  (VOID **)&ShellProtocol
);

if (EFI_ERROR(Status)) {
  Print(L"Can't open EFI_SHELL_PROTOCOL: %r\n", Status);
  return EFI_SUCCESS;
}

SaveACPITable 함수를 만들어 파일 저장 기능에 대해서 한번에 처리할 수 있도록 만들어 주겠다.

EFI_STATUS SaveACPITable(UINT32 Signature,  // table signature
                         VOID* addr,        // table address
                         UINTN size)        // table size

main의 while문은 이전과 다르게 SaveACPITable함수를 추가하여 아래와 같이 변경되었다.

while (offset < XSDT->Length) {
  UINT64* table_address = (UINT64*)((UINT8*)XSDT + offset);
  EFI_ACPI_6_3_COMMON_HEADER* table = (EFI_ACPI_6_3_COMMON_HEADER*)(*table_address);
  Print(L"\t%c%c%c%c table is placed at address %p with length 0x%x\n",
                                           (CHAR8)((table->Signature>> 0)&0xFF),
                                           (CHAR8)((table->Signature>> 8)&0xFF),
                                           (CHAR8)((table->Signature>>16)&0xFF),
                                           (CHAR8)((table->Signature>>24)&0xFF),
                                           table,
                                           table->Length);

  SaveACPITable(table->Signature, table, table->Length);

  CheckSubtables(table);

  offset += sizeof(UINT64);
}

또한 DSDT 및 FACS 테이블을 저장하기 위해 CheckSubtable 기능에 추가하는 것을 잊으면 안된다.\

SaveACPITable 함수를 호출할 때마다 EFI_SHELL_PROTOCOL* ShellProtocol을 사용하므로 모든 곳의 매개변수로 전달하거나 ShellProtocol을 전역변수로 옮기면 된다. 이제 SaveACPITable 함수를 작성해야한다. ACPI 테이블 데이터를 .aml 파일에 저장한다. ACPI 언어 소스 파일에는 일반적으로 .asl/.dsl 확장자(ACPI 소스 언어)가 있고 컴파일된 파일에는 .aml확장자(ACPI 기계 언어)가 있기 때문에 파일에 .aml확장자를 사용한다.

EFI_STATUS SaveACPITable(UINT32 Signature, VOID* addr, UINTN size) {
  CHAR16 TableName[5];
  TableName[0] = (CHAR16)((Signature>> 0)&0xFF);
  TableName[1] = (CHAR16)((Signature>> 8)&0xFF);
  TableName[2] = (CHAR16)((Signature>>16)&0xFF);
  TableName[3] = (CHAR16)((Signature>>24)&0xFF);
  TableName[4] = 0;

  CHAR16 FileName[9] = {0};
  StrCpyS(FileName, 9, TableName);
  StrCatS(FileName, 9, L".aml");
  SHELL_FILE_HANDLE FileHandle;
  EFI_STATUS Status = ShellProtocol->OpenFileByName(FileName,
                                                    &FileHandle,
                                                    EFI_FILE_MODE_CREATE |
                                                    EFI_FILE_MODE_WRITE |
                                                    EFI_FILE_MODE_READ);
  if (!EFI_ERROR(Status)) {
    Status = ShellProtocol->WriteFile(FileHandle, &size, addr);
    if (EFI_ERROR(Status)) {
      Print(L"Error in WriteFile: %r\n", Status);
    }
    Status = ShellProtocol->CloseFile(FileHandle);
    if (EFI_ERROR(Status)) {
      Print(L"Error in CloseFile: %r\n", Status);
    }
  } else {
    Print(L"Error in OpenFileByName: %r\n", Status);
  }
  return Status;
}

앱을 빌드하고 OVMF에서 실행하면 UEF_disk 공유 폴더에 4개의 파일이 생길 것이다.

$ ls -1 ~/UEFI_disk/*.aml
/home/kostr/UEFI_disk/apic.aml
/home/kostr/UEFI_disk/bgrt.aml
/home/kostr/UEFI_disk/dsdt.aml
/home/kostr/UEFI_disk/facp.aml
/home/kostr/UEFI_disk/facs.aml
/home/kostr/UEFI_disk/hpet.aml

iasl 컴파일러를 사용하여 ACPI 테이블 데이터를 disassemble 할 수 있다.

$ iasl -d ~/UEFI_disk/*.aml

Intel ACPI Component Architecture
ASL+ Optimizing Compiler/Disassembler version 20190509
Copyright (c) 2000 - 2019 Intel Corporation

File appears to be binary: found 81 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file /home/kostr/UEFI_disk/apic.aml, Length 0x78 (120) bytes
ACPI: APIC 0x0000000000000000 000078 (v01 BOCHS  BXPCAPIC 00000001 BXPC 00000001)
Acpi Data Table [APIC] decoded
Formatted output:  /home/kostr/UEFI_disk/apic.dsl - 4939 bytes
File appears to be binary: found 31 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file /home/kostr/UEFI_disk/bgrt.aml, Length 0x38 (56) bytes
ACPI: BGRT 0x0000000000000000 000038 (v01 INTEL  EDK2     00000002      01000013)
Acpi Data Table [BGRT] decoded
Formatted output:  /home/kostr/UEFI_disk/bgrt.dsl - 1632 bytes
File appears to be binary: found 1630 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file /home/kostr/UEFI_disk/dsdt.aml, Length 0x140B (5131) bytes
ACPI: DSDT 0x0000000000000000 00140B (v01 BOCHS  BXPCDSDT 00000001 BXPC 00000001)
Pass 1 parse of [DSDT]
Pass 2 parse of [DSDT]
Parsing Deferred Opcodes (Methods/Buffers/Packages/Regions)

Parsing completed
Disassembly completed
ASL Output:    /home/kostr/UEFI_disk/dsdt.dsl - 43444 bytes
File appears to be binary: found 91 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file /home/kostr/UEFI_disk/facp.aml, Length 0x74 (116) bytes
ACPI: FACP 0x0000000000000000 000074 (v01 BOCHS  BXPCFACP 00000001 BXPC 00000001)
Acpi Data Table [FACP] decoded
Formatted output:  /home/kostr/UEFI_disk/facp.dsl - 4896 bytes
File appears to be binary: found 59 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file /home/kostr/UEFI_disk/facs.aml, Length 0x40 (64) bytes
ACPI: FACS 0x0000000000000000 000040
Acpi Data Table [FACS] decoded
Formatted output:  /home/kostr/UEFI_disk/facs.dsl - 1394 bytes
File appears to be binary: found 33 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file /home/kostr/UEFI_disk/hpet.aml, Length 0x38 (56) bytes
ACPI: HPET 0x0000000000000000 000038 (v01 BOCHS  BXPCHPET 00000001 BXPC 00000001)
Acpi Data Table [HPET] decoded
Formatted output:  /home/kostr/UEFI_disk/hpet.dsl - 1891 bytes

명령어를 수행하면 UEFI_disk 공유 폴더에 .dsl 파일이 생성될 것이다.

$ cat ~/UEFI_disk/apic.dsl
/*
 * Intel ACPI Component Architecture
 * AML/ASL+ Disassembler version 20190509 (64-bit version)
 * Copyright (c) 2000 - 2019 Intel Corporation
 *
 * Disassembly of /home/kostr/UEFI_disk/apic.aml, Sat Jul  3 00:09:16 2021
 *
 * ACPI Data Table [APIC]
 *
 * Format: [HexOffset DecimalOffset ByteLength]  FieldName : FieldValue
 */

[000h 0000   4]                    Signature : "APIC"    [Multiple APIC Description Table (MADT)]
[004h 0004   4]                 Table Length : 00000078
[008h 0008   1]                     Revision : 01
[009h 0009   1]                     Checksum : ED
[00Ah 0010   6]                       Oem ID : "BOCHS "
[010h 0016   8]                 Oem Table ID : "BXPCAPIC"
[018h 0024   4]                 Oem Revision : 00000001
[01Ch 0028   4]              Asl Compiler ID : "BXPC"
[020h 0032   4]        Asl Compiler Revision : 00000001

[024h 0036   4]           Local Apic Address : FEE00000
[028h 0040   4]        Flags (decoded below) : 00000001
                         PC-AT Compatibility : 1

[02Ch 0044   1]                Subtable Type : 00 [Processor Local APIC]
[02Dh 0045   1]                       Length : 08
[02Eh 0046   1]                 Processor ID : 00
[02Fh 0047   1]                Local Apic ID : 00
[030h 0048   4]        Flags (decoded below) : 00000001
                           Processor Enabled : 1
                      Runtime Online Capable : 0

[034h 0052   1]                Subtable Type : 01 [I/O APIC]
[035h 0053   1]                       Length : 0C
[036h 0054   1]                  I/O Apic ID : 00
[037h 0055   1]                     Reserved : 00
[038h 0056   4]                      Address : FEC00000
[03Ch 0060   4]                    Interrupt : 00000000

[040h 0064   1]                Subtable Type : 02 [Interrupt Source Override]
[041h 0065   1]                       Length : 0A
[042h 0066   1]                          Bus : 00
[043h 0067   1]                       Source : 00
[044h 0068   4]                    Interrupt : 00000002
[048h 0072   2]        Flags (decoded below) : 0000
                                    Polarity : 0
                                Trigger Mode : 0

[04Ah 0074   1]                Subtable Type : 02 [Interrupt Source Override]
[04Bh 0075   1]                       Length : 0A
[04Ch 0076   1]                          Bus : 00
[04Dh 0077   1]                       Source : 05
[04Eh 0078   4]                    Interrupt : 00000005
[052h 0082   2]        Flags (decoded below) : 000D
                                    Polarity : 1
                                Trigger Mode : 3

[054h 0084   1]                Subtable Type : 02 [Interrupt Source Override]
[055h 0085   1]                       Length : 0A
[056h 0086   1]                          Bus : 00
[057h 0087   1]                       Source : 09
[058h 0088   4]                    Interrupt : 00000009
[05Ch 0092   2]        Flags (decoded below) : 000D
                                    Polarity : 1
                                Trigger Mode : 3

[05Eh 0094   1]                Subtable Type : 02 [Interrupt Source Override]
[05Fh 0095   1]                       Length : 0A
[060h 0096   1]                          Bus : 00
[061h 0097   1]                       Source : 0A
[062h 0098   4]                    Interrupt : 0000000A
[066h 0102   2]        Flags (decoded below) : 000D
                                    Polarity : 1
                                Trigger Mode : 3

[068h 0104   1]                Subtable Type : 02 [Interrupt Source Override]
[069h 0105   1]                       Length : 0A
[06Ah 0106   1]                          Bus : 00
[06Bh 0107   1]                       Source : 0B
[06Ch 0108   4]                    Interrupt : 0000000B
[070h 0112   2]        Flags (decoded below) : 000D
                                    Polarity : 1
                                Trigger Mode : 3

[072h 0114   1]                Subtable Type : 04 [Local APIC NMI]
[073h 0115   1]                       Length : 06
[074h 0116   1]                 Processor ID : FF
[075h 0117   2]        Flags (decoded below) : 0000
                                    Polarity : 0
                                Trigger Mode : 0
[077h 0119   1]         Interrupt Input LINT : 01

Raw Table Data: Length 120 (0x78)

    0000: 41 50 49 43 78 00 00 00 01 ED 42 4F 43 48 53 20  // APICx.....BOCHS
    0010: 42 58 50 43 41 50 49 43 01 00 00 00 42 58 50 43  // BXPCAPIC....BXPC
    0020: 01 00 00 00 00 00 E0 FE 01 00 00 00 00 08 00 00  // ................
    0030: 01 00 00 00 01 0C 00 00 00 00 C0 FE 00 00 00 00  // ................
    0040: 02 0A 00 00 02 00 00 00 00 00 02 0A 00 05 05 00  // ................
    0050: 00 00 0D 00 02 0A 00 09 09 00 00 00 0D 00 02 0A  // ................
    0060: 00 0A 0A 00 00 00 0D 00 02 0A 00 0B 0B 00 00 00  // ................
    0070: 0D 00 04 06 FF 00 00 01                          // ........

시그니처 값인 RSP PTR은 RSDP(Root System Description Pointer (RSDP) Structure)를 나타낸다.

최신 ACPI 표준 헤더 파일에서 RSDP 구조에 대한 정의를 살펴보겠다.

해당 설명에서 나온 EFI_ACPI_DESCRIPTION_HEADER에 대한 정의는 아래와 같다.

FADT에 대한 구조와 설명은 아래의 링크에 언급되어 있다.

Fixed ACPI Description Table (FACP) -

Differentiated System Description Table (DSDT) -

Firmware ACPI Control Structure (FACS) -

Multiple APIC Description Table (MADT) -

IA-PC High Precision Event Timer Table (HPET) - - ACPI 사양에는 없지만 페이지와 별도의 문서에 존재한다.

Boot Graphics Resource Table (BGRT) -

SMBIOS 테이블과 마찬가지로 프로토콜을 사용하여 동일한 데이터를 얻을 수 있음을 명심해야한다. EFI_ACPI_SDT_PROTOCOL의 GetAcpiTable()함수는 동일한 정보를 얻는데 도움이 될 수 있다. 해당 프로토콜은 또한 UEFI PI 사양에 의해 정의된다.

이제 메모리에서 파일로 ACPI 테이블을 저장해 보겠다. 이를 위해서 UEFI 쉘 사양에 정의된 EFI_SHELL_PROTOCOL을 활용할 수 있다. File I/O를 위한 많은 기능이 존재하기 때문에 도움이 될 수 있다.

파일 이름을 문자열로 생성하기 위해 StrCatS 및 StrCpyS 함수를 사용한다. 이들은 C++에서 사용되는 strcat_s/strcpy_s와 유사한 문자열 연결/복사 함수의 안전한 버전이다.

🖥️
https://uefi.org/specifications
https://uefi.org/sites/default/files/resources/ACPI_Spec_6_4_Jan22.pdf
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#root-system-description-pointer-rsdp-structure
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi63.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi63.h
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#fixed-acpi-description-table-fadt
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#fixed-acpi-description-table-fadt
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#differentiated-system-description-table-dsdt
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#firmware-acpi-control-structure-facs
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#multiple-apic-description-table-madt
http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/software-developers-hpet-spec-1-0a.pdf
https://uefi.org/acpi
https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#boot-graphics-resource-table-bgrt
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/AcpiSystemDescriptionTable.h
https://uefi.org/sites/default/files/resources/UEFI_Shell_2_2.pdf
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/Shell.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/SafeString.c