Project 12: UEFI Application

Build a UEFI application that runs before any operating system - displaying graphics, reading files, and interacting with UEFI services. This project introduces you to the modern boot environment that has replaced legacy BIOS on all contemporary PCs.


Quick Reference

Attribute Value
Difficulty Level 3: Advanced
Time Estimate 2 weeks
Language C (primary), Rust (alternative)
Prerequisites C programming, understanding of PE format, Projects 5-6 recommended
Key Topics UEFI, EFI_SYSTEM_TABLE, GOP, Simple File System Protocol, PE/COFF format, OVMF

Table of Contents


1. Learning Objectives

By completing this project, you will:

  1. Understand UEFI architecture: Master the fundamental concepts of the Unified Extensible Firmware Interface
  2. Work with UEFI protocols: Use GOP, Simple Text Output, Simple File System, and other core protocols
  3. Navigate the System Table: Access Boot Services, Runtime Services, and configuration tables
  4. Build PE/COFF executables: Create properly formatted UEFI applications
  5. Use UEFI memory services: Allocate memory, query the memory map, and prepare for OS handoff
  6. Handle graphics output: Display pixels and text using the Graphics Output Protocol
  7. Read files from disk: Access the EFI System Partition using file protocols
  8. Prepare for ExitBootServices: Understand the critical transition from UEFI to OS control
  9. Set up a modern UEFI development environment: Master GNU-EFI or EDK2 toolchains

2. Theoretical Foundation

2.1 Core Concepts

What is UEFI?

UEFI (Unified Extensible Firmware Interface) is the modern replacement for legacy BIOS. It provides a standardized interface between the operating system and platform firmware:

+-----------------------------------------------------------------------+
|                    UEFI vs LEGACY BIOS                                 |
+-----------------------------------------------------------------------+
|                                                                        |
|  Feature              Legacy BIOS              UEFI                    |
|  ─────────────────────────────────────────────────────────────────── |
|  Architecture         16-bit real mode         32/64-bit native mode  |
|  Boot Code            512-byte MBR             PE/COFF executable     |
|  Disk Support         MBR (2TB limit)          GPT (9.4 ZB limit)     |
|  Drivers              BIOS interrupts          Protocol-based drivers |
|  Graphics             VGA BIOS (INT 10h)       GOP (native graphics)  |
|  Security             None built-in            Secure Boot support    |
|  Extensibility        Very limited             Modular, extensible    |
|  Pre-boot Network     PXE only                 Full TCP/IP stack      |
|  User Interface       Text-only                Mouse/GUI support      |
|                                                                        |
|  Boot Process Comparison:                                              |
|                                                                        |
|  Legacy BIOS:                                                          |
|    Power On -> POST -> MBR (512B) -> Stage 2 -> Kernel                |
|                                                                        |
|  UEFI:                                                                 |
|    Power On -> SEC -> PEI -> DXE -> BDS -> \EFI\BOOT\BOOTX64.EFI      |
|                                                                        |
+-----------------------------------------------------------------------+

UEFI Boot Phases

UEFI firmware goes through several phases before your application runs:

+-----------------------------------------------------------------------+
|                    UEFI BOOT PHASES                                    |
+-----------------------------------------------------------------------+
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ SEC - Security Phase                                             │  |
|  │   - First code to run after CPU reset                           │  |
|  │   - Minimal assembly initialization                              │  |
|  │   - Establishes temporary memory (Cache-as-RAM)                  │  |
|  │   - Passes control to PEI with initial handoff                   │  |
|  └─────────────────────────┬───────────────────────────────────────┘  |
|                            │                                           |
|                            ▼                                           |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ PEI - Pre-EFI Initialization                                     │  |
|  │   - Memory initialization (DRAM training)                        │  |
|  │   - CPU initialization                                           │  |
|  │   - Creates Hand-Off Blocks (HOBs) for DXE                       │  |
|  │   - Loads DXE Foundation                                         │  |
|  └─────────────────────────┬───────────────────────────────────────┘  |
|                            │                                           |
|                            ▼                                           |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ DXE - Driver Execution Environment                               │  |
|  │   - Full UEFI services available                                 │  |
|  │   - Loads and executes UEFI drivers                              │  |
|  │   - Initializes protocols for hardware                           │  |
|  │   - Builds System Table and Boot Services                        │  |
|  └─────────────────────────┬───────────────────────────────────────┘  |
|                            │                                           |
|                            ▼                                           |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ BDS - Boot Device Selection                                      │  |
|  │   - Reads boot options from NVRAM variables                      │  |
|  │   - Presents boot menu (if configured)                           │  |
|  │   - Locates and loads boot loader (\EFI\BOOT\BOOTX64.EFI)       │  |
|  │   ─────────────────────────────────────────────────────────────  │  |
|  │   YOUR UEFI APPLICATION RUNS HERE!                               │  |
|  └─────────────────────────┬───────────────────────────────────────┘  |
|                            │                                           |
|                            ▼                                           |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ TSL - Transient System Load (Operating System Loader)            │  |
|  │   - Boot loader prepares OS environment                          │  |
|  │   - Calls ExitBootServices() to take control                     │  |
|  │   - Hands off to operating system kernel                         │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

UEFI Application Entry Point

Every UEFI application receives two parameters at entry:

+-----------------------------------------------------------------------+
|                    UEFI APPLICATION ENTRY                              |
+-----------------------------------------------------------------------+
|                                                                        |
|  EFI_STATUS EFIAPI efi_main(                                           |
|      EFI_HANDLE ImageHandle,       // Handle to this loaded image     |
|      EFI_SYSTEM_TABLE *SystemTable // Pointer to System Table         |
|  );                                                                    |
|                                                                        |
|  ImageHandle:                                                          |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ - Unique identifier for this loaded UEFI image                  │  |
|  │ - Used to install/uninstall protocols on this image             │  |
|  │ - Required for many Boot Services calls                         │  |
|  │ - Passed to protocols that need to know the caller              │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  SystemTable:                                                          |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ - Gateway to ALL UEFI services                                  │  |
|  │ - Contains pointers to:                                         │  |
|  │     - ConIn  (Simple Text Input Protocol)                       │  |
|  │     - ConOut (Simple Text Output Protocol)                      │  |
|  │     - StdErr (Error output)                                     │  |
|  │     - BootServices (memory, protocols, events, etc.)            │  |
|  │     - RuntimeServices (time, variables, reset, etc.)            │  |
|  │     - ConfigurationTable (ACPI, SMBIOS, etc.)                   │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

The EFI System Table Structure

+-----------------------------------------------------------------------+
|                    EFI_SYSTEM_TABLE STRUCTURE                          |
+-----------------------------------------------------------------------+
|                                                                        |
|  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;    ◄── Text output      |
|      EFI_HANDLE                      StandardErrorHandle;              |
|      EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;                         |
|      EFI_RUNTIME_SERVICES            *RuntimeServices;  ◄── Available |
|      EFI_BOOT_SERVICES               *BootServices;     ◄── Boot only |
|      UINTN                           NumberOfTableEntries;             |
|      EFI_CONFIGURATION_TABLE         *ConfigurationTable;              |
|  } EFI_SYSTEM_TABLE;                                                   |
|                                                                        |
|  Key Service Tables:                                                   |
|                                                                        |
|  BootServices (available until ExitBootServices):                      |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Memory Services:                                                 │  |
|  │   AllocatePool, FreePool, AllocatePages, FreePages              │  |
|  │   GetMemoryMap                                                   │  |
|  │                                                                  │  |
|  │ Protocol Services:                                               │  |
|  │   LocateProtocol, HandleProtocol, OpenProtocol                  │  |
|  │   LocateHandleBuffer                                             │  |
|  │                                                                  │  |
|  │ Image Services:                                                  │  |
|  │   LoadImage, StartImage, Exit, ExitBootServices                 │  |
|  │                                                                  │  |
|  │ Event Services:                                                  │  |
|  │   CreateEvent, SetTimer, WaitForEvent, SignalEvent              │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  RuntimeServices (available after ExitBootServices):                   |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Variable Services:                                               │  |
|  │   GetVariable, SetVariable, GetNextVariableName                 │  |
|  │                                                                  │  |
|  │ Time Services:                                                   │  |
|  │   GetTime, SetTime, GetWakeupTime, SetWakeupTime                │  |
|  │                                                                  │  |
|  │ Virtual Memory Services:                                         │  |
|  │   SetVirtualAddressMap, ConvertPointer                          │  |
|  │                                                                  │  |
|  │ Miscellaneous:                                                   │  |
|  │   ResetSystem, UpdateCapsule, QueryCapsuleCapabilities          │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

Graphics Output Protocol (GOP)

GOP provides access to the framebuffer for graphical output:

+-----------------------------------------------------------------------+
|                    GRAPHICS OUTPUT PROTOCOL                            |
+-----------------------------------------------------------------------+
|                                                                        |
|  typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {                        |
|      EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE  QueryMode;               |
|      EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE    SetMode;                 |
|      EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT         Blt;    // Block Transfer|
|      EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE        *Mode;  // Current mode  |
|  } EFI_GRAPHICS_OUTPUT_PROTOCOL;                                       |
|                                                                        |
|  Mode Structure:                                                       |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ MaxMode           - Number of supported video modes              │  |
|  │ Mode              - Current mode number                          │  |
|  │ Info              - Pointer to mode information                  │  |
|  │   ->HorizontalResolution   (e.g., 1024)                         │  |
|  │   ->VerticalResolution     (e.g., 768)                          │  |
|  │   ->PixelFormat            (RGB, BGR, Bitmask, BltOnly)         │  |
|  │   ->PixelsPerScanLine      (may be > HorizontalResolution)      │  |
|  │ SizeOfInfo        - Size of Info structure                       │  |
|  │ FrameBufferBase   - Physical address of framebuffer ◄── Key!    │  |
|  │ FrameBufferSize   - Size of framebuffer in bytes                 │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Pixel Formats:                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ PixelRedGreenBlueReserved8BitPerColor:                          │  |
|  │   Byte 0: Red, Byte 1: Green, Byte 2: Blue, Byte 3: Reserved    │  |
|  │                                                                  │  |
|  │ PixelBlueGreenRedReserved8BitPerColor:                          │  |
|  │   Byte 0: Blue, Byte 1: Green, Byte 2: Red, Byte 3: Reserved    │  |
|  │                                                                  │  |
|  │ PixelBitMask:                                                    │  |
|  │   Custom masks defined in PixelInformation                      │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Framebuffer Access:                                                   |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ // Direct pixel write                                            │  |
|  │ UINT32 *fb = (UINT32 *)gop->Mode->FrameBufferBase;              │  |
|  │ fb[y * PixelsPerScanLine + x] = 0x00FF0000; // Red pixel        │  |
|  │                                                                  │  |
|  │ // Using Blt for rectangles (more efficient)                     │  |
|  │ gop->Blt(gop, &pixel, EfiBltVideoFill,                          │  |
|  │          0, 0, x, y, width, height, 0);                         │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

Simple File System Protocol

Access files on the EFI System Partition:

+-----------------------------------------------------------------------+
|                    FILE SYSTEM PROTOCOLS                               |
+-----------------------------------------------------------------------+
|                                                                        |
|  Locating File System:                                                 |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ // Get Simple File System Protocol                               │  |
|  │ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs;                             │  |
|  │ BootServices->LocateProtocol(                                    │  |
|  │     &gEfiSimpleFileSystemProtocolGuid,                           │  |
|  │     NULL,                                                        │  |
|  │     (VOID **)&fs                                                 │  |
|  │ );                                                               │  |
|  │                                                                  │  |
|  │ // Open root directory                                           │  |
|  │ EFI_FILE_PROTOCOL *root;                                         │  |
|  │ fs->OpenVolume(fs, &root);                                       │  |
|  │                                                                  │  |
|  │ // Open a file                                                   │  |
|  │ EFI_FILE_PROTOCOL *file;                                         │  |
|  │ root->Open(root, &file, L"\\myfile.txt",                        │  |
|  │            EFI_FILE_MODE_READ, 0);                               │  |
|  │                                                                  │  |
|  │ // Read file contents                                            │  |
|  │ UINTN size = 1024;                                               │  |
|  │ CHAR8 buffer[1024];                                              │  |
|  │ file->Read(file, &size, buffer);                                 │  |
|  │                                                                  │  |
|  │ // Close file                                                    │  |
|  │ file->Close(file);                                               │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  EFI_FILE_PROTOCOL Functions:                                          |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Open      - Open or create a file                                │  |
|  │ Close     - Close a file handle                                  │  |
|  │ Delete    - Delete a file                                        │  |
|  │ Read      - Read data from file                                  │  |
|  │ Write     - Write data to file                                   │  |
|  │ SetPosition - Set file position                                  │  |
|  │ GetPosition - Get current position                               │  |
|  │ GetInfo   - Get file information                                 │  |
|  │ SetInfo   - Set file information                                 │  |
|  │ Flush     - Flush file data to device                            │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

PE/COFF Executable Format

UEFI applications are PE32+ executables (same format as Windows):

+-----------------------------------------------------------------------+
|                    PE/COFF FORMAT FOR UEFI                             |
+-----------------------------------------------------------------------+
|                                                                        |
|  File Structure:                                                       |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ DOS Header (64 bytes)                                            │  |
|  │   e_magic = "MZ" (0x5A4D)                                       │  |
|  │   e_lfanew = offset to PE header                                │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ DOS Stub (optional, usually skipped)                             │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ PE Signature ("PE\0\0" = 0x00004550)                            │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ COFF File Header (20 bytes)                                      │  |
|  │   Machine = 0x8664 (x86-64) or 0x14c (i386)                     │  |
|  │   NumberOfSections                                               │  |
|  │   SizeOfOptionalHeader                                           │  |
|  │   Characteristics = 0x2022 (EFI Application)                     │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ Optional Header (PE32+)                                          │  |
|  │   Magic = 0x20B (PE32+) or 0x10B (PE32)                         │  |
|  │   AddressOfEntryPoint = efi_main                                 │  |
|  │   ImageBase = 0                                                  │  |
|  │   SectionAlignment = 0x1000                                      │  |
|  │   FileAlignment = 0x200                                          │  |
|  │   Subsystem = 0x0A (EFI Application)  ◄── KEY FOR UEFI          │  |
|  │              0x0B (EFI Boot Service Driver)                      │  |
|  │              0x0C (EFI Runtime Driver)                           │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ Section Headers                                                  │  |
|  │   .text  - Code section                                          │  |
|  │   .data  - Initialized data                                      │  |
|  │   .bss   - Uninitialized data                                    │  |
|  │   .reloc - Relocation data (required for UEFI)                   │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ Section Data                                                     │  |
|  │   (actual code and data)                                         │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Key Requirements for UEFI:                                            |
|  - Subsystem must be EFI Application (0x0A)                           |
|  - Must include .reloc section (position-independent)                  |
|  - Entry point follows UEFI calling convention                         |
|  - No external library dependencies (freestanding)                     |
|                                                                        |
+-----------------------------------------------------------------------+

Memory Map and ExitBootServices

The critical transition from UEFI to OS:

+-----------------------------------------------------------------------+
|                    MEMORY MAP AND EXITBOOTSERVICES                     |
+-----------------------------------------------------------------------+
|                                                                        |
|  Getting the Memory Map:                                               |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ UINTN MemoryMapSize = 0;                                         │  |
|  │ EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL;                         │  |
|  │ UINTN MapKey;                                                    │  |
|  │ UINTN DescriptorSize;                                            │  |
|  │ UINT32 DescriptorVersion;                                        │  |
|  │                                                                  │  |
|  │ // First call: get required buffer size                          │  |
|  │ Status = BS->GetMemoryMap(&MemoryMapSize, MemoryMap,            │  |
|  │                           &MapKey, &DescriptorSize,              │  |
|  │                           &DescriptorVersion);                   │  |
|  │ // Returns EFI_BUFFER_TOO_SMALL, MemoryMapSize now valid         │  |
|  │                                                                  │  |
|  │ // Allocate buffer (add extra for allocation itself)             │  |
|  │ MemoryMapSize += 2 * DescriptorSize;                             │  |
|  │ BS->AllocatePool(EfiLoaderData, MemoryMapSize,                   │  |
|  │                  (VOID **)&MemoryMap);                           │  |
|  │                                                                  │  |
|  │ // Second call: get actual memory map                            │  |
|  │ BS->GetMemoryMap(&MemoryMapSize, MemoryMap,                      │  |
|  │                  &MapKey, &DescriptorSize,                       │  |
|  │                  &DescriptorVersion);                            │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Memory Descriptor Structure:                                          |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ typedef struct {                                                 │  |
|  │     UINT32 Type;           // Memory type                        │  |
|  │     EFI_PHYSICAL_ADDRESS PhysicalStart;                          │  |
|  │     EFI_VIRTUAL_ADDRESS VirtualStart;                            │  |
|  │     UINT64 NumberOfPages;  // 4KB pages                          │  |
|  │     UINT64 Attribute;      // Memory attributes                  │  |
|  │ } EFI_MEMORY_DESCRIPTOR;                                         │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Memory Types:                                                         |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ EfiReservedMemoryType      - Not usable                         │  |
|  │ EfiLoaderCode              - UEFI app code                      │  |
|  │ EfiLoaderData              - UEFI app data                      │  |
|  │ EfiBootServicesCode        - Boot Services code                 │  |
|  │ EfiBootServicesData        - Boot Services data                 │  |
|  │ EfiRuntimeServicesCode     - Runtime Services code ◄── Keep!    │  |
|  │ EfiRuntimeServicesData     - Runtime Services data ◄── Keep!    │  |
|  │ EfiConventionalMemory      - Free memory ◄── Use this!          │  |
|  │ EfiUnusableMemory          - Memory errors                      │  |
|  │ EfiACPIReclaimMemory       - ACPI tables (reclaimable)          │  |
|  │ EfiACPIMemoryNVS           - ACPI firmware preserved            │  |
|  │ EfiMemoryMappedIO          - Hardware MMIO                      │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  ExitBootServices:                                                     |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ // CRITICAL: Must call with CURRENT MapKey                       │  |
|  │ // The MapKey changes with any memory operation!                 │  |
|  │                                                                  │  |
|  │ Status = BS->ExitBootServices(ImageHandle, MapKey);              │  |
|  │                                                                  │  |
|  │ if (Status == EFI_INVALID_PARAMETER) {                           │  |
|  │     // MapKey was stale, get new memory map and try again        │  |
|  │     BS->GetMemoryMap(...);  // Get fresh map                     │  |
|  │     BS->ExitBootServices(ImageHandle, MapKey);                   │  |
|  │ }                                                                │  |
|  │                                                                  │  |
|  │ // After this point:                                             │  |
|  │ // - BootServices pointer is INVALID                             │  |
|  │ // - Only RuntimeServices can be used                            │  |
|  │ // - You control the machine completely                          │  |
|  │ // - Timer interrupts stop                                       │  |
|  │ // - You must set up your own interrupt handling                 │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

2.2 Why This Matters

Understanding UEFI is essential because:

  1. Modern boot standard: All new PCs use UEFI (since ~2012)
  2. OS development: Modern bootloaders must understand UEFI
  3. Security: Secure Boot is UEFI-based
  4. Firmware development: BIOS/UEFI engineering is a career path
  5. Pre-OS tools: Diagnostics, disk utilities, installers run as UEFI apps
  6. Bootkit/rootkit analysis: Security research requires UEFI knowledge
  7. Virtual machines: Modern VMs use UEFI firmware (OVMF)

2.3 Historical Context

+-----------------------------------------------------------------------+
|                    EVOLUTION FROM BIOS TO UEFI                         |
+-----------------------------------------------------------------------+
|                                                                        |
|  1981: IBM PC BIOS                                                     |
|  ├── 16-bit real mode interface                                       |
|  ├── Interrupt-based services (INT 10h, 13h, 16h...)                  |
|  └── MBR boot with 512-byte limitation                                 |
|                                                                        |
|  1998: Intel Boot Initiative (IBI)                                     |
|  ├── Response to Itanium's inability to run real mode                 |
|  └── Early prototype of what became EFI                                |
|                                                                        |
|  2000: Extensible Firmware Interface (EFI) 1.0                         |
|  ├── Intel specification for Itanium                                  |
|  ├── C-language interface, no real mode                               |
|  └── 64-bit native support                                             |
|                                                                        |
|  2005: Unified EFI Forum formed                                        |
|  ├── Industry consortium (Intel, AMD, Microsoft, Apple, Dell...)      |
|  └── EFI becomes UEFI (unified)                                        |
|                                                                        |
|  2006: UEFI 2.0                                                        |
|  ├── x86 and x64 support (not just Itanium)                           |
|  ├── Driver model improvements                                         |
|  └── Network boot enhancements                                         |
|                                                                        |
|  2011: UEFI 2.3.1 with Secure Boot                                     |
|  ├── Cryptographic signature verification                              |
|  ├── Required for Windows 8 logo                                       |
|  └── Controversial for Linux users initially                           |
|                                                                        |
|  2013-2020: UEFI 2.4 - 2.8                                             |
|  ├── ARM architecture support                                          |
|  ├── HTTP boot                                                         |
|  ├── TPM integration improvements                                      |
|  └── Memory protection enhancements                                    |
|                                                                        |
|  Today: UEFI is standard on all x86/ARM PCs                            |
|  ├── Legacy BIOS compatibility (CSM) being phased out                  |
|  └── Windows 11 requires UEFI Secure Boot and TPM                      |
|                                                                        |
+-----------------------------------------------------------------------+

2.4 Common Misconceptions

Misconception Reality
“UEFI is just new BIOS” UEFI is a complete replacement with different architecture, not an evolution
“UEFI apps must be in C” Any language producing PE/COFF can work (Rust, Assembly, Pascal)
“ExitBootServices means UEFI is gone” RuntimeServices persist, firmware still handles SMM, ACPI
“GOP replaces VGA” GOP provides modern graphics; some UEFI still offer CSM with VGA
“Secure Boot prevents custom code” You can enroll your own keys or disable Secure Boot
“UEFI apps are hard to debug” OVMF with GDB provides excellent debugging

3. Project Specification

3.1 What You Will Build

A complete UEFI application that demonstrates core UEFI capabilities:

  1. Console output: Display text using Simple Text Output Protocol
  2. Graphics display: Draw to screen using Graphics Output Protocol
  3. System information: Query and display UEFI version, memory, and screen info
  4. File reading: Read and display contents of a file from the ESP
  5. Keyboard input: Handle user input through console protocols
  6. Clean exit: Properly return to UEFI shell or boot manager

3.2 Functional Requirements

Requirement Description
FR-1 Build as a valid PE32+ executable with EFI Application subsystem
FR-2 Boot successfully in QEMU with OVMF firmware
FR-3 Display formatted text output using ConOut
FR-4 Query and display screen resolution using GOP
FR-5 Draw graphical elements (rectangle, colors) using GOP framebuffer
FR-6 Display system memory information from memory map
FR-7 Read and display contents of a configuration file from ESP
FR-8 Wait for user keypress before exiting

3.3 Non-Functional Requirements

Requirement Description
NFR-1 Code compiles with GNU-EFI or EDK2 toolchain
NFR-2 Application size under 100KB
NFR-3 Works on both x86_64 UEFI and can be adapted for i386
NFR-4 Well-commented code explaining UEFI concepts
NFR-5 Makefile for reproducible builds

3.4 Example Output

$ qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:esp

# UEFI application displays:
╔══════════════════════════════════════╗
║       MY UEFI APPLICATION v1.0       ║
╠══════════════════════════════════════╣
║  UEFI Version: 2.70                  ║
║  Firmware: EDK II                    ║
║  Screen: 1024x768 @ 32bpp            ║
║  Pixel Format: BGR                   ║
╠══════════════════════════════════════╣
║  Memory Map Summary:                 ║
║    Conventional: 127 MB              ║
║    Reserved: 1 MB                    ║
║    ACPI: 64 KB                       ║
╠══════════════════════════════════════╣
║  Config file contents:               ║
║    timeout=5                         ║
║    resolution=1024x768               ║
╚══════════════════════════════════════╝

[A colored rectangle appears on screen]

Press any key to exit...

3.5 Real World Outcome

After completing this project, you will have:

  1. Working UEFI application that demonstrates pre-OS programming
  2. Understanding of UEFI protocols essential for OS development
  3. Modern boot environment skills applicable to bootloader development
  4. Portfolio piece showing systems programming expertise
  5. Foundation for writing OS loaders, UEFI drivers, or pre-boot tools

4. Solution Architecture

4.1 High-Level Design

+-----------------------------------------------------------------------+
|                    UEFI APPLICATION STRUCTURE                          |
+-----------------------------------------------------------------------+
|                                                                        |
|  ESP (EFI System Partition - FAT32):                                   |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ /EFI/                                                            │  |
|  │ └── BOOT/                                                        │  |
|  │     └── BOOTX64.EFI        (Your application, renamed)          │  |
|  │ /config.txt                 (Sample config file to read)         │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Application Flow:                                                     |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │                         efi_main()                               │  |
|  │                             │                                    │  |
|  │    ┌────────────────────────┼────────────────────────┐          │  |
|  │    │                        │                        │          │  |
|  │    ▼                        ▼                        ▼          │  |
|  │ Initialize            Query System             Set Up            │  |
|  │ Globals               Information              Graphics          │  |
|  │ (ST, BS, RT)          (version, memory)        (locate GOP)      │  |
|  │    │                        │                        │          │  |
|  │    └────────────────────────┼────────────────────────┘          │  |
|  │                             │                                    │  |
|  │                             ▼                                    │  |
|  │                      Display UI Box                              │  |
|  │                      (text output)                               │  |
|  │                             │                                    │  |
|  │                             ▼                                    │  |
|  │                      Draw Graphics                               │  |
|  │                      (colored rectangle)                         │  |
|  │                             │                                    │  |
|  │                             ▼                                    │  |
|  │                      Read Config File                            │  |
|  │                      (file protocol)                             │  |
|  │                             │                                    │  |
|  │                             ▼                                    │  |
|  │                      Wait for Key                                │  |
|  │                      (ConIn)                                     │  |
|  │                             │                                    │  |
|  │                             ▼                                    │  |
|  │                      Return EFI_SUCCESS                          │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

4.2 Key Components

+-----------------------------------------------------------------------+
|                    COMPONENT INTERACTION                               |
+-----------------------------------------------------------------------+
|                                                                        |
|  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐               |
|  │   main.c    │    │  console.c  │    │ graphics.c  │               |
|  │             │───▶│             │    │             │               |
|  │ Entry point │    │ Text output │    │ GOP access  │               |
|  │ Init code   │    │ Input       │    │ Framebuffer │               |
|  └─────────────┘    └─────────────┘    └─────────────┘               |
|         │                  │                  │                       |
|         │                  │                  │                       |
|         ▼                  ▼                  ▼                       |
|  ┌─────────────────────────────────────────────────────┐             |
|  │                  EFI_SYSTEM_TABLE                    │             |
|  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐   │             |
|  │  │  ConOut  │  │  ConIn   │  │  BootServices    │   │             |
|  │  └──────────┘  └──────────┘  │  ├─LocateProtocol│   │             |
|  │                              │  ├─GetMemoryMap  │   │             |
|  │                              │  └─AllocatePool  │   │             |
|  │                              └──────────────────┘   │             |
|  └─────────────────────────────────────────────────────┘             |
|                          │                                            |
|                          ▼                                            |
|  ┌─────────────────────────────────────────────────────┐             |
|  │                    UEFI Protocols                    │             |
|  │  ┌──────────────┐  ┌──────────────────────────────┐ │             |
|  │  │     GOP      │  │  Simple File System Protocol  │ │             |
|  │  │ Framebuffer  │  │  ├─ OpenVolume               │ │             |
|  │  │ Resolution   │  │  └─ EFI_FILE_PROTOCOL        │ │             |
|  │  └──────────────┘  │      ├─ Open                 │ │             |
|  │                    │      ├─ Read                 │ │             |
|  │                    │      └─ Close                │ │             |
|  │                    └──────────────────────────────┘ │             |
|  └─────────────────────────────────────────────────────┘             |
|                                                                        |
|  Files:                                                                |
|  - main.c:      Entry point, initialization, main logic               |
|  - console.c:   Text output helper functions                          |
|  - graphics.c:  GOP wrapper, drawing functions                        |
|  - file.c:      File reading functions                                |
|  - Makefile:    Build system                                          |
|                                                                        |
+-----------------------------------------------------------------------+

4.3 Data Structures

Global State

// Global pointers (initialized in efi_main)
EFI_SYSTEM_TABLE *gST;           // System Table
EFI_BOOT_SERVICES *gBS;          // Boot Services
EFI_RUNTIME_SERVICES *gRT;       // Runtime Services
EFI_HANDLE gImageHandle;         // Our image handle

// Graphics state
EFI_GRAPHICS_OUTPUT_PROTOCOL *gGOP;  // Graphics protocol
UINT32 *gFramebuffer;                // Framebuffer pointer
UINT32 gScreenWidth;                 // Horizontal resolution
UINT32 gScreenHeight;                // Vertical resolution
UINT32 gPixelsPerLine;               // Stride (may differ from width)

System Information Structure

typedef struct {
    UINT16 UefiMajor;
    UINT16 UefiMinor;
    CHAR16 *FirmwareVendor;
    UINT32 FirmwareRevision;
    UINT64 TotalMemory;
    UINT64 FreeMemory;
    UINT32 ScreenWidth;
    UINT32 ScreenHeight;
    UINT32 PixelFormat;
} SYSTEM_INFO;

4.4 Algorithm Overview

