Debian 9 mit EFISTUB booten

Marvin Gülker · 15.05.2018

Der Debian-Installer installiert auch auf einem UEFI-System noch GRUB als Bootloader, obwohl das nicht nötig ist. Der Artikel zeigt, wie man auch unter Debian 9 mit EFISTUB bootet.

Kategorien: Software

Zwar folgt Debian nicht dem KISS-Prinzip, sondern achtet ganz im Gegenteil darauf, eine möglichst gut aufeinander abgestimmte Software-Auswahl auszuliefern. Dennoch ist es legitim, die auf seinem PC installierte Software auf das notwendige Minimum beschränken zu wollen. Über EFISTUB gab es auf diesem Blog zuletzt vor Jahren einen Artikel, allerdings sind die dort aufgeführten Probleme mit der Implementation von UEFI seitens der PC-Hersteller mittlerweile behoben worden, und wo nicht, hat man in neueren Kernel-Versionen entsprechende Workarounds in den Code aufgenommen. Einer Nutzung von EFISTUB sollte aus heutiger Sicht deshalb nichts mehr im Wege stehen.

Was ist EFISTUB?

UEFI verfügt im Gegensatz zum alten BIOS über moderne Boot-Fähigkeiten, die einen Bootloader überflüssig machen. Dazu legt man auf der sog. EFI-System-Partition, einer kleinen, mit FAT32 formatierten Partition typischerweise am Anfang der Festplatte, sog. UEFI-Executables ab. Anschließend gibt man deren Speicherort nebst etwaigen Boot-Parametern dem UEFI bekannt. Gute UEFI-Implementationen bieten dafür in der Konfigurations-Oberfläche entsprechende Einstellungen an, für schlechtere muß man auf die EFI-Shell, eine Kommandozeile für UEFI, zurückgreifen. Ein dritter Weg, der hier beschritten werden soll, führt über das Programm efibootmgr(8), welches man aber nur aus einem per UEFI gestarteten System aus erfolgreich ausführen kann (also nicht, wenn man den BIOS-Kompatibilitätsmodus benutzt). Unabhängig davon, für welche Variante man sich entscheidet, werden diese Informationen dann im eigenen Speicher des UEFI (NVRAM) abgelegt. Dieser Speicher befindet sich direkt auf der Hauptplatine und nicht etwa auf der Festplatte.

EFISTUB nun bezeichnet die Fähigkeit des Linux-Kernels, ohne Zuhilfenahme von Dritt-Software direkt aus dem UEFI heraus gestartet zu werden. Das bedeutet, daß man den gewünschten Linux-Kernel direkt auf der EFI-System-Partition ablegt und ihn UEFI als Boot-Eintrag bekannt gibt. Anschließend kann man ihn beim Start des Rechners automatisch starten lassen oder im Boot-Menü auswählen. Eine Notwendigkeit für einen dazwischengeschalteten Bootloader besteht in aller Regel nicht. Ausnahmen gibt es, wenn man seinen Kernel nicht auf der EFI-System-Partition ablegen will, da UEFI nur auf diese eine Partition zugreifen kann. Hier muß ein Bootloader zwischengeschaltet werden, der vom UEFI als UEFI-Executable geladen wird. Dieser Bootloader kann dann mit anderen Partitionen umgehen und auch anderswo platzierte Kernels laden. Dafür muß man aber mitnichten den schwergewichtigen GRUB installieren; vielmehr gibt es spezielle Bootloader wie rEFInd oder den mittlerweile — wie könnte es anders sein — von Systemd geschluckten Gummiboot, heute systemd-boot, die eigens für den Einsatz als UEFI-(Nach-)Bootloader konzipiert wurden. Für ein Setup, wie es dieser Artikel beschreibt, sind sie aber aus dem erwähnten Grunde unnötig.

EFISTUB unter Debian einrichten

Vorbereitungen

Sofern noch nicht geschehen, ist zunächst der efibootmgr(8) zu installieren, damit man aus dem laufenden System heraus Zugriff auf den NVRAM nehmen kann.

# apt-get install efibootmgr

Anschließend notiert man sich seine aktuellen Kernel-Start-Parameter. Diese findet man in der virtuellen Datei /proc/cmdline:

# cat /proc/cmdline
BOOT_IMAGE=../vmlinuz ro initrd=../initrd.img root=/dev/sda2 ro

Die Option BOOT_IMAGE=... ist wegzulassen, die Option initrd=... wird weiter unten noch angepaßt.

Kernel-Installations-Skript

