3.2 · Inside the VM definition

How a Computer Boots Before Any OS Exists: BIOS, UEFI, and GPT

UEFI · Linux · Virtualization · KVM

The previous post showed that a libvirt VM is an XML document, and that lab1’s document has an <os> block naming two firmware files. Before looking at those files, this post steps back to a question that applies to every computer, real or virtual: what happens between pressing the power button and the operating system starting?

Boot problems are some of the most confusing failures in computing, because they happen before anything familiar exists. There is no shell, no log file and no operating system, only a firmware message on a black screen, in words that appear nowhere else.

Most of those words come from one change: the replacement of the old BIOS firmware by UEFI, and of the old MBR partition table by GPT. Understanding that change explains the EFI System Partition, why bootloaders are now ordinary files, what Secure Boot checks, and why a virtual machine needs firmware files at all. Nothing in this post is specific to ARM64; it applies equally to x86 machines, physical or virtual.

The problem firmware solves

When a computer is switched on, its processor knows nothing about disks. It has no idea what a file system is, that files exist, or where an operating system might be. It can run instructions, and nothing more. Somewhere on a disk, an operating system kernel is waiting to be loaded, but nothing in the processor knows how to find it.

Something has to fill that gap. It has to prepare enough of the hardware to be useful, find a bootloader (a small program whose job is to load the operating system), and hand control to it. That something is firmware: a program stored in the machine itself, which runs before any operating system exists.

   power on
      │
      ▼
   FIRMWARE runs         stored in the machine, not on the disk
      │  prepares the hardware
      │  finds a bootloader
      ▼
   BOOTLOADER runs       stored on the disk
      │  loads the operating system kernel
      ▼
   OPERATING SYSTEM

Every computer does this. What changed between the two generations of firmware is the middle step: how the firmware finds the bootloader.

BIOS: the first 512 bytes

BIOS, the Basic Input/Output System, was the firmware of the original IBM PC in 1981. Its way of finding a bootloader is very simple:

  1. The computer is switched on, and the BIOS checks the hardware.
  2. The BIOS reads the first 512 bytes of the disk, known as the Master Boot Record (MBR).
  3. Those 512 bytes contain a tiny piece of bootloader code.
  4. The BIOS runs that code, which then loads the rest of the bootloader.

That is the whole mechanism. The BIOS reads one block from the start of the disk and runs it. It does not know what is in that block and does not check it. If the bytes happen to be valid code, they run.

This design explains why UEFI was needed:

  • The BIOS cannot read file systems. It has no idea what a file or folder is, so the bootloader has to sit in a raw block at a fixed position, the only place the BIOS can find it.
  • 512 bytes is very little. It is not enough for a real bootloader, so the MBR holds only a small first stage, which loads a larger second stage from elsewhere on the disk. Multi-stage bootloaders exist because of this limit.
  • It is tied to the MBR partition table, which has limits of its own: disks of up to about 2 TiB, and at most four primary partitions.

UEFI: firmware that understands files

UEFI, the Unified Extensible Firmware Interface, replaced the BIOS on most new computers during the 2010s. It does the same job in a very different way:

  1. The computer is switched on, and the UEFI firmware runs.
  2. The firmware can read file systems, specifically the FAT file system.
  3. It looks at a dedicated partition, the EFI System Partition (ESP), which is an ordinary FAT partition holding bootloaders as files, with names ending in .efi.
  4. It checks its list of saved boot entries, kept in its own non-volatile memory (NVRAM, memory that keeps its contents when the power is off), to decide which .efi file to run.
  5. It runs that file, and the bootloader loads the operating system.

The important step is the second one. Because UEFI can read FAT, bootloaders can be ordinary files on an ordinary partition, with no 512-byte limit and no need for a multi-stage workaround.

That one change explains most of what makes modern booting different:

  • Bootloaders are files, such as \EFI\debian\grubaa64.efi. You can list them, copy them and back them up.
  • Several operating systems can be installed side by side, each with its own folder on the ESP.
  • The firmware keeps a list of boot entries in NVRAM. Each entry names a file to run, and the list is kept separately from the disk.
  • Secure Boot becomes possible. Because the firmware loads a specific file, it can check that file’s digital signature before running it, and refuse files that are not signed by a key it trusts.
