3. Hello World 출력하기

앞서 생성한 패키지 내부에 HelloWorld 애플리케이션을 포함하도록 한다.

$ mkdir UefiLessonsPkg/HelloWorld
$ vi UefiLessonsPkg/HelloWorld/HelloWorld.inf
-----
[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = HelloWorld
  FILE_GUID                      = 2e55fa38-f148-42d3-af90-1be247323e30
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  
$ vi UefiLessonsPkg/UefiLessonsPkg.dsc
-----
[Components]
  UefiLessonsPkg/SimplestApp/SimplestApp.inf
+ UefiLessonsPkg/HelloWorld/HelloWorld.inf

HelloWorld.c의 소스 코드는 아래와 같이 작성한다.

$ vi UefiLessonsPkg/HelloWorld/HelloWorld.c
-----
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello World!\n");
  return EFI_SUCCESS;
}

위와 같이 작성한 이유는 다음과 같다.

문자열을 출력하기 위해서는 애플리케이션의 Entry Point에 전달되는EFI_SYSTEM_TABLE 서비스를 이용해야 한다. EFI_SYSTEM_TABLE은 다음과 같이 구성되어 있으며 공식 UEFI 스펙에서 자세히 기술하고 있다.(https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf)

typedef struct {
 EFI_TABLE_HEADER Hdr;
 CHAR16 *FirmwareVendor;
 UINT32 FirmwareRevision;
 EFI_HANDLE ConsoleInHandle;
 EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
 EFI_HANDLE ConsoleOutHandle;
 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
 EFI_HANDLE StandardErrorHandle;
 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
 EFI_RUNTIME_SERVICES *RuntimeServices;
 EFI_BOOT_SERVICES *BootServices;
 UINTN NumberOfTableEntries;
 EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;

그 중에서 *ConOut은 'Console Output'의 약자로 ConsoleOutHandle과 연결된 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 인터페이스에 대한 포인터이다.

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 인터페이스에 대한 정보는 다음과 같다.

typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
 EFI_TEXT_RESET Reset;
 EFI_TEXT_STRING OutputString;
 EFI_TEXT_TEST_STRING TestString;
 EFI_TEXT_QUERY_MODE QueryMode;
 EFI_TEXT_SET_MODE SetMode;
 EFI_TEXT_SET_ATTRIBUTE SetAttribute;
 EFI_TEXT_CLEAR_SCREEN ClearScreen;
 EFI_TEXT_SET_CURSOR_POSITION SetCursorPosition;
 EFI_TEXT_ENABLE_CURSOR EnableCursor;
 SIMPLE_TEXT_OUTPUT_MODE *Mode;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

출력 기능을 담당하는 OutputString의 구현은 다음과 같이 정의되어 있다. (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/SimpleTextOut.h)

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString()

Summary
Writes a string to the output device.

Prototype
typedef
EFI_STATUS
(EFIAPI *EFI_TEXT_STRING) (
 IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
 IN CHAR16 *String
 );

Parameters
This    A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL instance.
String  The Null-terminated string to be displayed on the output device(s).

또한 L""CHAR16 을 의미하며 다른 프로세서 아키텍처에서 다른 유형 구현을 위한 프록시이다. 이 때문에 아키텍처별 다른 ProcessorBind.h 헤더 정보를 지정하여 빌드한다.

예로 X64경우에는 다음과 같든 헤더 정보를 이용한다(https://github.com/tianocore/edk2/blob/master/MdePkg/Include/X64/ProcessorBind.h)

이제 빌드를 통해 정상적인 출력이 진행되는지 확인해보자

$ cp Build/UefiLessonsPkg/RELEASE_GCC5/X64/HelloWorld.efi ~/UEFI_disk/
$ qemu-system-x86_64 -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
                     -drive format=raw,file=fat:rw:~/UEFI_disk \
                     -nographic \
                     -net none

아래와 같은 결과를 얻을 수 있다.

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 4 seconds to skip startup.nsh or any other key to continue.
Shell> fs0:
FS0:\> HelloWorld.efi
Hello World!
FS0:\>

Last updated