Debian legt den Linux-Kernel stets unter /boot/vmlinuz-* ab. Die EFI-System-Partition wird dagegen unter /boot/efi eingebunden. Da wie erwähnt UEFI nur auf die EFI-System-Partition zugreifen kann, folgt daraus, daß ein nur unter /boot gespeicherter Kernel von UEFI nicht erreicht werden kann. Zur Lösung dieses Problems gibt es zwei Möglichkeiten.

  1. Man hängt die EFI-System-Partition direkt unter /boot ein.
  2. Man kopiert den Kernel nach /boot/efi.

Beide Ansätze führen zu demselben Ergebnis und sind gleichwertig. Variante 1 macht unter einem typischen Debian-System aber wohl mehr Arbeit, denn Debian setzt standardmäßig auf eine eigene Partition für /boot, die man extra löschen müßte. Vorgezogen wird hier deshalb die zweite Möglichkeit. Wichtig zu beachten ist noch, daß es nicht genügt, allein den Kernel selbst zu kopieren. Auch das Initramfs, also das kleine „Vor-System“, dessen Aufgabe es ist, das Einhängen der Root-Partition vorzubereiten (was insbesondere bei per LUKS vollverschlüsselten Systemen wichtig ist) muß kopiert werden.

Es ist sehr fehleranfällig, Kernel und Initramfs jedes Mal, wenn der Kernel eine Aktualisierung erhält, manuell in ein Verzeichnis unterhalb von /boot/efi zu kopieren. Richtigerweise muß dieser Vorgang automatisiert stattfinden, damit man ihn nicht vergessen kann. Dazu dient unter Debian das Verzeichnis /etc/kernel/postinst.d. Dort abgelegte, ausführbare Skripte werden in alphabetischer Reihenfolge ausgeführt, nachdem ein neuer Kernel installiert wurde. Jedes Skript erhält als Kommandozeilenparameter die Version des gerade installierten Kernels.

Man lege also das folgende Skript /etc/kernel/postinst.d/zz-copy-to-efi an. Durch den hohen Buchstaben z am Anfang läuft es sehr spät im Installationsprozeß; wichtig ist für die Zwecke dieses Artikels vor allem, daß es nach dem Skript initramfs-tools läuft, denn jenes Skript erzeugt das für den gerade installierten Kernel passende Initramfs, welches ja wie dargelegt mitkopiert werden muß.

#!/bin/sh

set -e

version="$1"

echo >&2 "Copying $version kernel and initramfs to EFI partition"
cp /boot/vmlinuz-$version /boot/efi/EFI/debefi/VMLINUZ
cp /boot/initrd.img-$version /boot/efi/EFI/debefi/INITRD

exit 0

APT zeigt während des Installationsprozesses nur die Ausgabe auf dem Standard-Error-Stream an, nicht diejenige auf der Standardausgabe. Für informative Meldungen wie oben mit echo muß deshalb mit >&2 auf den Standard-Error-Stream umgeleitet werden.

Die übrigen Zeilen des Skripts kopieren zunächst den Linux-Kernel, danach das Initramfs auf die EFI-System-Partition in das Verzeichnis EFI/debefi/. Der Name des Verzeichnisses kann frei gewählt werden. Beim Namen der Dateien für Kernel und Initramfs selbst ist man zwar ebenfalls frei, sie sollten aber keinesfalls die Versionsnummer enthalten. Man müßte sonst nach jeder Aktualisierung des Kernels den Eintrag im UEFI ändern und auf die neue Versionsnummer anpassen.

Das Skript muß noch ausführbar gemacht werden:

# chmod a+x /etc/kernel/postinst.d/zz-copy-to-efi

Anschließend führt man das Skript aus, um den aktuellen Kernel und sein Initramfs erstmalig auf die EFI-System-Partition zu kopieren.

# mkdir -p /boot/efi/EFI/debefi
# /etc/kernel/postinst.d/zz-copy-to-efi `uname -r`

mkdir -p legt zunächst die nötigen Verzeichnisse an, uname -r gibt die Version des laufenden Kernels zurück.

Änderung der UEFI-Boot-Einträge

Mithilfe des Programms efibootmgr(8) kann man nun die Boot-Einträge im NVRAM ändern. Ohne Parameter aufgerufen erhält man eine Liste aller Boot-Einträge, die so oder ähnlich aussehen kann:

# efibootmgr
BootCurrent: 0001
Timeout: 0 seconds
BootOrder: 0001,0000,000A,0007,0008,0009,000B
Boot0000* EFI Shell
Boot0001* debian
Boot0004  Diagnostic Splash Screen
...