BIOSUEFI
Introduced1981Common from the 2010s
Finds the bootloader byRunning the first 512 bytes of the diskRunning a .efi file from a partition
Understands file systemsNoYes, FAT
Partition tableMBRGPT
Disk and partition limitsAbout 2 TiB, four primary partitionsVery large disks, usually up to 128 partitions
Can check signatures (Secure Boot)NoYes

The EFI System Partition

The ESP is worth a closer look, because most people meet it during an operating system installation without any explanation.

It is an ordinary partition, formatted as FAT, and marked in the partition table as an EFI System Partition. It is usually between 100 MB and 1 GB in size. Each installed operating system keeps its bootloader files in its own folder under \EFI\.

Here is the Debian folder on lab1’s ESP, listed from inside the firmware itself. (Post 3.10 explains how I ended up at this Shell> prompt.)

Shell>   FS0:
FS0:\> ls EFI\debian
Directory of: FS0:\EFI\debian\
06/15/2026  13:26 <DIR>         8,192  .
06/15/2026  13:26 <DIR>         8,192  ..
06/15/2026  13:26                 110  BOOTAA64.CSV
06/15/2026  13:26              90,752  fbaa64.efi
06/15/2026  13:26                 117  grub.cfg
06/15/2026  13:26           4,339,136  grubaa64.efi
06/15/2026  13:26             887,472  mmaa64.efi
06/15/2026  13:26             990,600  shimaa64.efi
          6 File(s)   6,308,187 bytes
          2 Dir(s)

These are all ordinary files. grubaa64.efi is the GRUB bootloader, built for 64-bit ARM (aa64). shimaa64.efi is a small first-stage loader used with Secure Boot, which checks GRUB’s signature before running it. mmaa64.efi is a tool for managing Secure Boot keys, and fbaa64.efi with BOOTAA64.CSV can rebuild a lost boot entry. grub.cfg is only 117 bytes long, because its job is to point GRUB to its main configuration on the Linux partition.

There is also a standard fallback location, \EFI\BOOT\. If the firmware has no usable boot entry, it looks for a default file there, which on ARM64 is BOOTAA64.EFI. This fallback is what lets a disk boot on a machine that has never seen it before, such as a USB installer, which cannot have a boot entry saved on your computer in advance.

FAT is used because every UEFI firmware is required to understand it. That also explains why the file system of the rest of your disk, such as ext4, does not matter to the firmware: it cannot read it, and it never needs to.

GPT: the partition table that goes with UEFI

A partition table is a disk’s table of contents. It records where each partition starts, how big it is, and what it is for. There are two kinds in common use, and they pair with the two kinds of firmware.

MBR is the old kind, named after the block it lives in. The partition information sits next to the bootloader code in those first 512 bytes, with the limits described above.

GPT, the GUID Partition Table, is the modern kind. Every partition has a globally unique identifier (a GUID), which gives it its name. GPT supports enormous disks and usually up to 128 partitions, and it keeps a second copy of the table at the end of the disk, so a damaged table can be recovered.

The pairing of BIOS with MBR, and UEFI with GPT, is a convention rather than a strict rule, but it is what you will see in practice. That is why a Linux installer on a UEFI machine creates a GPT disk whose first partition is an ESP.

lab1’s disk is a good example. The same UEFI shell lists the disk’s partitions when it starts:

Mapping table
      FS0: Alias(s):HD0b:;BLK1:
          PciRoot(0x0)/Pci(0x1,0x0)/HD(1,GPT,5AE32D7B-CE6D-4D5C-ACAB-A639DD8427FB,0x800,0x100000)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)/HD(2,GPT,E7D847E9-ED9C-426F-BFEC-C0C3FD15210D,0x100800,0x1517000)
     BLK3: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)/HD(3,GPT,ECA11A3B-DA7F-43F1-8339-FD97B0A75A7C,0x1617800,0x1E8000)

Each HD(...) entry describes one GPT partition: its number, the word GPT, its unique GUID, where it starts and how long it is, both counted in 512-byte sectors. Converted into sizes:

PartitionStarts atSizeWhat it is
1 (FS0:)1 MiB512 MiBThe EFI System Partition, the only one the firmware can read
2513 MiB10.5 GiBThe Debian system
311,311 MiB976 MiBProbably Debian’s swap space

The firmware gave partition 1 a file system name, FS0:, because it is FAT. The other two only appear as block devices (BLK2, BLK3), because the firmware cannot read what is on them, and does not need to. That is also why the table says “probably” for partition 3: Debian’s installer usually puts swap space there, but only Linux, running inside the guest, can say for certain.

What UEFI does, step by step

UEFI’s start-up is divided into phases, defined by a specification called Platform Initialization. The names appear in firmware messages and documentation, so they are worth recognising, even though you will rarely deal with most of them directly:

PhaseNameWhat happens
SECSecurityThe first code to run. It sets up a temporary area of memory.
PEIPre-EFI InitializationPrepares the main memory, using very few resources.
DXEDriver Execution EnvironmentMost of the start-up work: hardware drivers and boot services are loaded.
BDSBoot Device SelectionGoes through the boot entries and starts one.
TSLTransient System LoadThe bootloader runs. This phase ends when the operating system takes over.
RTRuntimeThe operating system is running. Most firmware services are gone, but a few remain, including access to the NVRAM variables.

Two of these matter in practice.

BDS is where boot failures appear. Messages that start with BdsDxe: come from this phase, as the firmware works through its boot entries. Here is one from lab1, from an attempt to boot it with a blank set of firmware settings:

UEFI firmware (version  built at 13:10:49 on Nov  5 2024)

BdsDxe: failed to load Boot0001 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x1,0x0): Not Found

>>Start PXE over IPv4.

The firmware started correctly, tried boot entry Boot0001, did not find what it pointed to, and moved on to booting from the network, its last resort. The next post explains why this happened.

RT explains how a running operating system can change boot settings. Some UEFI services remain available after the operating system starts. That is how Linux’s efibootmgr command can add or reorder boot entries: it asks the firmware, which is still there, to change its own NVRAM.

What this means for virtual machines

A physical computer keeps its firmware in a chip on the motherboard. A virtual machine has no motherboard.

So a VM’s firmware comes as a file, which the hypervisor loads into the guest when it starts. It is a complete UEFI implementation, with the same phases, the same NVRAM boot entries and the same use of the ESP, delivered as a file rather than built into a chip.

That is why a libvirt VM definition names its firmware files explicitly, and why ARM64 guests need them: ARM64 virtual machines have no BIOS to fall back on, so they boot with UEFI. (The Raspberry Pi itself is different. It boots using its own bootloader, stored on the board, rather than UEFI. The guests it runs use UEFI.)

The firmware for virtual machines comes from an open-source project and is split into two files: a program that never changes, and a writable store for settings, including the boot entries. That split, and the failure above, are the subject of the next post.

Summary

  • At power-on, a processor knows nothing about disks, files or operating systems. Firmware fills that gap: it prepares the hardware, finds a bootloader, and starts it.
  • BIOS runs the first 512 bytes of the disk, the MBR. It cannot read file systems, which is why its bootloader lives in a raw block of the disk.
  • UEFI can read FAT file systems, so its bootloaders are ordinary .efi files on an EFI System Partition.
  • UEFI keeps a list of boot entries in NVRAM, each naming a file to run. \EFI\BOOT\ is the standard fallback when no entry works.
  • Because UEFI loads a specific file, it can check that file’s signature, which is what Secure Boot does.
  • MBR goes with BIOS (about 2 TiB, four primary partitions). GPT goes with UEFI (very large disks, usually up to 128 partitions, and a backup copy of the table).
  • UEFI starts in phases: SEC, PEI, DXE, BDS, TSL and RT. Boot failures appear during BDS, as messages that start with BdsDxe:.
  • A VM has no motherboard, so its UEFI firmware is supplied as a file, which is why VM definitions name their firmware files.

Comments

get new posts

About one email a week, and only when there is something new.

Subscribe →