dev-guides

GRUB Bootloader Configuration

When doing kernel development, you’ll often work with multiple kernel builds: you may be testing different implementations/features, and ideally you also have at least one pristine fallback kernel in case you (almost inevitably) really mess up and build an unbootable kernel.

We use a bootloader to manage these kernels; we use it to choose which kernel we boot into, and what parameters we pass to that kernel. This guide will walk you through some good practices for using the GNU GRand Unified Bootloader (GRUB).

These instructions should allow you to do kernel development entirely headlessly: you’ll be able to work fully remotely. And if you’re working locally in a VM, you won’t have to open your VMWare serial console.

Preparation

Take a snapshot

Better safe than sorry, especially if you’re working with something as fragile as a kernel! Unlike with regular user processes, there’s no operating system sitting underneath your kernel to keep things running if it crashes. The last thing you want to end up with is a bricked kernel.

Install dependencies

If you don’t already, make sure you have GRUB 2 installed. If you followed our Debian VM setup tutorial, it should already be installed for you. You may make sure that GRUB 2 is installed by running the following:

# grub-install --version

It should indicate that you are on some version of GRUB 2:

grub-install (GRUB) 2.06-3~deb11u5

If for some reason you don’t have GRUB 2 installed, you should install the grub2 package.

Finding Your Way Around

Just to give you a better idea of where things are located and which files you’re going to be touching, here’s an overview of some important filepaths you should be aware of.

/boot/

The /boot/ directory contains everything your machine needs during the boot process, including the kernel images that contain your operating system (that’s why you need to copy them here to boot into them). This is also where GRUB reads its configuration file from at boot time, and where update-grub looks to generate the bootloader configuration file.

/boot/grub/grub.cfg

This is the configuration file that GRUB reads from during the boot process. It is written as a shell script, and defines the menu entries that appear on your bootloader menu.

This configuration file is usually generated from /etc/default/grub, so any changes made here will be lost next time you generate a new configuration file (by running update-grub). While we won’t be editing this file, we will be reading it to make sure update-grub did what we expected it to.

/etc/default/grub

This GRUB file is what you’ll be editing in order to set up your bootloader. When you run update-grub, it will generate the /boot/grub/grub.cfg configuration file from the options you set in your GRUB file and the kernels it finds in your /boot/ directory.

The syntax of your GRUB file is declarative. Each option is identified by a key, which you set with KEY=value. Here are some useful options:

For more information about configuring your GRUB file, you can check out the official GRUB documentation.

Listing your kernels

Each kernel image menu entry has its own menu entry ID. We can inspect the GRUB configuration file to see what the menu entry ID for each kernel is by running the following shell incantation:

# grep '\$menuentry_id_option' /boot/grub/grub.cfg | sed 's/menuentry //g' | sed 's/--class.*menuentry_id_option//g' | nl -v 0

The output should look something like this:

 0  'Debian GNU/Linux, with Linux 5.10.158-cs4118'  'gnulinux-5.10.158-cs4118-advanced-a376ec0b-e707-4282-973e-1a9e9dbe017b' {
 1  'Debian GNU/Linux, with Linux 5.10.158-cs4118 (recovery mode)'  'gnulinux-5.10.158-cs4118-recovery-a376ec0b-e707-4282-973e-1a9e9dbe017b' {
 2  'Debian GNU/Linux, with Linux 5.10.0-20-amd64'  'gnulinux-5.10.0-20-amd64-advanced-a376ec0b-e707-4282-973e-1a9e9dbe017b' {
 3  'Debian GNU/Linux, with Linux 5.10.0-20-amd64 (recovery mode)'  'gnulinux-5.10.0-20-amd64-recovery-a376ec0b-e707-4282-973e-1a9e9dbe017b' {

We want to use the 5.10.158-cs4118 kernel we built as our fallback kernel, so we’ll note its ID is gnulinux-5.10.158-cs4118-advanced-a376ec0b-e707-4282-973e-1a9e9dbe017b.

Setting a default kernel

Now that we have the menu entry ID for our kernel, we can reliably set it as the default in our GRUB file, inpendent of its index. For this to work, add the following to /etc/default/grub with root privileges:

GRUB_DISABLE_SUBMENU=y

Next, change GRUB_DEFAULT to the desired kernel image:

GRUB_DEFAULT='gnulinux-5.10.158-cs4118-advanced-a376ec0b-e707-4282-973e-1a9e9dbe017b'

After you’ve saved these changes, make sure to regenerate your GRUB config file:

# update-grub

Now when you reboot, you should see that you boot into this kernel regardless of what you may have previously selected. You may verify what kernel you’ve booted into by running this command:

$ uname -r
5.10.158-cs4118

Booting into a custom kernel

Now that you’ve set up your bootloader to boot into your cs4118 fallback kernel by default, we can tell the GRUB bootloader to reboot into an experimental kernel on the next boot only.

Let’s say that we installed a new kernel into our /boot/ directory named 5.10.158-dev, and ran update-grub to generate our new config file. We can run the menu entry list incantation again, to find something like this:

 0  'Debian GNU/Linux, with Linux 5.10.158-cs4118'  'gnulinux-5.10.158-cs4118-advanced-a376ec0b-e707-4282-973e-1a9e9dbe017b' {
 1  'Debian GNU/Linux, with Linux 5.10.158-cs4118 (recovery mode)'  'gnulinux-5.10.158-cs4118-recovery-a376ec0b-e707-4282-973e-1a9e9dbe017b' {
 3  'Debian GNU/Linux, with Linux 5.10.158-dev'  'gnulinux-5.10.158-dev-advanced-c9052917-0eaf-4792-9af6-555e787bd6f4' {
 4  'Debian GNU/Linux, with Linux 5.10.158-dev (recovery mode)'  'gnulinux-5.10.158-dev-recovery-c9052917-0eaf-4792-9af6-555e787bd6f4' {
 5  'Debian GNU/Linux, with Linux 5.10.0-20-amd64'  'gnulinux-5.10.0-20-amd64-advanced-a376ec0b-e707-4282-973e-1a9e9dbe017b' {
 6  'Debian GNU/Linux, with Linux 5.10.0-20-amd64 (recovery mode)'  'gnulinux-5.10.0-20-amd64-recovery-a376ec0b-e707-4282-973e-1a9e9dbe017b' {

We can instruct the bootloader to boot into 5.10.158-dev on the next boot only:

# grub-reboot 'gnulinux-5.10.158-dev-advanced-c9052917-0eaf-4792-9af6-555e787bd6f4'

Then reboot:

# reboot

And verify that we’ve rebooted into our dev kernel with the following:

$ uname -r
5.10.158-dev

When we boot yet again, we should find ourselves back in the 5.10.158-cs4118 kernel we set as our default.