Translation(s): none


Since Wheezy, Debian kernels on platforms with UEFI (e.g. x86, ARM, RISC-V) contain their own BootLoader called EFI stub. Therefore it is possible to load the kernel directly, without any additional bootloader (like grub-efi). With the help of systemd-stub, it's also possible to create a Unified Kernel Image, combining the kernel, cmdline, initrd, and an optional splash screen into one single EFI binary, convenient for SecureBoot signing.

Manually setting up EFIStub

To set up EFIStub, you need to first copy the kernel and initrd into the EFI system partition, then set up an EFI boot entry for it.

Copy the files

The UEFI firmware is only able to load files from the EFI partition (usually FAT). If you use a standard UEFI installation of Debian, you should copy the kernel and the initrd to /boot/efi. The best way to keep it up to date is to place a script in /etc/kernel/postinst.d/zz-update-efistub:

   1 #!/bin/bash
   2 set -e
   3 
   4 cp --verbose "$2" /boot/efi/EFI/Debian/vmlinuz

Make it executable and create the destination folder:

chmod +x /etc/kernel/postinst.d/zz-update-efistub
mkdir -p /boot/efi/EFI/Debian

Repeat for the initrd update hook in /etc/initramfs/post-update.d/zz-update-efistub:

   1 #!/bin/bash
   2 set -e
   3 
   4 cp --verbose "$2" /boot/efi/EFI/Debian/initrd.img

Make it executable:

chmod +x /etc/initramfs/post-update.d/zz-update-efistub

To copy the kernel and initrd while also testing the hooks work correctly run:

dpkg-reconfigure linux-image-$(uname -r)

Add the boot entry

Replace /dev/sda3 with the device of your / partition. The --disk and --part options specify your EFI partition (here /dev/sdb1). Paths are relative to the root of the EFI partition (with optional leading / or \). efibootmgr will translate forward slashes to backslashes for the --loader option, but the initrd= option must use backslashes because it is interpreted in the EFI environment.

export UUID=$(blkid -s UUID -o value /dev/sda3)
efibootmgr --create --disk /dev/sdb --part 1 --label "Debian" --loader EFI/Debian/vmlinuz --unicode "root=UUID=$UUID ro initrd=EFI\\Debian\\initrd.img"

You can check your new boot entry. Since EFI uses UCS2, it should look like this:

#efibootmgr -v
...
.i.n.i.t.r.d.=.\.E.F.I.\.d.e.b.i.a.n.\.i.n.i.t.r.d.

The kernel and initramfs hooks above will be run on every kernel or initrd update. Depending on the order of operations it is possible to end up with the wrong kernel and/or initramfs copied. Running update-initramfs -u -k all in particular will update initramfs' in reverse order of kernel versions (the last initramfs updated -- and copied -- will be the one for the oldest kernel installed). If the kernel and initramfs are mismatched the system will very likely fail to boot. Always keep another boot manager like grub-efi or refind installed as backup.

Setting up EFIStub with a script

There are many automated solutions available for EFIStub. Here's an example from TriMoon:

  1. Create the script below with it's contents. (Tip: Highlight and copy with linenumbers hidden)

  2. Edit the parts to accommodate your needs.
  3. Make it executable.
    • chmod a+x /sbin/create_EFI_Boot_Entry.sh

  4. Execute the script as root.

    • sudo create_EFI_Boot_Entry.sh

