One of the nice things about the old venerable GRUB 1 was that is was quite simple and easily understandable, not to mention that its boot process was well-documented on a low level, which enabled debugging when stuff went wrong, and easy customization for out-of-the-ordinary scenarios. GRUB 2 is of course much nicer with its modular nature and much wider variety of features, but in its ambition to make everything work automatically, documentation on a lower level seems somewhat lacking. This is why I have been using GRUB 1 on some of my systems for many years since it was officially deprecated.
I finally decided to pull myself together on this and try to find out as much about GRUB 2 as I needed in order to regain the proficiency I felt I had with GRUB 1, and having browsed through the source code of the various utilities, this document is my attempt to document the conclusions partly for my own memory, but perhaps also for someone else who may be in a similar situation. This document does not aim to be any kind of complete manual to GRUB 2, however; its ambition is merely to augment the official manual with more low-level information.
Note that the structure of GRUB 2 seems to have been in a bit of
flux, and that this document pertains to the post-1.99 versions
of GRUB, where grub-install
is not a shell script
but a native C program, and the utility to install GRUB onto the
MBR is called grub-bios-setup
rather
than grub-setup
. It also only deals with BIOS
booting, for now. If and when I configure some system to use
UEFI booting, I may update this document to include information
on that.
Some information in this document is guesswork. If there are errors, please drop me a mail and tell me about them.
The structure of GRUB 1 was fairly simple; with its stages 1 & 2 and optional stage 1.5, there was not much to it. GRUB 2 is a bit more complex, but is mostly worth it. The main components of interest would seem to be:
grub-mkimage
program, and the result consists of the kernel, the specified
modules, the
prefix string (more on which later), put together in
a platform-specific format. This document currently deals only
with the i386-pc
format. Normally, the core image
is built automatically by grub-install
, but it is
possible and perfectly reasonable to build it manually by
running grub-mkimage
directly.
grub-mkimage
or grub-bios-setup
, which do well-defined
tasks according to specification; andgrub-install
or grub-mkconfig
, which try to analyze the
system automatically to install and configure GRUB in a
working manner without user assistance.
The way GRUB is normally set up is by
running grub-install
and grub-mkconfig
. grub-install
will
look at the general system configuration to determine the kinds
of filesystems, virtual device layers and drivers to use, and
use the information thus obtained to build a core image that
contains the bare necessary modules that are needed for GRUB to
load other modules, and install that core image to the specified
hard drive. grub-mkconfig
does similar things to
construct a configuration file that loads the necessary modules
to reach into the filesystem where the operating system kernel
is located, load the kernel and actually boot.
In the PC BIOS boot protocol, the BIOS selects (in an implementation-specific manner) a hard drive to boot from, and does so by loading the first 512-byte sector (the MBR) into a well-defined address, and finally jumping to the code thus loaded, letting it do the rest. Using GRUB, that code is the boot image as described above. When installed onto a device, the boot image is patched to contain the LBA address of the first sector of the core image (it relies on the core image always residing on the same disk as itself, the ID of which is supplied by the BIOS), and its task is to use BIOS calls to load that sector into memory and in turn transfer control to it.
In the i386-pc
core image format, the first sector
of the core image itself is patched with the LBA addresses of
the rest of the blocks that together comprise the core image,
and contains a loader that will load the rest of itself using
that information. How the patching is done is described further
below. Once the rest of the core image has been loaded into
memory, the GRUB kernel takes over and initializes the modules
that were packed alongside it into the core image. At this
point, GRUB is basically running, and if anything goes wrong
from hereon, it will drop into its rescue shell.
Once GRUB is thus running, the first thing it will do is to try
and load the module called normal
. When loading
modules, GRUB uses the prefix string mentioned above as
the base location, postfixed by the architecture name, in this
case i386-pc
. Module names are also postfixed
with .mod
to form a filename. Thus, if the prefix
string is set to (hd0,msdos1)/boot/grub
, then GRUB
will attempt to locate the normal
module by the
complete
name (hd0,msdos1)/boot/grub/i386-pc/normal.mod
. The
prefix string is also made available as
the ${prefix}
variable to GRUB commands. GRUB
modules may specify dependencies of other modules, so
loading normal
will usually entail another couple
of modules being loaded. Once GRUB has successfully loaded
the normal
module, it will run
the normal
command, which, perhaps obviously, comes
from that module. From this point, everything
is described
by the GRUB manual.
Unlike GRUB 1, GRUB 2 cannot install itself, which is perhaps a
bit sad, but not a great loss. Instead, the user-space utilities
that come with GRUB are used from within a booted operating
system to do that task. Normally, the grub-install
utility is used to do this task automatically, as described
above, but this document will ignore that and focus on manual
installation. This section will by necessity refer to certain
files located on the host system, the location of which may
differ from system to system; here, the locations on a standard
Debian system are used. Modify as necessary.
Assuming the GRUB files and utilities are installed on your
system, the first step in installing GRUB is to construct a core
image, using grub-mkimage
. The process is really
quite simple: all that needs to be considered is the prefix
string and the modules to be included. Normally, the module
files and the pre-constructed images (particularly, the kernel
image and the boot image) are located
in /boot/grub/i386-pc
on the host system. The files
in there are simply copies of the GRUB installation
in /usr/lib/grub/i386-pc
, but the constructed core
image is also usually put in the former directory, and will not
exist in the latter.
The inputs to grub-mkimage
, in
total, are the path at which to put the constructed core image,
the core image format, the prefix string, the modules to
include, and, if not the default, the location of the module
files. A complete command using the default module file
location, therefore, may be:
grub-mkimage -O i386-pc -o /boot/grub/i386-pc/core.img -p '(hd0,msdos1)/boot/grub' biosdisk part_msdos ext2
This command includes the biosdisk
,
part_msdos
and ext2
modules, in order
for GRUB to be able to load additional modules from the real
filesystem during boot. Additional modules to make GRUB even
more self-contained may, of course, be included, but will grow
the size of the core image, which may be an issue when
embedding, as described below. If any of the modules specified
depend on other modules, grub-mkimage
will include
them automatically.
Having a core image, the grub-bios-setup
program is
used to actually install GRUB on a boot device. Doing that
involves three main steps.
First of all, GRUB 2 much prefers to try and embed the
core image onto the boot device instead of letting the boot
image load it directly from its location in the filesystem
(unlike GRUB 1). There are many good reasons for this, not least
of which being that GRUB 2 supports booting via some filesystems
and/or block device layers that may move files around at whim,
making its location in the filesystem
unreliable. GRUB can be installed without embedding,
but if embedding is possible, it won't even let you choose that
option. When grub-bios-setup
is run, it will
examine the device it is instructed to install onto to see if
embedding is possible. For MBR-partitioned disks, this means
checking how much space is available between the MBR itself and
the first partition. If the space available is greater than the
size of the core image, it will copy the core image into those
sectors; that is, starting with the second sector of the disk
onwards. It is interesting in this context to note that
Linux' fdisk
tool has traditionally suggested
starting the first partition on sector 63 of the disk, but has
since switched to suggesting sector 2048 instead. One can only
assume this is to make more space available for boot-loader
embedding. GPT partition tables, on their hand, mandate a
certain amount of space for embedding, which GRUB will happily
use. If there is no space available, either due to the core
image being too large, or due to the install device not having
embeddable space at all (such as when installing into the boot
sector of a partition with a filesystem rather than to the boot
sector of the whole disk), grub-bios-setup
will
normally complain and exit, but can be run with
the -f
switch to skip embedding and use the core
image where it resides in the filesystem.
When embedding the core image, grub-bios-setup
will
patch into it the sectors it was embedded into, leaving the
original core image file intact and unchanged. If embedding is
not used, grub-bios-setup
will instead patch the
core image file in-place with the sectors it is found to reside
in on the filesystem.
Having embedded and/or patched the core
image, grub-bios-setup
will load the boot image,
patch it with the first sector of the core image and also copy
the existing partition table into it, and then write it to the
installation device's first sector. At this point, installation
is completed.
By default, grub-bios-setup
only takes the
installation device as an argument, and locates the core and
boot images via the default
names /boot/grub/i386-pc/core.img
and /boot/grub/i386-pc/boot.img
, respectively, but
that can be changed using command-line options.
Using the knowledge gained from the above sections, we can
manually construct a USB drive containing a bootable GRUB
installation. Since there are some buggy BIOSes that will look
for certain legacy signatures in the MBR of a boot device that
GRUB's boot image does not contain, we will use
the install-mbr
program to install a standard MBR
onto the USB drive's boot sector, and instead install GRUB into
the sole partition.
/dev/sdb
, partition
the drive using fdisk
to contain one large
partition:
$ sudo fdisk /dev/sdb Welcome to fdisk (util-linux 2.25.2). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Command (m for help): o Created a new DOS disklabel with disk identifier 0xca031dff. Command (m for help): n Partition type p primary (0 primary, 0 extended, 4 free) e extended (container for logical partitions) Select (default p): p Partition number (1-4, default 1): First sector (2048-3862527, default 2048): Last sector, +sectors or +size{K,M,G,T,P} (2048-3862527, default 3862527): Created a new partition 1 of type 'Linux' and of size 1.9 GiB. Command (m for help): t Selected partition 1 Hex code (type L to list all codes): c Changed type of partition 'Linux' to 'W95 FAT32 (LBA)'. Command (m for help): a Selected partition 1 The bootable flag on partition 1 is enabled now. Command (m for help): p Disk /dev/sdb: 1.9 GiB, 1977614336 bytes, 3862528 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xca031dff Device Boot Start End Sectors Size Id Type /dev/sdb1 * 2048 3862527 3860480 1.9G c W95 FAT32 (LBA) Command (m for help): w The partition table has been altered. Calling ioctl() to re-read partition table. Syncing disks.
i386-pc
, as described above for the module
loading procedure):
$ sudo mkfs.vfat /dev/sdb1 mkfs.fat 3.0.27 (2014-11-12) $ pmount sdb1 $ cd /media/sdb1 $ mkdir -p grub/i386-pc $ cp /usr/lib/grub/i386-pc/* grub/i386-pc/
$ grub-mkimage -O i386-pc -o grub/i386-pc/core.img -d /media/sdb1/grub/i386-pc -p '(hd0,msdos1)/grub' biosdisk part_msdos fatIt is instructive to note that, when booting, the BIOS will always consider the disk actually booted from as the first disk, so
(hd0)
should always work as the device
for the prefix string.
$ sudo grub-bios-setup -f -d /media/sdb1/grub/i386-pc/ /dev/sdb1 grub-bios-setup: warning: File system `fat' doesn't support embedding. grub-bios-setup: warning: Embedding is not possible. GRUB can only be installed in this setup by using blocklists. However, blocklists are UNRELIABLE and their use is discouraged.. $ sudo install-mbr /dev/sdbSince we are installing GRUB directly onto the FAT partition, where there is no space for embedding, GRUB will complain and require the
-f
switch to proceed, as described
above. Fortunately, FAT implementations do not normally move
files around on the disk, so it is harmless until you decide
to defrag the filesystem.