+-----------------------------------------------------------------------+
|                    EXECUTION FLOW                                      |
+-----------------------------------------------------------------------+
|                                                                        |
|  1. Entry (UEFI calls efi_main with ImageHandle, SystemTable)         |
|     ├── Store SystemTable globally                                     |
|     ├── Extract BootServices, RuntimeServices                          |
|     └── Store ImageHandle globally                                     |
|                                                                        |
|  2. Query System Information                                           |
|     ├── Read UEFI version from SystemTable->Hdr.Revision               |
|     ├── Read firmware vendor string                                    |
|     └── Calculate memory from GetMemoryMap()                           |
|                                                                        |
|  3. Initialize Graphics                                                |
|     ├── LocateProtocol(&gEfiGraphicsOutputProtocolGuid, ...)          |
|     ├── Read current mode information                                  |
|     ├── Store framebuffer base address                                 |
|     └── Optionally set desired resolution mode                         |
|                                                                        |
|  4. Display Text UI                                                    |
|     ├── Clear screen (ConOut->ClearScreen)                             |
|     ├── Set text color (ConOut->SetAttribute)                          |
|     └── Print formatted information (ConOut->OutputString)             |
|                                                                        |
|  5. Draw Graphics                                                      |
|     ├── Calculate rectangle position                                   |
|     ├── Write directly to framebuffer                                  |
|     └── Or use Blt() for block transfer                                |
|                                                                        |
|  6. Read Configuration File                                            |
|     ├── LocateProtocol(&gEfiSimpleFileSystemProtocolGuid, ...)        |
|     ├── OpenVolume() to get root directory                             |
|     ├── Open() the config file                                         |
|     ├── Read() file contents                                           |
|     ├── Display contents via ConOut                                    |
|     └── Close() file handle                                            |
|                                                                        |
|  7. Wait for User Input                                                |
|     ├── Print prompt                                                   |
|     └── ConIn->ReadKeyStroke() or WaitForEvent()                       |
|                                                                        |
|  8. Exit                                                               |
|     └── Return EFI_SUCCESS                                             |
|                                                                        |
+-----------------------------------------------------------------------+

5. Implementation Guide

5.1 Environment Setup

GNU-EFI provides a simpler interface for building UEFI applications:

# Install GNU-EFI
# On Ubuntu/Debian:
sudo apt install gnu-efi

# On Fedora:
sudo dnf install gnu-efi gnu-efi-devel

# On macOS (via Homebrew):
brew install gnu-efi

# Install QEMU and OVMF
# Ubuntu/Debian:
sudo apt install qemu-system-x86 ovmf

# Fedora:
sudo dnf install qemu-system-x86 edk2-ovmf

# macOS:
brew install qemu
# Download OVMF separately

# Locate OVMF firmware
# Ubuntu: /usr/share/OVMF/OVMF_CODE.fd
# Fedora: /usr/share/edk2/ovmf/OVMF_CODE.fd

Option B: EDK2 (Industry Standard)

EDK2 is the reference implementation used by firmware vendors:

# Clone EDK2
git clone https://github.com/tianocore/edk2.git
cd edk2
git submodule update --init

# Set up build environment
make -C BaseTools
source edksetup.sh

# Configure target
# Edit Conf/target.txt:
#   ACTIVE_PLATFORM = MdeModulePkg/MdeModulePkg.dsc
#   TARGET_ARCH = X64
#   TOOL_CHAIN_TAG = GCC5

OVMF Setup

# Create working directory for ESP
mkdir -p esp/EFI/BOOT

# Copy your compiled application
cp myapp.efi esp/EFI/BOOT/BOOTX64.EFI

# Create test config file
echo "timeout=5" > esp/config.txt
echo "resolution=1024x768" >> esp/config.txt

# Run QEMU with OVMF
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -net none \
    -serial stdio

5.2 Project Structure

uefi-app/
├── Makefile
├── src/
│   ├── main.c           # Entry point and main logic
│   ├── console.c        # Text output functions
│   ├── console.h
│   ├── graphics.c       # GOP wrapper functions
│   ├── graphics.h
│   ├── file.c           # File reading functions
│   ├── file.h
│   └── util.c           # Helper functions
├── esp/                  # EFI System Partition contents
│   ├── EFI/
│   │   └── BOOT/
│   │       └── BOOTX64.EFI  (copied from build)
│   └── config.txt
├── scripts/
│   └── run-qemu.sh      # QEMU launch script
└── README.md

Makefile for GNU-EFI

# Makefile for GNU-EFI UEFI Application

ARCH            = x86_64
TARGET          = myapp.efi

# GNU-EFI paths (adjust for your system)
GNUEFI_INC      = /usr/include/efi
GNUEFI_LIB      = /usr/lib
EFI_CRT         = $(GNUEFI_LIB)/crt0-efi-$(ARCH).o
EFI_LDS         = $(GNUEFI_LIB)/elf_$(ARCH)_efi.lds

# Compiler settings
CC              = gcc
LD              = ld
OBJCOPY         = objcopy

CFLAGS          = -I$(GNUEFI_INC) -I$(GNUEFI_INC)/$(ARCH) \
                  -I$(GNUEFI_INC)/protocol \
                  -DEFI_FUNCTION_WRAPPER \
                  -fno-stack-protector -fpic -fshort-wchar \
                  -mno-red-zone -Wall -c

LDFLAGS         = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \
                  -Bsymbolic -L $(GNUEFI_LIB) $(EFI_CRT)

LIBS            = -lefi -lgnuefi

# Source files
SRCS            = src/main.c src/console.c src/graphics.c src/file.c
OBJS            = $(SRCS:.c=.o)

# Targets
all: $(TARGET)

%.o: %.c
	$(CC) $(CFLAGS) $< -o $@

myapp.so: $(OBJS)
	$(LD) $(LDFLAGS) $(OBJS) -o $@ $(LIBS)

$(TARGET): myapp.so
	$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \
	           -j .dynsym -j .rel -j .rela -j .reloc \
	           --target=efi-app-$(ARCH) $< $@

esp: $(TARGET)
	mkdir -p esp/EFI/BOOT
	cp $(TARGET) esp/EFI/BOOT/BOOTX64.EFI
	echo "timeout=5" > esp/config.txt

run: esp
	qemu-system-x86_64 \
	    -bios /usr/share/OVMF/OVMF_CODE.fd \
	    -drive format=raw,file=fat:rw:esp \
	    -net none

debug: esp
	qemu-system-x86_64 \
	    -bios /usr/share/OVMF/OVMF_CODE.fd \
	    -drive format=raw,file=fat:rw:esp \
	    -net none \
	    -s -S &
	gdb -ex "target remote :1234"

clean:
	rm -f src/*.o myapp.so $(TARGET)
	rm -rf esp

.PHONY: all esp run debug clean

5.3 The Core Question You’re Answering

“How do you write code that runs directly on modern PC firmware before any operating system, using the standardized UEFI interface?”

This involves understanding:

  • The UEFI execution environment and its differences from OS environments
  • Protocol-based access to hardware (graphics, storage, console)
  • PE/COFF executable format requirements
  • Memory management in pre-OS environment
  • The transition from UEFI to OS control

5.4 Concepts You Must Understand First

Concept Self-Assessment Question Reference
EFI_SYSTEM_TABLE What services does SystemTable->BootServices provide? UEFI Spec Ch. 4
UEFI Protocols How do you locate and use a protocol by GUID? UEFI Spec Ch. 7
PE/COFF Format What subsystem value indicates a UEFI application? PE/COFF Spec
UEFI Strings Why does UEFI use CHAR16 (UCS-2) for strings? UEFI Spec Ch. 2
Memory Map What memory types can you use after ExitBootServices? UEFI Spec Ch. 7

5.5 Questions to Guide Your Design

System Initialization:

  • What is the first thing you should do with the SystemTable pointer?
  • How do you safely store global references to UEFI services?
  • What happens if you call BootServices after ExitBootServices?

Graphics:

  • How do you enumerate available video modes?
  • What’s the difference between PixelsPerScanLine and HorizontalResolution?
  • When would you use Blt() vs direct framebuffer access?

File Access:

  • How do you access files on the boot partition?
  • What character encoding do filenames use?
  • How do you handle file not found errors?

Memory:

  • Why does GetMemoryMap require two calls?
  • What’s a MapKey and why must it be current for ExitBootServices?
  • Which memory types are safe for OS use?

5.6 Thinking Exercise

Before coding, trace through this sequence:

EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle,
                           EFI_SYSTEM_TABLE *SystemTable) {
    EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
    EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

    // Step 1: Locate GOP
    EFI_STATUS status = SystemTable->BootServices->LocateProtocol(
        &gopGuid, NULL, (VOID **)&gop);

    // Step 2: Get framebuffer info
    UINT32 width = gop->Mode->Info->HorizontalResolution;
    UINT32 height = gop->Mode->Info->VerticalResolution;
    UINT32 *fb = (UINT32 *)gop->Mode->FrameBufferBase;

    // Step 3: Draw red pixel at (100, 100)
    fb[100 * gop->Mode->Info->PixelsPerScanLine + 100] = 0x00FF0000;

    return EFI_SUCCESS;
}

Questions:

  1. Why use LocateProtocol instead of just casting an address?
  2. Why multiply by PixelsPerScanLine instead of width?
  3. What’s the pixel format if 0x00FF0000 displays as red?
  4. What happens if GOP isn’t available (status != EFI_SUCCESS)?

5.7 Hints in Layers

Hint 1: Starting Point (Conceptual Direction)

Your application needs to:

  1. Store global references to SystemTable services
  2. Initialize console (already available via ConOut)
  3. Locate GOP for graphics
  4. Locate Simple File System for file access
  5. Query and display information
  6. Wait for input before exiting

Start with console output, then add graphics, then file access.

Hint 2: Next Level (More Specific Guidance)

// main.c structure
#include <efi.h>
#include <efilib.h>

EFI_SYSTEM_TABLE *gST;
EFI_BOOT_SERVICES *gBS;

EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle,
                           EFI_SYSTEM_TABLE *SystemTable) {
    // Initialize globals
    gST = SystemTable;
    gBS = SystemTable->BootServices;

    // Initialize library (GNU-EFI specific)
    InitializeLib(ImageHandle, SystemTable);

    // Clear screen
    gST->ConOut->ClearScreen(gST->ConOut);

    // Print welcome message
    Print(L"Hello from UEFI!\n");

    // TODO: Locate GOP
    // TODO: Draw graphics
    // TODO: Read file

    // Wait for key
    Print(L"Press any key to exit...\n");
    WaitForSingleEvent(gST->ConIn->WaitForKey, 0);

    return EFI_SUCCESS;
}

Hint 3: Technical Details (Approach/Pseudocode)

Locating and using GOP:

EFI_STATUS InitGraphics(void) {
    EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
    EFI_STATUS status;

    status = gBS->LocateProtocol(&gopGuid, NULL, (VOID **)&gGOP);
    if (EFI_ERROR(status)) {
        Print(L"Failed to locate GOP\n");
        return status;
    }

    // Store framebuffer info
    gScreenWidth = gGOP->Mode->Info->HorizontalResolution;
    gScreenHeight = gGOP->Mode->Info->VerticalResolution;
    gPixelsPerLine = gGOP->Mode->Info->PixelsPerScanLine;
    gFramebuffer = (UINT32 *)gGOP->Mode->FrameBufferBase;

    Print(L"Screen: %dx%d, Stride: %d\n",
          gScreenWidth, gScreenHeight, gPixelsPerLine);

    return EFI_SUCCESS;
}

void DrawRectangle(UINT32 x, UINT32 y, UINT32 w, UINT32 h, UINT32 color) {
    for (UINT32 row = y; row < y + h && row < gScreenHeight; row++) {
        for (UINT32 col = x; col < x + w && col < gScreenWidth; col++) {
            gFramebuffer[row * gPixelsPerLine + col] = color;
        }
    }
}

Reading a file:

EFI_STATUS ReadConfigFile(CHAR16 *filename, CHAR8 *buffer, UINTN *size) {
    EFI_GUID fsGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs;
    EFI_FILE_PROTOCOL *root, *file;
    EFI_STATUS status;

    // Get file system protocol
    status = gBS->LocateProtocol(&fsGuid, NULL, (VOID **)&fs);
    if (EFI_ERROR(status)) return status;

    // Open root volume
    status = fs->OpenVolume(fs, &root);
    if (EFI_ERROR(status)) return status;

    // Open file
    status = root->Open(root, &file, filename,
                        EFI_FILE_MODE_READ, 0);
    if (EFI_ERROR(status)) {
        root->Close(root);
        return status;
    }

    // Read file
    status = file->Read(file, size, buffer);

    // Cleanup
    file->Close(file);
    root->Close(root);

    return status;
}

Hint 4: Tools/Debugging (Verification Methods)

# Verify PE format of compiled EFI
file myapp.efi
# Should show: PE32+ executable (EFI application)

objdump -x myapp.efi | grep Subsystem
# Should show: Subsystem 0000000a (EFI application)

# Debug with QEMU and GDB
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -s -S &

gdb
(gdb) target remote :1234
(gdb) symbol-file myapp.efi
(gdb) break efi_main
(gdb) continue

# QEMU monitor commands (Ctrl+Alt+2)
info registers
x/20i $rip
xp/10x 0xfd000000   # Framebuffer (if GOP at this address)

# View UEFI debug output (if compiled with debug prints)
# Add -serial stdio to QEMU command

5.8 The Interview Questions They’ll Ask

  1. “Explain the UEFI boot process”
    • SEC initializes cache-as-RAM, PEI does memory init
    • DXE loads drivers and builds protocol database
    • BDS selects boot device, loads bootloader/OS
    • UEFI app receives ImageHandle and SystemTable
  2. “What’s the difference between Boot Services and Runtime Services?”
    • Boot Services: Available until ExitBootServices(), includes memory allocation, protocol access, image loading
    • Runtime Services: Available to OS, includes GetTime, GetVariable, ResetSystem
    • After ExitBootServices, Boot Services pointers become invalid
  3. “How does UEFI handle graphics vs legacy BIOS?”
    • BIOS: VGA BIOS + INT 10h, limited modes, real mode only
    • UEFI: Graphics Output Protocol, native resolution, direct framebuffer access
    • GOP provides mode enumeration, Blt for block transfers
  4. “What is the memory map used for?”
    • Describes all physical memory and its type
    • OS needs it to know what memory is usable
    • MapKey must be current for ExitBootServices to succeed
    • Types indicate if memory can be reclaimed or must be preserved
  5. “Why are UEFI applications PE/COFF executables?”
    • Industry standard format, well-documented
    • Supports position-independent code (required for relocation)
    • Toolchain widely available (GCC, LLVM can produce PE)
    • Windows compatibility simplified UEFI adoption

5.9 Books That Will Help

Topic Book/Resource Section
UEFI Specification UEFI Forum Entire document
Boot Process “UEFI Boot Flow” Intel documentation
EDK2 Development “EDK2 Build Specification” TianoCore wiki
PE/COFF Format Microsoft PE/COFF Spec Headers and sections
OS Development “Operating Systems: From 0 to 1” Boot chapter
Systems Programming “Modern Operating Systems” Boot section

5.10 Implementation Phases

Phase 1: Environment Setup (1-2 days)

  • Install GNU-EFI or EDK2
  • Install QEMU and OVMF
  • Compile and run “Hello UEFI” example
  • Verify ESP structure works

Phase 2: Console and System Info (2-3 days)

  • Implement text output functions
  • Query UEFI version and firmware info
  • Display formatted system information
  • Handle text colors and positioning

Phase 3: Graphics (2-3 days)

  • Locate GOP successfully
  • Query available video modes
  • Draw simple shapes to framebuffer
  • Implement color utilities

Phase 4: File System (2-3 days)

  • Locate Simple File System Protocol
  • Open and read text file
  • Display file contents
  • Handle errors gracefully

Phase 5: Polish and Extension (2-3 days)

  • Add decorative UI elements
  • Implement keyboard navigation
  • Clean up code and comments
  • Document the project

5.11 Key Implementation Decisions

Decision Option A Option B Recommendation
Toolchain GNU-EFI EDK2 GNU-EFI for learning (simpler), EDK2 for production
String handling Use Print() Custom output Use Print() from library, simpler
Graphics Direct framebuffer Use Blt() Direct FB for learning, Blt for efficiency
Memory Stack only AllocatePool Stack for simple apps, Pool for larger data
Error handling Return immediately Print and continue Print error, then decide based on severity

6. Testing Strategy

Verification Points

#!/bin/bash
# test_uefi_app.sh

echo "Test 1: Verify EFI executable format"
file myapp.efi
# Expected: PE32+ executable (EFI application)

echo "Test 2: Check subsystem"
objdump -x myapp.efi | grep -i subsystem
# Expected: 0000000a (EFI application)

echo "Test 3: Verify relocation section exists"
objdump -h myapp.efi | grep reloc
# Expected: .reloc section present

echo "Test 4: ESP structure"
ls -la esp/EFI/BOOT/
# Expected: BOOTX64.EFI present

echo "Test 5: Config file exists"
cat esp/config.txt
# Expected: Configuration content

echo "Test 6: Run in QEMU (manual verification)"
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -net none

Automated Testing

#!/bin/bash
# automated_test.sh - Run QEMU with timeout and check serial output

TIMEOUT=30
OUTPUT=$(timeout $TIMEOUT qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -net none \
    -serial stdio \
    -display none 2>&1)

# Check for expected output
if echo "$OUTPUT" | grep -q "MY UEFI APPLICATION"; then
    echo "PASS: Application title displayed"
else
    echo "FAIL: Application title not found"
fi

if echo "$OUTPUT" | grep -q "Screen:"; then
    echo "PASS: Screen info displayed"
else
    echo "FAIL: Screen info not found"
fi

if echo "$OUTPUT" | grep -q "Press any key"; then
    echo "PASS: Prompt displayed"
else
    echo "FAIL: Prompt not found"
fi

GDB Debug Session

# Terminal 1: Start QEMU with GDB server
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -s -S

# Terminal 2: Connect GDB
gdb
(gdb) target remote :1234
(gdb) # UEFI apps are loaded at varying addresses
(gdb) # Set breakpoint when you find load address
(gdb) break *0x6000000  # Example, adjust as needed
(gdb) continue
(gdb) info registers
(gdb) x/10i $rip

7. Common Pitfalls & Debugging

Pitfall 1: Application Not Found at Boot

Symptom: UEFI shell appears instead of your application.

Cause: Incorrect filename or path on ESP.

Fix:

# UEFI looks for these paths:
# \EFI\BOOT\BOOTX64.EFI (for x64)
# \EFI\BOOT\BOOTIA32.EFI (for x86)

mkdir -p esp/EFI/BOOT
cp myapp.efi esp/EFI/BOOT/BOOTX64.EFI  # Exact name matters!

Pitfall 2: GOP Not Found

Symptom: LocateProtocol returns EFI_NOT_FOUND for GOP.

Cause: OVMF may not initialize GOP in all configurations.

Fix:

# Use OVMF with graphics support
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -vga std  # Ensure VGA device is present

# Or try virtio-vga
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -device virtio-vga

Pitfall 3: File Not Found

Symptom: File Open() returns EFI_NOT_FOUND.

Cause: Wrong path format or missing file.

Fix:

// UEFI uses backslashes and UCS-2 strings
CHAR16 *filename = L"\\config.txt";     // Root of ESP
// NOT: "config.txt" or "/config.txt"

// Alternative: Relative to ESP root
CHAR16 *filename = L"config.txt";       // May work

Pitfall 4: Pixel Colors Wrong

Symptom: Colors appear inverted or wrong.

Cause: Assuming RGB when format is BGR (or vice versa).

Fix:

// Check pixel format before drawing
if (gGOP->Mode->Info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
    // BGR format: Blue in byte 0
    color = (blue << 0) | (green << 8) | (red << 16);
} else if (gGOP->Mode->Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
    // RGB format: Red in byte 0
    color = (red << 0) | (green << 8) | (blue << 16);
}

Pitfall 5: Crash After ExitBootServices

Symptom: System hangs or crashes after calling ExitBootServices.

Cause: Using BootServices or stale pointers after exit.

Fix:

// After ExitBootServices:
// - Do NOT use gBS (BootServices)
// - Do NOT use Print() or ConOut
// - Only RuntimeServices are valid
// - You must have your own IDT/GDT

// Before ExitBootServices:
EFI_STATUS status = gBS->ExitBootServices(ImageHandle, MapKey);
if (status == EFI_INVALID_PARAMETER) {
    // MapKey was stale, memory map changed
    // Must get fresh memory map and try again
    gBS->GetMemoryMap(&MemMapSize, MemMap, &MapKey, ...);
    status = gBS->ExitBootServices(ImageHandle, MapKey);
}
// After this point, you're on your own!

Pitfall 6: Wide String Issues

Symptom: Garbage characters or crashes with strings.

Cause: Mixing CHAR8 and CHAR16, or incorrect string format.

Fix:

// UEFI uses CHAR16 (UCS-2) for most strings
Print(L"Hello\n");           // Correct: L prefix for wide strings
// NOT: Print("Hello\n");    // Wrong: narrow string

// For ASCII data from files, convert or use %a format
CHAR8 fileData[100];
file->Read(file, &size, fileData);
Print(L"File contents: %a\n", fileData);  // %a for ASCII string

Debugging with UEFI Shell

# Boot to UEFI shell (don't auto-boot your app)
# Remove BOOTX64.EFI or rename it

# In UEFI shell:
Shell> fs0:                    # Switch to ESP
Shell> ls                      # List files
Shell> myapp.efi               # Run your app
Shell> dh -p GOP               # List handles with GOP
Shell> memmap                  # Show memory map
Shell> drivers                 # List loaded drivers

8. Extensions & Challenges

Extension 1: Boot Menu (Medium)

Create a menu that loads different EFI applications:

void BootMenu(void) {
    Print(L"Select boot option:\n");
    Print(L"  1. Windows\n");
    Print(L"  2. Linux\n");
    Print(L"  3. Shell\n");

    // Read selection
    EFI_INPUT_KEY key;
    gST->ConIn->ReadKeyStroke(gST->ConIn, &key);

    // Load selected EFI
    CHAR16 *paths[] = {
        L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi",
        L"\\EFI\\Linux\\grubx64.efi",
        L"\\EFI\\BOOT\\Shell.efi"
    };

    EFI_HANDLE image;
    EFI_DEVICE_PATH *path = FileDevicePath(NULL, paths[key.UnicodeChar - '1']);
    gBS->LoadImage(FALSE, gImageHandle, path, NULL, 0, &image);
    gBS->StartImage(image, NULL, NULL);
}

Extension 2: Graphical Font Rendering (Medium)

Implement bitmap font rendering to the framebuffer:

// Simple 8x8 bitmap font
static const UINT8 font8x8[128][8] = {
    // ASCII characters as 8x8 bitmaps
    ['A'] = {0x18, 0x24, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x00},
    // ... more characters
};

void DrawChar(UINT32 x, UINT32 y, CHAR8 c, UINT32 color) {
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 8; col++) {
            if (font8x8[(UINT8)c][row] & (0x80 >> col)) {
                gFramebuffer[(y + row) * gPixelsPerLine + (x + col)] = color;
            }
        }
    }
}

Extension 3: ACPI Table Parsing (Advanced)

Access ACPI tables from the Configuration Table:

EFI_STATUS FindACPI(void) {
    EFI_GUID acpi2Guid = EFI_ACPI_TABLE_GUID;

    for (UINTN i = 0; i < gST->NumberOfTableEntries; i++) {
        if (CompareGuid(&gST->ConfigurationTable[i].VendorGuid, &acpi2Guid)) {
            RSDP *rsdp = (RSDP *)gST->ConfigurationTable[i].VendorTable;
            Print(L"ACPI RSDP found at 0x%lx\n", rsdp);
            Print(L"  OEM ID: %a\n", rsdp->OemId);
            // Parse RSDT/XSDT for more tables
            return EFI_SUCCESS;
        }
    }
    return EFI_NOT_FOUND;
}

Extension 4: Network Boot Client (Advanced)

Use UEFI network protocols to download data:

EFI_STATUS NetworkDownload(CHAR8 *url) {
    EFI_GUID httpGuid = EFI_HTTP_PROTOCOL_GUID;
    EFI_HTTP_PROTOCOL *http;

    // Locate HTTP protocol
    gBS->LocateProtocol(&httpGuid, NULL, (VOID **)&http);

    // Configure and make request
    // ... (complex, requires Service Binding Protocol)
}

Extension 5: Write an OS Loader (Expert)

Create a minimal bootloader that loads and starts a kernel:

EFI_STATUS LoadKernel(void) {
    // 1. Load kernel file
    CHAR8 *kernel;
    UINTN kernelSize;
    ReadFile(L"\\kernel.bin", &kernel, &kernelSize);

    // 2. Parse kernel headers (ELF, PE, or custom)
    UINT64 entryPoint = ParseKernelHeaders(kernel);

    // 3. Get memory map
    GetMemoryMap(...);

    // 4. Exit boot services
    gBS->ExitBootServices(gImageHandle, MapKey);

    // 5. Set up page tables, GDT (if needed)

    // 6. Jump to kernel
    void (*kernelEntry)(void *bootInfo) = (void *)entryPoint;
    kernelEntry(&bootParams);

    // Never returns
}

9. Real-World Connections

How This Relates to Professional Software

This Project Real-World Equivalent
UEFI entry point Linux EFI stub entry
GOP graphics Windows boot screen graphics
File reading GRUB loading config files
Memory map Linux early_ioremap
ExitBootServices Linux efi_enter_virtual_mode

Industry Applications

  1. Bootloader Development: GRUB2, systemd-boot, Windows Boot Manager
  2. Firmware Development: AMI, Phoenix, Insyde UEFI implementations
  3. OS Development: Linux kernel EFI stub, Windows Boot Loader
  4. Pre-boot Tools: Dell diagnostics, HP hardware testing
  5. Security Tools: Kaspersky Rescue Disk, antivirus boot scanners
  6. Disk Utilities: GParted live, cloning tools

Modern Relevance

UEFI knowledge is increasingly important:

  • Secure Boot implementation requires understanding UEFI signing
  • TPM integration for full disk encryption starts in UEFI
  • Modern OS installation uses UEFI boot mechanisms
  • Virtual machines (QEMU, VMware, VirtualBox) all support UEFI
  • ARM servers use UEFI for standardized boot
  • Embedded x86 systems increasingly adopt UEFI

10. Resources

Official Documentation

Tutorials and Guides

Tools

  • QEMU - Emulator for testing
  • OVMF - UEFI firmware for VMs
  • efitools - UEFI utilities
  • sbsign - Secure Boot signing

Code Examples


11. Self-Assessment Checklist

Understanding

  • I can explain the UEFI boot phases (SEC, PEI, DXE, BDS)
  • I understand the EFI_SYSTEM_TABLE structure and what services it provides
  • I can describe the difference between Boot Services and Runtime Services
  • I know how to locate and use UEFI protocols by GUID
  • I understand the PE/COFF format requirements for UEFI applications
  • I can explain what happens at ExitBootServices and why MapKey matters
  • I understand UEFI string handling (CHAR16, UCS-2)

Implementation

  • My application compiles as a valid PE32+ EFI executable
  • It boots successfully in QEMU with OVMF
  • Console output works correctly (text, colors)
  • Graphics output works (GOP framebuffer access)
  • File reading works (Simple File System Protocol)
  • Memory information is displayed correctly
  • Application exits cleanly

Skills

  • I can set up GNU-EFI or EDK2 development environment
  • I can create and structure an ESP for booting
  • I can debug UEFI applications with QEMU and GDB
  • I can read and interpret UEFI specification sections
  • I understand how this relates to OS loader development

12. Submission / Completion Criteria

Your project is complete when:

  1. Build Requirements
    • Compiles with provided Makefile using GNU-EFI or EDK2
    • Produces valid PE32+ executable with EFI Application subsystem
    • Includes .reloc section for position independence
  2. Functional Requirements
    • Boots in QEMU with OVMF (no manual intervention)
    • Displays formatted system information box
    • Shows UEFI version and firmware vendor
    • Displays screen resolution (GOP working)
    • Draws colored rectangle on screen
    • Reads and displays config file contents
    • Waits for keypress before exiting
  3. Code Quality
    • Well-organized source files
    • Functions are documented with comments
    • Error handling for protocol location failures
    • No memory leaks (cleanup before exit)
  4. Documentation
    • README explains build and run process
    • ESP structure is documented
    • Key UEFI concepts are explained in comments

Verification Commands

# Build
make clean && make

# Verify executable format
file myapp.efi
objdump -x myapp.efi | grep Subsystem

# Create ESP
make esp

# Run
make run
# or:
qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -net none

# Debug
make debug

Expected Output

╔══════════════════════════════════════╗
║       MY UEFI APPLICATION v1.0       ║
╠══════════════════════════════════════╣
║  UEFI Version: 2.70                  ║
║  Firmware: EDK II                    ║
║  Screen: 1024x768 @ 32bpp            ║
║  Pixel Format: BGR                   ║
╠══════════════════════════════════════╣
║  Memory Map Summary:                 ║
║    Conventional: 127 MB              ║
║    Reserved: 1 MB                    ║
║    ACPI: 64 KB                       ║
╠══════════════════════════════════════╣
║  Config file contents:               ║
║    timeout=5                         ║
║    resolution=1024x768               ║
╚══════════════════════════════════════╝

[Colored rectangle displayed]

Press any key to exit...

Appendix: Quick Reference

Common UEFI Status Codes

Status Meaning
EFI_SUCCESS Operation completed successfully
EFI_NOT_FOUND Protocol or file not found
EFI_BUFFER_TOO_SMALL Buffer provided is too small
EFI_INVALID_PARAMETER Invalid parameter passed
EFI_OUT_OF_RESOURCES Memory allocation failed
EFI_UNSUPPORTED Operation not supported

Important Protocol GUIDs

// Graphics Output Protocol
EFI_GUID gop = {0x9042a9de, 0x23dc, 0x4a38,
    {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}};

// Simple File System Protocol
EFI_GUID fs = {0x964e5b22, 0x6459, 0x11d2,
    {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};

// Simple Text Output Protocol
EFI_GUID sto = {0x387477c2, 0x69c7, 0x11d2,
    {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};

QEMU Command Reference

# Basic boot
qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:esp

# With serial output
qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:esp -serial stdio

# With debug
qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:esp -s -S

# Different display
qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:esp -display gtk

# More memory
qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:esp -m 512M

Congratulations! Completing this project means you understand modern firmware interfaces and pre-OS programming. You now have skills applicable to bootloader development, firmware engineering, and operating system development. This knowledge bridges the gap between hardware initialization and operating system execution - the foundation of all computer systems.