Here are some line numbers with usage explanation:

   1 #!/usr/bin/env bash
   2 #       /sbin/create_EFI_Boot_Entry.sh v0.2
   3 #       Automatically create an EFI Boot entry.
   4 #
   5 #       (C) 2018+ ©TriMoon™ <https://github.com/TriMoon>
   6 #       ------------------------------------------------
   7 #       License: BY-SA 4.0
   8 #       This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
   9 #       https://creativecommons.org/licenses/by-sa/4.0/
  10 #
  11 
  12 # First compose the variables used as arguments:
  13 label='Debian (EFI stub)'
  14 loader='\EFI\debian\vmlinuz' # Use single \'s !
  15 initrd='\EFI\debian\initrd.img' # Use single \'s !
  16 # Compose default kernel arguments for an EFI-boot
  17 printf -v largs "%s " \
  18         "root=UUID=$(findmnt -kno UUID /) ro" \
  19         "rootfstype=$(findmnt -kno FSTYPE /)" \
  20         "initrd=${initrd}"
  21 # Grab extra kernel arguments from grub2 config.
  22 grub_cmdline=''
  23 if test -f /etc/default/grub; then
  24         grub_cmdline="$(sed -nE '/^GRUB_CMDLINE_LINUX_DEFAULT=\"/ {s#GRUB_CMDLINE_LINUX_DEFAULT=\"##; s#\"$##; p}' </etc/default/grub)"
  25 fi
  26 # Append extra kernel arguments
  27 if test -n "${grub_cmdline}"; then
  28         printf -v largs "%s " \
  29                 "${largs%* }" \
  30                 "${grub_cmdline}"
  31 else
  32         printf -v largs "%s " \
  33                 "${largs%* }" \
  34                 "quiet splash" \
  35                 "add_efi_memmap" \
  36                 "intel_iommu=on" \
  37                 "nvidia-drm.modeset=1"
  38 fi
  39 # echo "${largs%* }"; exit
  40 # Then create the EFI entry:
  41 efibootmgr -c -L "${label}" -l "${loader}" -u "${largs%* }"

On my system this EFI-entry was created using the script above:

Boot0000* Debian (EFI stub)     HD(1,GPT,1e4d16a9-ba85-4a29-9fd1-277c77f4e461,0x800,0x100000)/File(\EFI\DEBIAN\VMLINUZ)r.o.o.t.=.U.U.I.D.=.7.9.2.0.e.1.9.8.-.5.0.5.6.-.4.e.b.4.-.b.3.7.c.-.1.a.b.f.8.1.c.5.a.e.8.d. .r.o. .r.o.o.t.f.s.t.y.p.e.=.e.x.t.4. .i.n.i.t.r.d.=.\.E.F.I.\.d.e.b.i.a.n.\.i.n.i.t.r.d...i.m.g. .q.u.i.e.t. .s.p.l.a.s.h. .i.n.t.e.l._.i.o.m.m.u.=.o.n. .n.v.i.d.i.a.-.d.r.m...m.o.d.e.s.e.t.=.1. .h.u.g.e.p.a.g.e.s.z.=.1.G.B. .h.u.g.e.p.a.g.e.s.=.4.

If you were installing Debian from UEFI medium, efibootmgr should be installed by default. However, sometimes it can report that EFI variables are not supported. If you are sure that you have EFI partition, probably you need to download efivar package and modprobe efivars module.

Setting up a Unified Kernel Image

To build a unified kernel image, you will need the systemd-boot-efi and binutils packages installed, as well as sbsigntool if you want to sign the image. You will also need to place your kernel cmdline into a file to be read by the tool used.

Manually

Some math is used to calculate the offsets that each part of the image are placed in, then objcopy is used to create the image:

   1 align="$(objdump -p /usr/lib/systemd/boot/efi/linuxx64.efi.stub | awk '{ if ($1 == "SectionAlignment"){print $2} }')"
   2 align=$((16#$align))
   3 osrel_offs="$(objdump -h "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" | awk 'NF==7 {size=strtonum("0x"$3); offset=strtonum("0x"$4)} END {print size + offset}')"
   4 osrel_offs=$((osrel_offs + "$align" - osrel_offs % "$align"))
   5 cmdline_offs=$((osrel_offs + $(stat -Lc%s "/usr/lib/os-release")))
   6 cmdline_offs=$((cmdline_offs + "$align" - cmdline_offs % "$align"))
   7 splash_offs=$((cmdline_offs + $(stat -Lc%s "/path/to/cmdline")))
   8 splash_offs=$((splash_offs + "$align" - splash_offs % "$align"))
   9 initrd_offs=$((splash_offs + $(stat -Lc%s "/path/to/splash.bmp")))
  10 initrd_offs=$((initrd_offs + "$align" - initrd_offs % "$align"))
  11 linux_offs=$((initrd_offs + $(stat -Lc%s "/path/to/initrd.img")))
  12 linux_offs=$((linux_offs + "$align" - linux_offs % "$align"))
  13 
  14 objcopy \
  15     --add-section .osrel="/usr/lib/os-release" --change-section-vma .osrel=$(printf 0x%x $osrel_offs) \
  16     --add-section .cmdline="/path/to/cmdline" --change-section-vma .cmdline=$(printf 0x%x $cmdline_offs) \
  17     --add-section .splash="/path/to/splash.bmp" --change-section-vma .splash=$(printf 0x%x $splash_offs) \
  18     --add-section .initrd="/path/to/initrd.img" --change-section-vma .initrd=$(printf 0x%x $initrd_offs) \
  19     --add-section .linux="/path/to/vmlinuz" --change-section-vma .linux=$(printf 0x%x $linux_offs) \
  20     "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "/boot/efi/EFI/Linux/debian.efi"

Replace example paths with the actual paths to kernel, cmdline, initrd, and the splash image (/dev/null can be used to disable the splash screen). Optionally sign the image for SecureBoot:

sbsign --key /path/to/db.key --cert /path/to/db.crt --output /boot/efi/EFI/Linux/debian.efi /boot/efi/EFI/Linux/debian.efi

This will need to be done every time the kernel is updated, initrd is regenerated, or the cmdline is changed. There are apt hooks like this available that automatically manage this.

With systemd-ukify

systemd's ukify provides an easy way to generate unified kernel images. To run it automatically with every kernel upgrade and initrd generation, create these two scripts:

/etc/kernel/postinst.d/zz-ukify:

   1 #!/bin/bash
   2 set -e
   3 
   4 /usr/lib/systemd/ukify build \
   5     --linux="$2" \
   6     --initrd="/boot/initrd.img-$1" \
   7     --cmdline="replace with your cmdline" \
   8     --splash="/path/to/splash.bmp" \ # Remove this line if you don't want one
   9     --output="/boot/efi/EFI/Linux/debian.efi"
  10 
  11 # Add this line if you want to sign the image for secure boot
  12 sbsign --key /path/to/db.key --cert /path/to/db.crt --output /boot/efi/EFI/Linux/debian.efi /boot/efi/EFI/Linux/debian.efi

/etc/initramfs/post-update.d/zz-ukify:

   1 #!/bin/bash
   2 set -e
   3 
   4 /usr/lib/systemd/ukify build \
   5     --linux="/boot/vmlinuz-$1" \
   6     --initrd="$2" \
   7     --cmdline="replace with your cmdline" \
   8     --splash="/path/to/splash.bmp" \ # Remove this line if you don't want one
   9     --output="/boot/efi/EFI/Linux/debian.efi"
  10 
  11 # Add this line if you want to sign the image for secure boot
  12 sbsign --key /path/to/db.key --cert /path/to/db.crt --output /boot/efi/EFI/Linux/debian.efi /boot/efi/EFI/Linux/debian.efi

Make them executable, create the destination folder, and update the initrd to create the first image:

chmod +x /etc/kernel/postinst.d/zz-ukify
chmod +x /etc/initramfs/post-update.d/zz-ukify
mkdir -p /boot/efi/EFI/Linux
update-initramfs -u

Add the boot entry

Assuming your EFI partition in on /dev/sdb1:

efibootmgr --create --disk /dev/sdb --part 1 --label "Debian" --loader '\EFI\Linux\debian.efi'


CategoryBootProcess