Dynamic Memory Allocation in UEFI Boot Services

In bare-metal UEFI (Unified Extensible Firmware Interface) programming, standard C runtime environments do not exist natively, making traditional functions like malloc() and free() unavailable. Instead, developers interact directly with the UEFI System Table to perform dynamic memory management through UEFI Boot Services. This article explores how developers allocate, manage, and free dynamic memory using UEFI Boot Services APIs such as AllocatePool, FreePool, AllocatePages, and FreePages, while addressing memory types, alignment, and lifecycle constraints.

The Role of UEFI Boot Services

When developing UEFI applications or drivers via the EDK II (EFI Development Kit II) framework, dynamic memory management is facilitated by the Global Boot Services pointer (gBS). The firmware manages all system RAM prior to the operating system loading and exposes functions to request arbitrary buffers or page-aligned blocks.

Unlike standard application-level memory allocation, UEFI requires developers to specify the memory type being allocated. This designation determines how the memory is treated and whether it persists after the operating system takes control via ExitBootServices().

Pool Allocation: The malloc Equivalent

For variable-sized, arbitrary byte allocations similar to malloc(), developers use gBS->AllocatePool(). Releasing this memory is accomplished using gBS->FreePool().

Syntax and Parameters

EFI_STATUS
EFIAPI
AllocatePool (
  IN  EFI_MEMORY_TYPE  PoolType,
  IN  UINTN            Size,
  OUT VOID             **Buffer
  );

EFI_STATUS
EFIAPI
FreePool (
  IN VOID  *Buffer
  );
  • PoolType: Identifies the intended purpose of the memory block. Common types include:
    • EfiBootServicesData: General-purpose memory used during the pre-boot phase. It is released back to the OS after ExitBootServices() is called.
    • EfiRuntimeServicesData: Memory that remains intact and accessible even after the OS kernel has booted.
    • EfiLoaderData: Memory reserved for loading executable images such as OS loaders or UEFI drivers.
  • Size: The number of bytes to allocate.
  • Buffer: A pointer to a pointer that receives the base address of the allocated block.

Example Implementation

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

EFI_STATUS
AllocateExampleBuffer (VOID)
{
    EFI_STATUS Status;
    UINT8      *Buffer = NULL;
    UINTN      BufferSize = 1024; // 1 KB

    // Allocate memory from the boot services pool
    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    BufferSize,
                    (VOID **)&Buffer
                    );

    if (EFI_ERROR (Status)) {
        return Status;
    }

    // Use the allocated buffer
    Buffer[0] = 0xAA;

    // Free the allocated memory
    Status = gBS->FreePool (Buffer);

    return Status;
}

Unlike free(), which does not typically return a status code, FreePool() returns an EFI_STATUS (such as EFI_INVALID_PARAMETER if an invalid pointer is passed), allowing developers to directly verify deallocation success.

Page Allocation: Coarse-Grained Management

When an allocation requires page alignment (typically 4 KB boundaries) or physical address constraints—often necessary for Direct Memory Access (DMA) or loading executable images—developers use gBS->AllocatePages() and gBS->FreePages().

Syntax and Types

EFI_STATUS
EFIAPI
AllocatePages (
  IN     EFI_ALLOCATE_TYPE     Type,
  IN     EFI_MEMORY_TYPE       MemoryType,
  IN     UINTN                 Pages,
  IN OUT EFI_PHYSICAL_ADDRESS  *Memory
  );

EFI_STATUS
EFIAPI
FreePages (
  IN EFI_PHYSICAL_ADDRESS  Memory,
  IN UINTN                 Pages
  );
  • Type: Dictates address placement constraints:
    • AllocateAnyPages: Allocates any available pages that fit the requirement.
    • AllocateMaxAddress: Allocates pages at or below a specified maximum physical address.
    • AllocateAddress: Allocates pages starting at a specific physical address (useful for legacy hardware constraints).
  • Pages: The number of 4 KB pages requested.
  • Memory: For AllocateAnyPages, this is an output parameter receiving the base address. For fixed types, it is an input containing the target boundary or address.

Critical Differences Between malloc and UEFI Allocation

  1. Explicit Memory Lifecycles: Standard malloc creates heap space tied to the lifecycle of the user-space process. In UEFI, developers must choose whether data persists into runtime OS operations or gets purged at ExitBootServices().
  2. Granular Error Reporting: UEFI memory APIs return explicit error codes, such as EFI_OUT_OF_RESOURCES or EFI_NOT_FOUND, making it straightforward to diagnose failure modes compared to a simple NULL return from malloc.
  3. Alignment Guarantees: AllocatePages() guarantees 4 KB alignment by default, removing the need for auxiliary alignment functions like posix_memalign() or _aligned_malloc().
  4. No Implicit Garbage Collection: If an application exits without calling FreePool(), memory allocated with types like EfiRuntimeServicesData can remain reserved, potentially reducing the usable memory pool for the incoming operating system.