Foreword
I was using GRUB as bootloader, but using GRUB doesn't comfort me. UEFI firmware can execute executable EFI files directly, a 2nd bootloader is not strongly required. Besides, GRUB will show a boot option menu by default, but I'm not interested in customizing this menu.
It's possible to boot Linux without a 2nd bootloader. So I decided to let the UEFI firmware boot Linux directly instead of boot via GRUB.
Different Ways to Boot
Boot via GRUB
On legacy BIOS platforms, Master Boot Record (MBR) partition table is commonly used. When creating MBR disks, usually we leave a margin of about 1-2 MB between MBR and the first partition. This spare space is called post-MBR gap. While installing GRUB, installer will embed GRUB into post-MBR gap and rewrite MBR, codes in MBR will boot GRUB. When computer boots up, BIOS will first execute codes in MBR, codes is MBR will then load and boot GRUB.
If GUID Partition Table (GPT) is used, GRUB will be installed into the BIOS boot partition. While booting, BIOS will load and boot GRUB from that partition.
On UEFI platforms, GRUB is present as a executable EFI file. It's usually installed to the EFI System Partition (ESP). While installing, installer will also add a GRUB boot option into the UEFI boot sequence. After selecting GRUB boot option in UEFI firmware, it will load and boot GRUB from EFI system partition.
After GRUB is booted, GRUB will present boot options according to its configuration file. GRUB can read ext4 and BtrFS filesystem, therefore it's possible to store Linux kernel and initramfs in Linux root filesystem (usually they are stored in /boot
). GRUB will load Linux kernel (vmlinux
or vmlinuz
) and initramfs to RAM and afterwards run Linux.
Boot via EFISTUB
EFISTUB is enabled if linux is compiled with CONFIG_EFI_STUB=y
. Linux provided by Arch Linux has EFISTUB enabled by default. Linux kernel image compiled with EFISTUB enabled contains a piece of code called UEFI Boot Stub, it will be executed first when the kernel image is executed. UEFI Boot Stub will modify the kernel loaded, making it bootable before booting the kernel. UEFI Boot Stub is just like a bootloader embedded in kernel image.
UEFI boot sequence can be modified using tools like efibootmgr
. After adding the boot option of Linux kernel image with proper arguments, when you select that boot option, UEFI firmware will boot the Linux kernel image.
Unified Kernel Image (UKI)
Linux kernel and resources required in the boot process can be bundled into a executable EFI file. This EFI file is a Unified Kernel Image (UKI). A UKI may contain:
- UEFI Boot Stub,like systemd-stub
- Linux Kernel Image
- initramfs Image
- Kernel Parameters
- CPU Microcodes
- Splash Screen
Compared to EFISTUB, UKI bundled initramfs and CPU microcodes that will be used in the boot process. This makes UKI more integrated and convenient than Linux kernel image with EFISTUB enabled. With few configuration, UKI will be able to boot from UEFI firmware.
Structure of UKI
UKI is an executable EFI file, and an EFI file is also a PE/COFF file. Using UKI on my system as an example, its PE segment list is like this:
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00009930 0000000000003000 0000000000003000 00000400 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .reloc 0000000c 000000000000d000 000000000000d000 00009e00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .data 00002008 000000000000e000 000000000000e000 0000a000 2**4
CONTENTS, ALLOC, LOAD, DATA
3 .dynamic 00000100 0000000000011000 0000000000011000 0000c200 2**2
CONTENTS, ALLOC, LOAD, DATA
4 .rela 000004f8 0000000000012000 0000000000012000 0000c400 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynsym 00000018 0000000000013000 0000000000013000 0000ca00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .sbat 000000ea 0000000000015000 0000000000015000 0000cc00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .sdmagic 00000030 0000000000015100 0000000000015100 0000ce00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .osrel 0000017b 0000000000015200 0000000000015200 0000d000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .uname 0000000c 0000000000015400 0000000000015400 0000d200 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .splash 0005c572 0000000000015600 0000000000015600 0000d400 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
11 .linux 00ab0b18 0000000000071c00 0000000000071c00 00069a00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
12 .initrd 0638eac7 0000000000b22800 0000000000b22800 00b1a600 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
Among these segments:
.linux
contains Linux kernel image.initrd
contains initramfs and CPU microcodes.splash
contains splash screen image
There are also segments that are not used in my UKI such as .cmdline
, which contains embedded kernel parameters.
systemd-stub and UKI Creation
Just like UEFI Boot Stub in EFISTUB, systemd-stub implemented similar functionality. They will be executed first, load kernel and related resources, then boot Linux kernel. systemd-stub provides executable EFI file with only the part of UEFI Boot Stub, they are used as empty templates.
When UKI is booted, systemd-stub will load Linux kernel from ELF formatted Linux kernel in .linux
segment, initramfs and microcodes from .initrd
segment, kernel parameters from .cmdline
, etc. We can make an UKI by adding PE segments into empty templates.
mkinitcpio and dracut can not only create initramfs but also create UKI. Next, I will demonstrate how to create UKI using mkinitcpio.
Create UKI using mkinitcpio
Before creating UKI, we need to modify mkinition presets and the kernel parameters.
Embed Kernel Parameters
If you need embedded kernel parameters, create /etc/kernel/cmdline
and write kernel parameters into that file. Here is an example:
root=UUID=b9fb5b31-07f1-408c-9447-10a1b2476b4d rw splash loglevel=3
mkinitcpio will read the content in this file and embed into UKI.
Modify mkinitcpio Presets
mkinitcpio can create UKI, but it needs some configuration on its presets. Modify /etc/mkinitcpio.d/linux.preset
:
- Uncomment
PRESET_uki=...
to enable UKI creation, this variable indicates where to store UKI - Comment
PRESET_image
to disable initramfs image creation
UKI should be stored in ESP. For a example, if the system is Arch Linux with mainline linux
kernel and ESP is mounted at /efi
, the modified linux.preset
should look like this:
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
ALL_microcode=(/boot/*-ucode.img)
PRESETS=('default' 'fallback')
#default_config="/etc/mkinitcpio.conf"
#default_image="/boot/initramfs-linux.img"
default_uki="/efi/EFI/Linux/arch-linux.efi"
#default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
#fallback_config="/etc/mkinitcpio.conf"
#fallback_image="/boot/initramfs-linux-fallback.img"
fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"
- To embed splash screen into UKI, just uncomment
<PRESET_NAME>_options
and add--splash /path/to/your/splash_picture.bmp
into it. - If it's require to set kernel parameters separately, add
--cmdline /path/to/your/cmdline
to<PRESET_NAME>_options
. - If embedded kernel parameters is not used, add
--no-cmdline
to<PRESET_NAME>_options
.
Now use mkinitcpio to create UKI:
$ mkdir -p /efi/EFI/Linux
$ mkinitcpio -p linux
Boot UKI
Boot via UEFI Boot Options
Here we use efibootmgr for demonstration, adding a boot option into the UEFI boot sequence:
$ efibootmgr --create \
-d /dev/sdX -p Y \
--label "Arch Linux" \
--loader "EFI/Linux/arch-linux.efi"
In this command, sdX
is the name of the device where ESP resides, Y
is the partition number of ESP. --loader
indicates the path to our UKI in ESP.
If you want to add kernel parameters in the UEFI boot option, use -u parameter
to assign extra parameters encoded in UTF-16:
$ efibootmgr --create \
> -d /dev/sdX -p Y \
> --label "Arch Linux" \
> --loader "EFI/Linux/arch-linux.efi" \
> -u "root=UUID=b9fb5b31-07f1-408c-9447-10a1b2476b4d rw splash loglevel=3"
Boot via UEFI Shell
Sometimes you may want to boot a UKI, but you don't want, or forget to add it into the boot sequence. You can go to the UEFI Shell to run UKI manually.
After getting into to UEFI Shell, at first it will usually display available filesystem (FS) and block devices (BLK). You can use map
command to display these information later. They look like this:
Mapping table
FS0: Alias(s):HD0b:;BLK1:
PciRoot(0x0)/Pci(0x1,0x1)/Pci(0x0,0x0)/
NVMe(0x1,00-11-22-33-44-55-66-77)/
HD(1,GPT,C9CFBD7E-CF6B-4FBA-BC16-FA9BC170AE5F,0x800,0x180000)
BLK3: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Pci(0x0,0x0)/
NVMe(0x1,00-11-22-33-44-55-66-77)/
HD(4,GPT,11F19580-6B4A-4D1B-B963-6C434DC14700,0x2180800,0x38205800)
# ...
Find ESP in available filesystems and use following commands to enter the ESP and run UKI:
Shell> FS0:
FS0:\> cd EFI\Linux
FS0:\EFI\Linux> arch-linux.efi root=UUID=b9fb5b31-07f1-408c-9447-10a1b2476b4d rw splash loglevel=3
You will probably use your own parameters and filesystem number. After executing the last command, linux should boot.
About Secure Boot
It's required to sign UKI to make UEFI firmware accept to boot UKI in platforms with Secure Boot enabled. Most devices uses certificates from Microsoft (and also vendor), which means only images signed by Microsoft will be accepted by the UEFI firmware. This also means that you need to replace existing certificates with your own certificates to boot your own UKI on platforms with Secure Boot enabled.
Since configuring Secure Boot is quite complicated, I will introduce Secure Boot in another article.
Ending
At first I thought it was a short article about how to create and use UKI, I did't expect iw will be so long. Although objdump and codes took a lot space, other contents are still quite large.
I almost thought it's just rewriting Wiki, but I learned a lot while writing this article and added my own understanding. From different ways to boot, structure of UKI, to creating and booting UKI. I have reviewed lots of materials, which enhanced my understanding to the boot process.