FreeBSD Boot Code (Boot Loaders)

Boot Code

  • UFS boot code hardly changes

  • ZFS requires an up-to-date boot code as new features require support from yhe boot code.

  • The ESP is created as a file system, so it’s bo longer possible to update it with gpart bootcode

  • The ESP is mointed as default since FreeBSD 13.

Boot Code Types

We have quite a few possible boot configurations options:

  • MBR
  • MBR EFI
  • GPT Legacy
  • GPT EFI
  • GPT Both

Update Obstacles

  • Different partition layouts mean the boot code is not always in the same place.
  • Multiple disks - if disks are mirroed/raid, we need to update all copies.
  • Are EFI files always in the FreeBSad namespace on the ESP?
  • Legacy boot code may be larger than the provided freebsd-boot GPT partition prior to FreeBSD 10? I thnk - you could reduce swap size to make it bigger? GPT partition ordwr doesn’t matter - doesnt need to be first on thr list.

ZFS Upgrades

  • Enabling new zpool features requires updated bootcode
  • Prior to 13.0 running zpool upgrade provided instructions for updating - which were often wrong and dangerous - lost as part of the switch to OpenZFS 2.0

We need to know what to update, note linux doesnt have thks issue because it doesn’t do root on ZFS.

Since FreeBSD does use root on zfs, we unfortunately have to make sure the boot code supports the zfs features used by the pool.

Which one did we boot with? We can run the command sysctl machdep.bootmethod as shown here:

$ sysctl machdep.bootmethod
machdep.bootmethod: BIOS

The above console command shows the system was booted with BIOS mode

If you have both, you should probably update both to avoid having a second boot option that becomes stale.

Gpart show to find partitions

$ gpart show
=>        34  1953525101  ada0  GPT  (932G)
          34           6        - free -  (3.0K)
          40        1024     1  freebsd-boot  (512K)
        1064    50331648     2  freebsd-swap  (24G)
    50332712  1903192416     3  freebsd-zfs  (908G)
  1953525128           7        - free -  (3.5K)

=>        34  1953525101  ada1  GPT  (932G)
          34           6        - free -  (3.0K)
          40        1024     1  freebsd-boot  (512K)
        1064    50331648     2  freebsd-swap  (24G)
    50332712  1903192416     3  freebsd-zfs  (908G)
  1953525128           7        - free -  (3.5K)

The above shows we have to drives with boot, swap, and zfs partitions. We can verify with

$ ls -l /dev/ada0*
crw-r-----  1 root  operator  0x58 Oct 16 04:55 /dev/ada0
crw-r-----  1 root  operator  0x5a Oct 16 04:55 /dev/ada0p1
crw-r-----  1 root  operator  0x5c Oct 16 04:55 /dev/ada0p2
crw-r-----  1 root  operator  0x5e Oct 16 04:55 /dev/ada0p3

We can confirm running zfs

$ zfs list
NAME                                     USED  AVAIL  REFER  MOUNTPOINT
zroot                                    442G   434G   144K  none
zroot@2014-06-08_06.00.00--1m               0      -   144K  -
zroot@2014-06-15_06.00.00--1m               0      -   144K  -
zroot@2014-06-22_06.00.00--1m               0      -   144K  -
zroot@2014-06-29_06.00.00--1m               0      -   144K  -
zroot@2014-07-06_06.00.00--1m               0      -   144K  -
zroot/ROOT                               434G   434G   144K  none
zroot/ROOT/10.0-STABLE                     8K   434G   409G  /
zroot/ROOT/10.4-RELEASE                    8K   434G   410G  /
zroot/ROOT/12-3                          197G   434G   411G  /
zroot/ROOT/12-3@2025-10-16-02:42:13     6.50M      -   409G  -
zroot/ROOT/12-3@2025-10-16-05:12:58     8.93M      -   410G  -
zroot/ROOT/default                       237G   434G   237G  /
zroot/ROOT/default@2022-05-26-20:52:39   512K      -   237G  -
zroot/tmp                                390M   434G   390M  /tmp
zroot/usr                               2.68G   434G   144K  /usr
zroot/usr/home                           127M   434G   127M  /usr/home
zroot/usr/ports                         1.34G   434G  1.34G  /usr/ports
zroot/usr/src                           1.21G   434G  1.21G  /usr/src
zroot/var                               4.71G   434G  4.61G  /var
zroot/var/crash                          148K   434G   148K  /var/crash
zroot/var/log                            101M   434G   101M  /var/log
zroot/var/mail                           216K   434G   216K  /var/mail
zroot/var/tmp                            424K   434G   424K  /var/tmp

Or