BootCurrent gibt an, welcher Boot-Eintrag standardmäßig aktiviert wird. Führt man das Programm mit --verbose als zusätzlichem Parameter aus, erfährt man auch, welche UEFI-Executable ausgeführt wird. So kommt dann heraus, daß der Boot-Eintrag debian, den der Debian-Installer anlegt, GRUB lädt, der als grubx64.efi auf der EFI-System-Partition abgelegt wurde. Die Zahlen vor dem Namen geben die Nummer des Boot-Eintrags (als Hex-Zahl) an; diese benötigt man für die Änderung eines Eintrags.

Zunächst löscht man den bisherigen Eintrag debian.

# efibootmgr -b 0001 -B

Danach legt man einen neuen Eintrag für EFISTUB an.

# efibootmgr --disk /dev/sda \
  --part 1 \
  --create \
  --label "Debian" \
  --loader /EFI/debefi/VMLINUZ \
  --verbose \
  --unicode 'root=/dev/sda2 ro initrd=/EFI/debefi/INITRD'

efibootmgr(8) ist schlau genug, für den neuen Eintrag die erste freie Boot-Nummer zu vergeben (im Beispiel ist dies 0001, die vorher durch das zwischenzeitlich gelöschte debian besetzt war).

Die Optionen im Einzelnen:

  • --disk gibt an, auf welcher Festplatte sich die EFI-System-Partition befindet. Ist die EFI-System-Partition /dev/sda1, dann ist dies /dev/sda.
  • --part gibt die (1-basierte) Nummer der EFI-System-Partition an. Ist die EFI-System-Partition /dev/sda1, dann ist dies 1.
  • --label "Debian" gibt den Namen des Boot-Eintrages an, so wie er im Boot-Menü dargestellt werden soll. Leerzeichen sind zulässig, wenn sie vor der Shell escaped werden.
  • --loader gibt die UEFI-Executable an. Im Falle von EFISTUB muß dies der Pfad zum kopierten Linux-Kernel auf der EFI-System-Partition sein. Der Pfad ist absolut anzugeben, wobei man als Wurzel aber die EFI-System-Partition selbst annehmen muß. Es handelt sich hierbei um den vom oben erstellten Postinstall-Skript kopierten Kernel.
  • --verbose gibt einige zusätzliche Diagnosen aus.
  • --unicode teilt efibootmgr(8) mit, daß jetzt die Boot-Parameter für die UEFI-Executable folgen und daß diese in UTF-16 zu kodieren sind.

Am Schluß folgen die Boot-Parameter. Im Falle von EFISTUB werden diese an den Linux-Kernel übergeben, d.h. es handelt sich um die Kernel-Parameter. Diese wurden oben bereits aus /proc/cmdline entnommen und müssen hier wieder angegeben werden. Hinzu kommt ein geänderter Parameter initrd, der dem Kernel mitteilt, von wo er das (ebenfalls durch obiges Skript kopierte) Initramfs zu laden hat. Auch dieser Pfad ist absolut anzugeben, wobei ebenso wie beim Kernel als Wurzel die EFI-System-Partition anzunehmen ist.

Nun kann man noch einmal kontrollieren, ob alles so aussieht, wie man es erwartet:

# efibootmgr --verbose
BootCurrent: 0001
Timeout: 0 seconds
BootOrder: 0001,0000,000A,0007,0008,0009,000B
Boot0000* EFI Shell HD(1,GPT,...uuid...,0x800,0x40000)/File(\EFI\shellx64_v2.efi)
Boot0001* Debian    HD(1,GPT,...uuid...,0x800,0x40000)/File(\EFI\debefi\VMLINUZ)r.o.o.t.=./.d.e.v./.s.d.a.2. .r.o. .i.n.i.t.r.d.=./.E.F.I./.d.e.b.e.f.i./.I.N.I.T.R.D.
Boot0004  Diagnostic Splash Screen  FvFile(...uuid...)
...

GRUB entfernen

Zum Schluß kann der nicht mehr benötigte GRUB einschließlich seiner erzeugten Dateien entfernt werden.

# apt-get remove grub-common
# rm -r /boot/grub
# find /boot/efi -iname '*grub*' -print -delete

Wer sichergehen will, daß jetzt auch wirklich alles auf demselben Stand ist, reinstalliert den Kernel und stößt damit alle Postinstall-Skripte noch einmal an.

# apt-get install --reinstall linux-image-`uname -r`

Schluß

Selbst mit einer minimalen Installation von Debian erhält man mit GRUB Software, die man jedenfalls auf einem UEFI-System nicht benötigt. Durch die Umstellung auf EFISTUB erhält man eine saubere, moderne Lösung, die insgesamt weniger fehleranfällig sein dürfte als der hochkomplexe GRUB.