Introduction: The Dawn of Unified Kernel Images in Android
The Android ecosystem has historically relied on the
boot.img
format to package the kernel, ramdisk, and device tree blob (DTB) for bootloaders. While effective, this approach can introduce complexity in multi-boot scenarios and doesn’t align with modern boot strategies like the Unified Kernel Image (UKI) concept. A UKI bundles the kernel, initial ramdisk, and kernel command line (and potentially other assets like DTB or splash images) into a single, self-executable EFI binary. This article delves into creating an Android UKI, leveraging the power and simplicity of
systemd-boot
as the EFI boot manager.
Adopting UKIs for Android devices offers several advantages:
- Simplified Boot Process: A single EFI executable streamlines the boot flow, reducing the number of components the bootloader needs to parse.
- Enhanced Security: When combined with Secure Boot, UKIs provide a more robust chain of trust by verifying a single, immutable binary.
- Flexibility: Easier integration into heterogeneous boot environments, especially on devices that also run desktop Linux distributions.
- Modern Alignment: Aligns Android’s boot mechanism with contemporary Linux distributions that increasingly adopt UKIs.
This guide provides a comprehensive, expert-level walkthrough for developers and enthusiasts looking to customize their Android booting experience.
Prerequisites for Android UKI Creation
Before diving into the technical steps, ensure you have the following:
- AOSP Build Environment: A working Android Open Source Project (AOSP) build environment. You’ll need access to the Android kernel source and various build tools.
- Linux Kernel Compilation Knowledge: Familiarity with configuring and compiling the Linux kernel.
- EFI System Partition (ESP): Access to an ESP on your target device, formatted as FAT32, where the UKI and
systemd-boot
configuration will reside.
-
systemd-boot
Installation:
systemd-boot
must be installed and configured on your target device’s ESP.
- Toolchain: An appropriate cross-compilation toolchain for your Android device’s architecture (e.g., AArch64).
Step 1: Preparing the Android Kernel Source for UKI
The core of a UKI is an EFI-stub enabled kernel. This means the kernel itself can act as an EFI executable. You need to enable specific configurations in your Android kernel source.
Kernel Configuration
Navigate to your Android kernel source directory and launch the kernel configuration menu:
cd path/to/android/kernel/source make menuconfig
Within the configuration interface, ensure the following options are enabled:
- Processor type and features —> EFI stub support:
CONFIG_EFI_STUB=y - General setup —> Initial RAM filesystem and RAM disk (initramfs/initrd) support:
CONFIG_BLK_DEV_INITRD=y - (Optional, but recommended for debugging) Kernel hacking —> Kernel debugging: Ensure necessary debugging features are enabled for easier troubleshooting.
Save your configuration and exit
menuconfig
.
Step 2: Compiling the Android Kernel with EFI Stub
Now, compile your modified Android kernel. For AArch64 (commonly used in modern Android devices), the target image is typically
Image.efi
or
Image
which can be processed. The EFI stub creates an
Image.efi
or makes
Image
directly executable. Let’s assume you’re building for an AArch64 target:
export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make Image.efi
If
Image.efi
is not generated,
Image
typically contains the EFI stub when
CONFIG_EFI_STUB=y
. You’ll find the compiled kernel at
arch/arm64/boot/Image.efi
(or
Image
).
Step 3: Crafting the Android Ramdisk for UKI
Unlike traditional Android
boot.img
, where the ramdisk is a separate blob, for a UKI, the ramdisk needs to be embedded directly into the kernel as an
initramfs
. The Android ramdisk is a gzipped CPIO archive.
Extracting and Preparing the Android Ramdisk
First, you need a working Android ramdisk. You can extract it from an existing
boot.img
or build it from your AOSP tree. Assuming you have a
ramdisk.img
file (e.g., from
out/target/product/[device_name]/ramdisk.img
):
# Decompress the ramdisk.img gzip -d ramdisk.img # Extract the contents (optional, for modification) mkdir ramdisk_contents cd ramdisk_contents cpio -id < ../ramdisk # If modifications needed, do them here. # Re-create the cpio archive cd .. find ramdisk_contents | cpio -H newc -o > new_ramdisk.cpio # Compress it again gzip new_ramdisk.cpio
You should now have
new_ramdisk.cpio.gz
which will serve as your
initramfs
.
Step 4: Assembling the Unified Kernel Image (UKI)
This is the crucial step where all components are combined into a single EFI executable. We’ll use
objcopy
to achieve this, adding sections for the initial ramdisk and the kernel command line.
# Define paths KERNEL_IMAGE=arch/arm64/boot/Image.efi RAMDISK=new_ramdisk.cpio.gz UKI_OUTPUT=android-uki.efi # Create a temporary file for the kernel command line echo "console=ttyS0,115200 root=/dev/dm-0 dm_verity.verity_mode=EIO androidboot.selinux=permissive androidboot.fstab_suffix=default" > cmdline.txt # Example kernel command line. Adjust as per your device requirements. # Use llvm-objcopy if available, otherwise objcopy llvm-objcopy --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x20000 --add-section .initrd=${RAMDISK} --change-section-vma .initrd=0x3000000 --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x10000 ${KERNEL_IMAGE} ${UKI_OUTPUT} # Cleanup rm cmdline.txt
A note on virtual memory addresses (
--change-section-vma
): These addresses are arbitrary but must be distinct and outside the kernel’s own text/data sections to avoid collisions. The values `0x10000`, `0x20000`, and `0x3000000` are commonly used and safe for these small sections.
You now have
android-uki.efi
, your complete Android Unified Kernel Image.
Step 5: Configuring Systemd-boot
With the UKI created, the next step is to place it on the EFI System Partition (ESP) and configure
systemd-boot
to recognize and boot it.
Placing the UKI on the ESP
Mount your ESP (e.g.,
/boot
or
/efi
) and copy the UKI to an appropriate location:
sudo mount /dev/sdXN /efi # Replace sdXN with your ESP partition sudo mkdir -p /efi/EFI/Android/ sudo cp android-uki.efi /efi/EFI/Android/
Creating the Systemd-boot Entry
Create a new boot entry file within the
loader/entries/
directory on your ESP. For example,
/efi/loader/entries/android.conf
:
# /efi/loader/entries/android.conf title Android UKI linux /EFI/Android/android-uki.efi # No initrd or options needed here, as they are embedded in the UKI
Because we’ve embedded the ramdisk and command line directly into the UKI, the
initrd
and
options
lines are not strictly necessary in the
systemd-boot
configuration file for a basic setup. The kernel automatically finds them. This simplifies the boot loader configuration.
Step 6: Booting and Troubleshooting
Reboot your device. When the
systemd-boot
menu appears, you should see
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →