| tags: [ linux ]
How to create a bootable CD/USB image
The goal
The creation of bootable live Linux CDs is a topic well covered on the internet, but most guides are limited in compatibility. ISOs can require additional tools like Rufus to be written to a USB drive, or are only compatible with BIOS firmwares.
In the context of my work, an increasing number of users stumbled on these incompatibilities. This article will guide you through the process I followed to create a bootable live Linux ISO image that can be written on either a CD or a USB device, and that will boot with both BIOS and UEFI firmwares, without additional processing.
This is not the only way to achieve this result, and your needs will likely be different than mine, but I hope you will find useful information in this article.
How it works
First, let’s list and explain all the different boot systems we want to be compatible with.
CD boot with ISO 9660
The filesystem used in CDs, and therefore iso files, is named ISO 9660.
Bootable CDs are created using the El Torito specification, an extension of ISO 9660 allowing the bootloader of the computer to find bootable binaries.
Interestingly, ISO 9660 filesystems have an unused 32KB at the beginning of the image, which allows the insertion of partition tables and bootloaders when creating an hybrid CD.
Legacy boot
Legacy boot, or BIOS boot, is a very straightforward process: the bootloader finds a suitable bootable partition, and execute the code at its very beginning.
This code is extremely small, its task is to perform basic initialization of the system and launch a more complex bootloader, like Grub.
Non-bootable partitions often contain code at this location to display an error message, as a safeguard.
Modern boot with UEFI
To boot using UEFI, we need an EFI system partition (ESP).
An ESP partition is a FAT formatted partition marked by a special code in the partition table.
- For MBR, the partition type byte is
0xEF - For GPT, the partition type UUID is
C12A7328-F81F-11D2-BA4B-00A0C93EC93B
For bootable CDs, an El Torito boot entry can be set to point to an ESP.
A computer with an UEFI firmware will detect the ESP and use the bootloader at a standardized path, if one is present for its architecture.
- For amd64:
/EFI/boot/bootx64.efi - For i386:
/EFI/boot/bootia32.efi
Required tools for a Linux live CD
For our needs, we will have to use two different bootloaders: Grub2 and Syslinux.
Grub will be used for UEFI boot, while Isolinux will allow us to have a hybrid CD / USB boot with Isohybrid.
Grub2
We will be using Grub2 for UEFI boot.
grub-mkimage will be used to generate the EFI binaries.
These binaries will be put into a small FAT partition, which will be used as an ESP partition.
To get the tools and binaries, install the following packages:
grub-efi-amd64-bin grub-efi-ia32-bin
Syslinux (isolinux, isohybrid)
We will be using Isolinux for legacy boot.
An El Torito boot entry will point toward the Isolinux binary.
isohybrid is a tool able to enhance a bootable CD image, making it also bootable on USB drives.
Isohybrid reads the El Torito boot catalog, and create the partition tables and MBR bootloader code required to boot on a USB drive.
Isohybrid is only compatible with Isolinux for legacy boot.
To get the tools and binaries, install the following packages:
isolinux syslinux-utils
xorriso
Xorriso is a tool to create ISO 9660 images.
It supports many features and extensions that we will be using.
To get this tool, install the xorriso package.
Building a Linux live CD
Create a folder cd_image. It will contain all the files included in our ISO 9660 filesystem.
If you do not have a live Linux image yet, you can find plenty of resource online on how to create one for Debian using debootstrap.
Place your Linux image in the cd_image folder.
cd_image/live/vmlinuz1
cd_image/live/initrd1
cd_image/live/filesystem.squashfs
BIOS
Refer to the Isolinux documentation to create an isolinux configuration file. For a live Linux it should be straightforward.
A minimal Isolinux configuration file could contain:
label mylivelinux
menu label My live ^Linux
menu default
kernel /live/vmlinuz1
append initrd=/live/initrd1 boot=live init=/bin/systemd
Copy your Isolinux configuration file.
mkdir cd_image/isolinux
cp isolinux.cfg cd_image/isolinux/
And copy your Isolinux binaries from your distribution. In this example, you have the list of modules I am using, but your needs may be different.
cp /usr/lib/ISOLINUX/isolinux.bin cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/menu.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/vesamenu.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/hdt.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/ldlinux.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/libutil.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/libmenu.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/libcom32.c32 cd_image/isolinux/
cp /usr/lib/syslinux/modules/bios/libgpl.c32 cd_image/isolinux/
cp /usr/share/misc/pci.ids cd_image/isolinux/
And we are done for Isolinux.
UEFI
Prepare grub’s environment
Grub’s environment and configuration files will be on the main ISO 9660 filesystem.
Again, refer to the Grub2 documentation to create a grub configuration file.
Here is a fleshed out example with code to get a better screen resolution:
insmod efi_gop
insmod efi_uga
insmod font
if loadfont /boot/grub/unicode.pf2
then
insmod gfxterm
set gfxmode=auto
set gfxpayload=keep
terminal_output gfxterm
fi
set timeout=30
insmod linux
menuentry 'My live Linux' --hotkey 'l' --id 'mylivelinux' {
linux /live/vmlinuz1 boot=live init=/bin/systemd
initrd /live/initrd1
}
Copy your Grub configuration file. If you use my sample config, you also need to copy a font file.
mkdir -p cd_image/boot/grub
cp grub.cfg cd_image/boot/grub/
cp /usr/share/grub/unicode.pf2 cd_image/boot/grub/
Finally, copy the modules that will be dynamically loaded. Here I copy all the modules, but you could remove the unused ones.
For x86_64 architecture:
mkdir cd_image/boot/grub/x86_64-efi
cp /usr/lib/grub/x86_64-efi/*.mod cd_image/boot/grub/x86_64-efi
For i386 architecture:
mkdir cd_image/boot/grub/i386-efi
cp /usr/lib/grub/i386-efi/*.mod cd_image/boot/grub/i386-efi
If you want, you can support multiple architectures in the same image.
Grub’s environment on the main filesystem is now ready.
Create the ESP
The ESP can be of any size, in our case 5MB will be enough; we want it to be as small as possible to reduce the size ISO image.
dd if=/dev/zero of=esp.img bs=1M count=5
mkfs.fat esp.img
mkdir esp_mount
sudo mount -o loop esp.img esp_mount
mkdir -p esp_mount/EFI/boot
mkdir -p esp_mount/boot/grub
When booting from a CD, the UEFI root will be set to the ISO9660 filesystem, but from a USB drive, it will be set to the ESP. For this reason, we create a shim grub config file in the ESP that sets the root to the ISO9960 filesystem then loads the primary config file.
We use the grub command search.fs_label to locate our main filesystem by the name “MyLiveCd” (this name will be set later by xorriso).
cat > esp_mount/boot/grub/grub.cfg <<EOF
search.fs_label MyLiveCd root
set prefix=(${root})/boot/grub
insmod configfile
configfile /boot/grub/grub.cfg
EOF
Now we will use grub-mkimage to create the UEFI binaries and put them in the ESP.
Some modules must be loaded before the filesystem is initialized in Grub, and so they have to be embedded in the binary:
MODS="normal fat iso9660 part_gpt part_msdos search"
For x86_64 architecture:
grub-mkimage -O x86_64-efi $MODS -p "/boot/grub" -o esp_mount/EFI/boot/bootx64.efi
For i386 architecture:
grub-mkimage -O i386-efi $MODS -p "/boot/grub" -o esp_mount/EFI/boot/bootx64.efi
Again, you can support multiple architectures in the same image.
The ESP is now ready. Although it will be accessed as a partition, the image has to be embedded in the ISO9660 filesystem as a regular file.
sudo umount esp_mount
mv esp.img cd_image
Build ISO image
To create our ISO image, run the following command:
xorriso -as mkisofs \
-rational-rock -joliet \
-input-charset utf-8 -full-iso9660-filenames \
-volid "MyLiveCd" -o myimage.iso \
-c isolinux/boot.cat -b isolinux/isolinux.bin \
-no-emul-boot -boot-load-size 4 -boot-info-table \
--efi-boot esp.img -efi-boot-part --efi-boot-image \
cd_image
The important parts of this command are:
-volid "MyLiveCd" is used as the filesystem label. It must match the one we used in the Grub configuration file earlier.
-c isolinux/boot.cat is a boot catalog created by xorriso, and used by Isolinux.
-b isolinux/isolinux.bin adds an El Torito boot entry pointing to the Isolinux binary.
--efi-boot esp.img adds an El Torito boot entry pointing to the ESP partition.
-efi-boot-part --efi-boot-image creates a GPT table with an ESP entry pointing to the partiton declared with --efi-boot.
With this command, we have created our live CD, but a slight modification is necessary to make it fully functional when copied to a USB device.
The final step is to run isohybrid on the image to support booting from USB with a legacy firmware.
isohybrid -u myimage.iso
The ISO image is now complete.
Result
After all this hard work, you should have an ISO image which can boot in (hopefully) every case:
- CD boot, legacy: an El Torito entry point to the Isolinux binary
- CD boot, UEFI: an El Torito entry point to the ESP
- USB boot, legacy: bootloader inserted by Isohybrid, boot using the Isolinux binary
- USB boot, UEFI: ESP entry in GPT partition table created by xorriso
Links
- ISO 9660 on Wikipedia
- El Torito on Wikipedia
- EFI system partition on Wikipedia
- Isolinux
- Isohybrid
- man xorriso
- A good article, but CD and USB images are separate: Create a Custom Debian Live Environment (CD or USB)