1

As I understand it, the GRUB bootloader on a BIOS system (and most other bootloaders for that matter) are comprised of 3 parts. The first part (stage 1) is stored in the first 448 bytes, which is responsible for passing control to the so-called stage 1.5, located a little later in memory. This stage finally loads stage 2 from the /boot folder, and transfers control to it.

How does stage 1 know which disk stage 1.5 resides on? Once the code in stage 1 begins execution, there's no way for it to be aware of which disk it was loaded from (unless this information is somehow passed to stage 1 or the BIOS itself also loads stage 1.5 into memory?)

For stage 1.5 to stage 2, again, how does does stage 1.5 known which disk (and which partition) the /boot directory resides on?

2

If you look at the sources of GRUB, available here, you find stage1 is actually defined at grub/grub-core/boot/i386/pc/boot.S.

It can perform a floppy boot if configured. It does boot from a configured harddisk, and it needs to know which C/H/S it has to load stage1.5 from. The only automatic function it has is determining which drive the boot sector was loaded from, if not configured otherwise. A functional BIOS will load that value into DL before passing control to stage1. Some don't and grub falls back to the first harddisk.

stage1.5 is already able to understand partitions and filesystems, so it doesn't rely on C/H/S values any more. The drive it loads from is still the same as above though.

3

The first part (stage 1) is stored in the first 448 bytes, which is responsible for passing control to the so-called stage 1.5, located a little later in memory. This stage finally loads stage 2 from the /boot folder, and transfers control to it.

The names "stage1", "stage1.5" and "stage2" belong to GRUB Legacy, i.e. GRUB versions 0.xx. When stage 1 is written into the MBR (or PBR), the installer will also write in it the actual disk block number where the beginning of the next stage will be located. The first block of the next stage will contain more program code, and a blocklist describing where the rest of the stage is located. The blocklist entries are of the form "load X blocks starting from disk block #Y". If the next stage was written to the disk as a contiguous (non-fragmented) file, just one blocklist entry was usually needed.

The stage1.5 was actually optional: it was perfectly possible to not install stage1.5 at all and just have stage1 load the stage2 directly. Stage 1.5 would contain just enough code to be able to understand a single filesystem type; stage2 would contain all the supported filesystem drivers, which would make it much bigger. This way, stage1.5 could be embedded into the normally-unused space between the MBR and the beginning of the first partition.

(Modern MBR-partitioned disks now begin the first partition at exactly 1 MiB from the beginning of the first disk, i.e. at block #2048, to allow for an optimal data alignment for large storage systems. This leaves much more unused space between the MBR and the beginning of the first partition than the old convention to begin the first partition at C/H/S 0/1/1.)

Both stage1.5 and stage2 had a pre-allocated space within them, for the installer to write a GRUB disk identifier and pathname into. In case of stage1.5, this would identify the partition and filename in which the actual stage2 was located; in case of stage2, it would identify the location of the GRUB configuration file.

GNU GRUB (i.e. GRUB versions 1.xx and above) in its BIOS-compatible form skips the stage1.5 entirely, and uses different names:

  • what used to be stage1 is now boot.img
  • what used to be stage2 is now core.img

The boot.img is still 448 bytes that gets embedded into MBR, but core.img is built dynamically at GRUB installation time from kernel.img and a set of GRUB modules.

I can see this information being hard-coded into stage 1.5, but how does this deal with drives getting mounted in different orders (there's guarantee that (hd0) and (hd1) will always be the same drive, so hard-coding something like that seems like a brittle strategy.

The de-facto standard BIOS convention was that whatever disk was selected from BIOS as the disk to boot from would be assigned ID 0x80 for BIOS disk access functions, and this ID would be directly mapped to GRUB (hd0). (The ancient MS-DOS would likewise always map BIOS disk ID 0x80 to drive C:.)

Fortunately, the BIOS was usually quite deterministic in how it enumerated the different disk controllers. So, as long as the hardware configuration and BIOS settings were kept the same, the disk detection order would stay the same from one boot to the next.

But yes, this definitely was a brittle strategy; unfortunately, there was no ubiquitous standard way to pass BIOS's disk detection information from the 16-bit BIOS routines to an operating system that uses 32-bit protected-mode programming (or even 64-bit). As a result, all 32-bit or better OSs will re-detect their disks from scratch after switching from BIOS-based bootloader to full 32-bit or 64-bit mode.

Yes, the BIOS Enhanced Disk Drive Services (EDD for short) includes a BIOS extension that can be used to report the essential details of BIOS disk detection to a protected-mode OS... but that extension was introduced fairly late, and the reporting part was optional, so its availability is far from guaranteed.

On BIOS-based systems with more than one disk controller, this was basically a standard headache.

The winning strategy would usually be to test the BIOS boot settings thoroughly when encountering a particular hardware model for the first time (possibly making several trial installations of the OS), and once a good configuration is found, write it down and not touch the BIOS boot settings after that.

Modern GNU GRUB includes the search command that can be used to select disk partitions by their label, UUID and/or by the presence of a particular file. In a modern grub-mkconfig-generated GRUB 2.xx configuration file, the fixed identifiers should usually be the option of last resort, to be used only if the earlier search commands fail.

The GPT partition table includes unique UUIDs for each disk and partition as standard, and UEFI NVRAM variables actually specify the location of the bootloader using the partition UUID of the ESP + the pathname of bootloader within the ESP partition. This allows for a much more robust configuration. There's also a standard interface for the running OS to both read the boot information from the UEFI firmware, and to modify the boot settings if needed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.