How to use GNU GRUB on OpenBSD.

At some point I got a machine with OpenBSD, which was running some expensive hardware.

A lot of time has passed since its purchase, and we wanted to move to Linux, as its support for the hard ware has improved.

However, the machine had a locked bootloader.

This file is my notes on trying to circumvent it.

1. How to install GNU GRUB from OpenBSD.

1.1. Build GRUB

We will need two grub installations, because, well,… because it is software, and everything is broken. GRUB 2.12 is generating bootloader images which are too huge (more than 64 sectors), and GRUB 2.06 is generating broken boot.img and diskboot.img. But combined together, they work.

Firstly we will do some preparatory work.

  1. pkg_add git rsync autoconf automake gawk gsed gmake libtool gettext-tools findutils wget gtar xz ggrep
  2. chsh -> bash
  3. printf ’export AUTOCONF_VERSION=2.72\nexport AUTOMAKE_VERSION=1.17\n export PS1=“\\u:\\w>”\n’ >> ~/.profile
  4. mkdir ~/grub ; cd ~/grub
  5. wget https://mirrors.dotsrc.org/gnu/grub/grub-2.06.tar.gz
  6. wget https://mirrors.dotsrc.org/gnu/grub/grub-2.12.tar.gz

nsectors patch

This small patch can be applied to grub source in order to show you what is the size of the resulting image, in case installation fails.

diff --git a/grub-core/partmap/msdos.c b/grub-core/partmap/msdos.c
index c85bb74be..25c460474 100644
--- a/grub-core/partmap/msdos.c
+++ b/grub-core/partmap/msdos.c
@@ -400,10 +400,12 @@ pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
			  "post-MBR gap; embedding won't be possible"));

   if (*nsectors > 62)
+    {
+      printf("lwf:core.img nsectors=%d\n", *nsectors);
     return grub_error (GRUB_ERR_OUT_OF_RANGE,
		       N_("your core.img is unusually large.  "
			  "It won't fit in the embedding area"));
-
+    }
   return grub_error (GRUB_ERR_OUT_OF_RANGE,
		     N_("your embedding area is unusually small.  "
			"core.img won't fit in it."));

1.1.1. Grub 2.12

We will build grub-2.12 first, because it is easier, and because later we will be using its build artefacts. The touch command is needed, because GRUB developers just do not care if their software fails to build.

  1. cd ~/grub/ ; gtar xvf grub-2.12.tar.gz ; cd grub-2.12
  2. touch grub-core/extra_deps.lst
  3. MAKE=gmake ./configure --disable-werror --disable-nls && gmake -j1

    Check results:

root:~/grub/grub-2.12>ls -l ./grub-core/{diskboot.img,boot.img}
-rwxr-xr-x  1 root  wheel   512 Sep 24 16:00 ./grub-core/boot.img
-rwxr-xr-x  1 root  wheel   512 Sep 24 16:00 ./grub-core/diskboot.img

Both must be exactly 512 bytes.

1.1.2. Build grub 2.06

  1. cd ~/grub ; gtar xvf grub-2.06.tar.gz ; cd grub-2.06
  2. sed -i ’s/-falign-jumps=1//g’ configure
  3. MAKE=gmake ./configure –disable-werror –disable-nls && gmake -j1
  4. gmake install

We can see that the two images are, indeed, broken:

root:~/grub/grub-2.06>ls -l ./grub-core/{diskboot.img,boot.img}
-rwxr-xr-x  1 root  wheel  531 Sep 24 16:07 ./grub-core/boot.img
-rwxr-xr-x  1 root  wheel  531 Sep 24 16:07 ./grub-core/diskboot.img

But do not worry, we will replace them:

  1. cd ~/grub/grub-2.12
  2. cp ./grub-core/{diskboot.img,boot.img} usr/local/lib/grub/i386-pc

Now we can use grub-related commands.

1.2. Resize the existing openbsd partition.

