I recently migrated my Xen hypervisors from HVM to PVH. I'll write about that later, but since the boot interface changed from legacy (BIOS) to EFI, today I have to migrate my VMs - including the one I'm writing this very post on.
Before doing anything, back up your data!
Boot the VM into a recovery ISO
My go-to is Alpine but anything works. You'll need to get e2fsprogs, dosfstools, gptfdisk and arch-install-scripts packages (which also requires mount and findmnt on Alpine, but they don't get auto-installed).
Convert the MBR partition table to GPT
I installed Debian on these VMs with the automated installer, and my partitions look like this:
localhost:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
xvda 202:0 0 100G 0 disk
├─xvda1 202:1 0 94.9G 0 part
├─xvda2 202:2 0 1K 0 part
└─xvda5 202:5 0 5.1G 0 part
Open the device in gdisk and save changes immediately.
localhost:~# gdisk /dev/xvda
GPT fdisk (gdisk) version 1.0.10
Partition table scan:
MBR: MBR only
BSD: not present
APM: not present
GPT: not present
***************************************************************
Found invalid GPT and valid MBR; converting MBR to GPT format
in memory. THIS OPERATION IS POTENTIALLY DESTRUCTIVE! Exit by
typing 'q' if you don't want to convert your MBR partitions
to GPT format!
***************************************************************
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING PARTITIONS!!
Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/xvda.
The operation has completed successfully.
localhost:~#
The device now looks like this, with the BIOS boot partition removed.
localhost:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
xvda 202:0 0 100G 0 disk
├─xvda1 202:1 0 94.9G 0 part
└─xvda5 202:5 0 5.1G 0 part
However, the device files aren't created in /dev:
localhost:~# ls /dev/xvda*
/dev/xvda
If your system has udev or something similar creating device files, skip to the next section!
Create them with mknod:
localhost:~# mknod /dev/xvda1 b 202 1
localhost:~# mknod /dev/xvda5 b 202 5
Mount the new partitions
In my case, partition 1 contains the data, and partition 5 is a swap partition. I don't need the swap so I'll repurpose that as the EFI partition.
If you need to keep the swap partition, just shrink it by 50MB or so and create a new EFI partition.
I'll do this:
localhost:~# mount /dev/xvda1 /mnt
localhost:~# mkdir /mnt/boot/efi
localhost:~# mkfs.fat -F 32 -n boot /dev/xvda5
mkfs.fat 4.2 (2021-01-31)
mkfs.fat: Warning: lowercase labels might not work properly on some systems
localhost:~# mount /dev/xvda5 /mnt/boot/efi
Regenerate the fstab, too.
localhost:~# genfstab -U /mnt > /mnt/etc/fstab
Chroot into the OS and reconfigure grub
Remove grub for BIOS and install grub for EFI.
localhost:~# arch-chroot /mnt
root@localhost:/# apt update && apt remove -y grub-pc && apt install -y grub-efi
[...]
root@localhost:/# grub-install /dev/xvda5
Installing for x86_64-efi platform.
Installation finished. No error reported.
Since this is a PVH domU, I'll also add the appropriate kernel parameters, and regenerate the config.
root@localhost:/# sed -i s/'GRUB_CMDLINE_LINUX="'/'GRUB_CMDLINE_LINUX="console=hvc0 earlyprintk=xen '/ /etc/default/grub
root@localhost:/# update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.12.48+deb13-amd64
Found initrd image: /boot/initrd.img-6.12.48+deb13-amd64
Found linux image: /boot/vmlinuz-6.12.43+deb13-amd64
Found initrd image: /boot/initrd.img-6.12.43+deb13-amd64
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done
Reboot
If your EFI bootloader is reasonable, this last command will have added an entry for /EFI/debian/grubx64.efi, which the VM will boot into after removing the recovery ISO.
Unfortunately, mine (intentionally) doesn't; EFI variables such as boot entries get reset after a reboot. As EFI looks for a bootloader at /EFI/BOOT/BOOTX64.EFI, we'll just get a No bootable option or device was found error at this point.

I forgot about that!
Back to the recovery ISO then. We'll mount just the EFI partition and copy the files over:
localhost:~# mount /dev/xvda5 /mnt
localhost:~# cd /mnt/EFI
localhost:/mnt/EFI# cp -r debian BOOT
localhost:/mnt/EFI# mv BOOT/grubx64.efi BOOT/BOOTX64.EFI
There, it boots now. All done!