$ mount | grep zfs
zroot/ROOT/12-3 on / (zfs, local, noatime, nfsv4acls)
zroot/tmp on /tmp (zfs, local, noatime, nosuid, nfsv4acls)
zroot/usr/home on /usr/home (zfs, local, noatime, nfsv4acls)
zroot/usr/ports on /usr/ports (zfs, local, noatime, nosuid, nfsv4acls)
zroot/usr/src on /usr/src (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var on /var (zfs, local, noatime, nfsv4acls)
zroot/var/crash on /var/crash (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var/log on /var/log (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var/mail on /var/mail (zfs, local, nfsv4acls)
zroot/var/tmp on /var/tmp (zfs, local, noatime, nosuid, nfsv4acls)

Backup existing

BIOS

$ gpart show
=>        34  1953525101  ada0  GPT  (932G)
          34           6        - free -  (3.0K)
          40        1024     1  freebsd-boot  (512K)
        1064    50331648     2  freebsd-swap  (24G)
    50332712  1903192416     3  freebsd-zfs  (908G)
  1953525128           7        - free -  (3.5K)

=>        34  1953525101  ada1  GPT  (932G)
          34           6        - free -  (3.0K)
          40        1024     1  freebsd-boot  (512K)
        1064    50331648     2  freebsd-swap  (24G)
    50332712  1903192416     3  freebsd-zfs  (908G)
  1953525128           7        - free -  (3.5K)

This shows:

  • LBA 0: pmbr (protective MBR bootcode)
  • LBA 1–33: Primary GPT header + table
  • LBA 34–39: padding
  • LBA 40–1063: freebsd-boot (1024 sectors = 512 KiB) — this contains gptzfsboot
  • Rest: swap + ZFS

So, if you copy sectors 0…1063, you capture pmbr + GPT primary + the entire freebsd-boot partition:

dd if=/dev/ada0 of=/root/ada0.bootcode+gpt.img bs=512 count=1064

Repeat for the mirrored disk:

dd if=/dev/ada1 of=/root/ada1.bootcode+gpt.img bs=512 count=1064

(Better) separated backups

If you want more flexibility (recommended), back up the pieces separately:

  1. GPT partition layout (primary + backup tables)
gpart backup ada0 > /root/ada0.gpt
gpart backup ada1 > /root/ada1.gpt
  1. Only the pmbr (LBA 0)
dd if=/dev/ada0 of=/root/ada0.pmbr bs=512 count=1
dd if=/dev/ada1 of=/root/ada1.pmbr bs=512 count=1
  1. The freebsd-boot partition content itself
dd if=/dev/ada0p1 of=/root/ada0p1.freebsd-boot.img bs=512 conv=sync,noerror
dd if=/dev/ada1p1 of=/root/ada1p1.freebsd-boot.img bs=512 conv=sync,noerror

(Using ada0p1 reads just that 512 KiB boot partition, without touching the rest of the disk.)

Quick verification

Sizes you should see:

ada0p1.freebsd-boot.img ≈ 524,288 bytes (512 KiB)

ada0.pmbr = 512 bytes

ada0.bootcode+gpt.img = 1064 * 512 = 545,792 bytes

Sanity checks:

file /root/ada0p1.freebsd-boot.img
strings /root/ada0p1.freebsd-boot.img | head
sha256 /root/ada0p1.freebsd-boot.img

Restore recipes (pick one style)

A) “Single blob” (what dd count=1064 created)

# DANGER: writes to the start of the disk – choose the correct disk!
dd if=/root/ada0.bootcode+gpt.img of=/dev/NEWDISK bs=512 count=1064
# Then recreate the rest from your normal backups (zpool, etc.) or restore GPT fully:
gpart restore NEWDISK < /root/ada0.gpt

B) Separated (safer & clearer)

  1. Recreate partitioning:
gpart destroy -F NEWDISK        # only if needed on a replacement disk
gpart create -s gpt NEWDISK
gpart restore NEWDISK < /root/ada0.gpt
  1. Restore boot partition contents:
dd if=/root/ada0p1.freebsd-boot.img of=/dev/NEWDISKp1 bs=512
  1. (Alternative to step 2) Reinstall from system files instead of an image:
gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 NEWDISK

Tip: Do this on both mirror members (e.g., ada0 and ada1) so either disk can boot if the other fails.

For a cleaner, restorable setup, back up GPT, pmbr, and /dev/ada?p1 separately as shown above, and you’ll be able to restore either by raw copy or by re-installing standard bootcode.

UEFI (Do not do this for BIOS)

Since 13.0 the EFI partition should be mounted already:

# mount | grep efi
/dev/gpt/efiboot0 on /boot/efi (msdosfs, local)

Check efi directory

# cd /boot/efi
# ls
efi

Backup:

# cd efi
# ls
boot freebsd
# cd freebsd
# cp loader.efi /root/loader.efi.bak

To restore copy back

Install BIOS bootloader

# gpart bootcode -b /boot/pmbr /boot/gptzfsboot -i 1 /dev/ada0
# gpart bootcode -b /boot/pmbr /boot/gptzfsboot -i 1 /dev/ada1

Index (-i) is freebsd-boot from gpart show

If not using zfs and want to update boot code, simply remove zfs:

# gpart bootcode -b /boot/pmbr /boot/gptboot -i 1 /dev/ada0
# gpart bootcode -b /boot/pmbr /boot/gptboot -i 1 /dev/ada1

Alternatively, easier:

# bsdinstall bootconfig

Other options

sysutils/loaders-update