OpenBSD default install installs makes the fourth partition on the disk to be the BSD disklabel. Inside the disklabel, there is a /home partition, which usually hosts user files, but we do not really need it to be a separate partition.

  1. mkdir /tmp/homebak
  2. mv /home/* /tmp/homebak
  3. umount /home
  4. remove mounting /home from /etc/fstab
  5. delete the home (in most cases, “k” disklabel-partition) disklabel -E sd0 ; d k w x ; reboot
  6. create two BSD-disklabel partitions with disklabel -E sd0 It is a bit annoying to compute those offsets and sizes, but not hard. I have made one partition sized 2Gb, on /dev/sd2k, for grub, and one for the future Linux system for the rest of the disk, /dev/sd0l.
  7. adjust two fdisk partitons (in my case, 2 and 3), to match the offset and size on /dev/sd0k and /dev/sd0l.
  8. format /dev/sd0k with newfs_msdos: newfs_msdos sd0k

The reason for the above is that OpenBSD (and newfs) sees partitions under the disklabel, and grub and BIOS see partitions under MBR/fdisk.

You do not really have to format the second partition, Linux can do it by itself.

1.3. install grub

  1. mkdir -p /boot_grub_part2/grub
  2. mount_msdos /dev/sd0k /boot_grub_part2
  3. echo ’(hd0) /dev/rsd0c’ > /boot_grub_part2/grub/device.map
  4. cd ~/grub/grub-2.06
  5. grub-install –no-rs-codes –disk-module=biosdisk –target=i386-pc -v –grub-mkdevicemap=/grub/device.map –skip-fs-probe –boot-directory=/boot_grub_part2/ ’/dev/rsd0c’ 2>&1 | ggrep -v ’Scanning for’ | ggrep -v ’signature found’

Look at the very bottom of it. There should be not issues with installation. If there are, they are likely due to “not enough space for embedding”. With the nsectors patch it should also show you what the actual size of the bootloader it.

This is the dangerous step, if it fails, you are screwed, the system will not boot.

However, you probably can still repair it by running installboot and fdisk.

1.4. /boot_grub_part2/grub/grub.cfg

Make the following grub.cfg.

set timeout=5
serial --unit=0 --speed=9600
terminal_input console serial
terminal_output console serial
menuentry "openbsd1" {
insmod chain
insmod ufs2
set root=(hd0,msdos4)
chainloader +1
}

This is a bit of a cheat. In fact, it relies on the fact that /dev/sd0a , which is usually root of OpenBSD, is at the beginning of the disklabel, msdos4.

You can do a better thing, you can do something like:

set timeout=5
menuentry "openbsd1" {
insmod chain
insmod ufs2
insmod part_bsd
set root=(hd0,openbsd1)
chainloader +1
}

Or maybe even

set timeout=5
menuentry "openbsd1" {
insmod chain
insmod ufs2
insmod part_bsd
set root=(hd0,openbsd1)
kopenbsd bsd
boot
}

But I have not tested it.

Never ever run grub-mkconfig, because what would you expect it to generate?

Reboot and observer your OpenBSD booting from GRUB.

1.5. Next steps.

If you want to install Linux, you probably can add another menuentry, which would mount an ISO file as a loop-back device, and boot into it, like this:

menuentry "GParted Live ISO" {
  set GPartedISOFile="/opt/Live-ISOs/gparted-live-0.31.0-1-amd64.iso"
  loopback loop (hd2,gpt2)$GPartedISOFile
  linuxefi (loop)/live/vmlinuz boot=live components config findiso=$GPartedISOFile ip=frommedia toram=filesystem.squashfs union=overlay username=user
  initrdefi (loop)/live/initrd.img
}

But I have not tried it, and I am not sure it works as intended.

1.6. What is happening here?

Question: Why exactly do we need this trickery with two versions of GRUB?

Answer: Because, despite most of the HOWTOs on the internet claiming that the 63 sectors between MBR and the first partition are enough for a GRUB image supporting msdos partitioning, and a FAT filesystem, this is wrong. Neither GRUB-git.20250923, nor GRUB-2.12 are building an image which is small enough. Ironically, with various tricks, I managed to build an image of 64 sectors, which is still 1 sector larger than allowed. Only GRUB-2.06 generates the bootloader small enough to fit in the MBR gap.

Question: Why do you even need an MBR/BIOS boot in 2025, where can you even encounter such machines?

Answer: If you are not dealing with an OEM-specific hardware, there are still plenty of virtual machines rented out by hosting providers which book with BIOS.

Question: What are all those “bootloaders”, “images”, all that kind of stuff you are talking about?

Answer: GRUB’s architecture is incredibly stupid. Here I will try to outline it briefly.

Normal booting

  1. MSDOS MBR usually works like this: the default bootloader, built into the MBR itself, cannot do any real booting, it just makes a jump to the “partition boot record” (PBR), of a partition marked as “bootable”.
  2. In the PBR there would be a 512-byte stage-1 bootloader, which is called “biosboot” on OpenBSD, which lists blocks containing the actual bootloader (called “boot” on OpenBSD), which is big enough to read the filesystem, its files and directories, not just blocks, and the code to assemble and execute it.
  3. “boot” boots the whole system, but it does not have to, actually, because it is already big enough to do more or less anything.

GRUB booting

GRUB, in theory, could support the same model of booting. It could generate the equivalent of the “boot” program, called “core.img”. In principle, you would do the same thing: write the blocklists-enabled 512-byte large “stage1” bootloader place in the PBR. This would require the partition to be edited seldom, but this is okay, since most people have a partition dedicated to GRUB specifically anyway. The issues is that:

  1. It is impossible to enable this machinery explicitly, unless GRUB infers that it cannot install ALL of its “core.img” into the “gap” between the first sector (where the MBR is), and the beginning of the first partition (which is often 64 sectors-large, that is about 32kilobytes).
  2. Even if the “gap” is small enough, GRUB’s code for blocklists is so broken that they discourage its use themselves.

Naturally, there is a question: why don’t you put the bootloader “somewhere outside the partitioned space”? Well, this is extremely obvious and requires no tweaking whatsoever: just make some space for grub to be installed at the end of the disk, beyond the boundary of the last partition, and you can even ignore all the “blocklist” machinery, just write the bytes constituting “core.img” linearly, sector by sector.

But GRUB does not support such an OBVIOUS thing.

Moreover, even if you give it a whole partition, it still cannot install itself.

Really, if you specifically prepare some space for it, and even:

grub-install --force --no-rs-codes --disk-module=biosdisk --target=i386-pc -v --grub-mkdevicemap=/grub/device.map --skip-fs-probe  --boot-directory=/boot_grub_part2/ '/dev/rsd0k' 2>&1 | ggrep -v 'Scanning for' | ggrep -v 'signature found'

You will get:

grub-install: info: guessed root_dev `hd0' from dir `/boot_grub_part2/grub/i386-pc'.
grub-install: info: setting the root device to `hd0,msdos4'.
grub-install: warning: Attempting to install GRUB to a partitionless disk or to a partition.  This is a BAD idea..
grub-install: 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..
grub-install: info: will leave the core image on the filesystem.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB.
grub-install: info: drive = 0.
grub-install: info: the size of hd0 is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hd0 is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hd0 is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hd0 is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hd0 is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: error: cannot read `/boot_grub_part2/grub/i386-pc/core.img' correctly.
grub-install: info: the size of hostdisk//dev/rsd0c is 104857600.
grub-install: info: guessed root_dev `hostdisk//dev/rsd0c' from dir `/boot_grub_part2/grub/i386-pc'.
grub-install: info: setting the root device to `hostdisk//dev/rsd0c,msdos4'.
grub-install: warning: Attempting to install GRUB to a partitionless disk or to a partition.  This is a BAD idea..
grub-install: 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..
grub-install: info: will leave the core image on the filesystem.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB.
grub-install: info: drive = 0.
grub-install: info: the size of hostdisk//dev/rsd0c is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hostdisk//dev/rsd0c is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hostdisk//dev/rsd0c is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hostdisk//dev/rsd0c is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: info: attempting to read the core image `/boot_grub_part2/grub/i386-pc/core.img' from GRUB again.
grub-install: info: drive = 0.
grub-install: info: the size of hostdisk//dev/rsd0c is 104857600.
grub-install: info: succeeded in opening the core image but the data is different.
grub-install: error: cannot read `/boot_grub_part2/grub/i386-pc/core.img' correctly.

WTF “impossible”, you have a whopping 2 GIGABYTES of space to embed yourself, you dummy. I told you explicitly: write your “core.img” into /dev/sd0k, write its blocklists as the PBR, and install your module files into /boot_grub_part2. What is wrong with you?

GRUB requires to be installed into a “partition with an FS”, because it insists on copying its “dynamic modules” into the same filesystem.

So, the only way to install GRUB reliably is to do both:

  1. Replace the standard MBR with GRUB’s specific code.
  2. Make sure that “core.img” is small enough to fit into 63 blocks between MBR and the first partition.
  3. Give it a dedicated fat/msdos partition to put its modules and grub.cfg into.

And to make (2) work, you MUST use grub 2.06, because the newer versions of GRUB produce a larger “core.img”.

1.7. Summary

After wasting some time, I eventually managed to install grub-2.06 into the “gap”, and boot OpenBSD from it. Not sense of joy ensued.

1.8. Post Scriptum (Using GRUB withe EFI)

GRUB with EFI is also stupid, but less so. I had to install it onto another device, and this is, strictly speaking, unrelated to OpenBSD and MBR, but I have no other better fitting file.

GRUB on EFI needs access to efivars on Linux. (Yes, I did this on Linux, not OpenBSD.)

mkdir /mnt/efi
mount /dev/sda1 /mnt/efi
mkdir -p /mnt/efi/EFI
umount /mnt/efi
mount -t efivarfs none /sys/firmware/efi/efivars
grub-install --target=x86_64-efi /dev/sda
grub-mkconfig -o /boot/grub/grub.cfg

I was in much of a less convoluted situation now.

  1. /boot/grub is just a directory on the root filesystem.
  2. EFI partition does not need to be mounted anywhere.
  3. What is the most reasonable place to install grub modules in such a case? Well, since your grubx64.efi goes into (hd0,1)/EFI/grub/, it is most reasonable to place the modules into (hd0,1)/EFI/grub/grub/, and the file grub.cfg into (hd0,1)/EFI/grub/grub/grub.cfg. Right? Right???
  4. Well, GRUB is, of course, smarter than that. It does not want to “just care about EFI fat in its core.img”, because that would mean that core.img would be able to ignore the filesystem drivers and all that complexity, and just load the modules and config from the same folder where core.img is. But of course, GRUB would never do something so lowly and profane. It needs to have drivers in the core, and therefore when you run grub-install, it will generate grubx64.efi with filesystem drivers and pointers to (hd0,3)/boot/grub/.

Also, of course grub-mkconfig messed up the kernels and generated a lot of noodle-like code in grub.cfg, but by chance, the kernel I needed to boot ended up as the first one, so whatever.