Linux – Windows 10 UEFI Physical to KVM/libvirt Virtual

linux-kvmp2vuefiwindows 10

Original Post

I am migrating my PC from Windows 10 to Linux. There are a few things for which I still need Windows, and I am currently dual-booting, with Windows and Linux on separate physical disks. I'd like to get away from dual-booting, and run my Windows 10 installation virtualized under KVM+libvirt+qemu.

The tricky part here seems to be that my Windows 10 install was done through UEFI (with GPT partition table), rather than legacy BIOS MBR. Here's what my Windows disk looks like:

$ sudo parted /dev/nvme0n1 print
Model: Unknown (unknown)
Disk /dev/nvme0n1: 500GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End    Size    File system  Name                          Flags
 1      1049kB  524MB  523MB   ntfs         Basic data partition          hidden, diag
 2      524MB   628MB  104MB   fat32        EFI system partition          boot, esp
 3      628MB   645MB  16.8MB               Microsoft reserved partition  msftres
 4      645MB   500GB  499GB   ntfs         Basic data partition          msftdata

Since it was setup as UEFI, it seems there are some extra steps needed to virtualize, as libvirt doesn't appear to support UEFI out of the box. What I tried was to export each of the above partitions as a qcow2 image, with a command like this:

$ qemu-img convert -f raw -O qcow2 /dev/nvme0n1p1 win10_part1.qcow2

And repeated for all four partitions. Then I created a virtual machine under virt-manager, importing all four of the qcow2 drives. I installed the "ovmf" package for my distro (Manjaro), and added this line into the virtual machine's XML config file, in the "os" section:

<loader type='rom'>/usr/share/ovmf/x64/OVMF_CODE.fd</loader>

When I boot the virtual machine, I see the TianoCore splash screen. But it just drops me into a grub2 shell, rather than finding the Windows bootloader.

I also tried booting this VM from the Windows 10 install ISO, hoping that I could "repair" the system to boot. But that did not work.

I'm sure I'm missing something. Even better would be to convert this to MBR boot, just to avoid the OVMF dependency.


Per Dylan's comment, I did get it working, but a number of small issues came up along the way, I thought I'd post them here in case others have similar issues.

First step was, as Dylan wrote, to create an image of the whole disk, rather than individual per-partition disks. I used this command:

qemu-img convert -f raw -O qcow2 /dev/nvme0n1 win10_import.qcow2

I then created the virtual machine in virt-manager, specifying the above disk image ("win10_import.qcow2") as my drive.

Next was to use the OVMF (TianoCore) UEFI firmware. This was done by installing the ovmf package ("ovmf" on Manjaro), then adding it to the virtual machine's XML definition:

    <type arch='x86_64' machine='pc-q35-3.0'>hvm</type>
    <loader type='rom'>/usr/share/ovmf/x64/OVMF_CODE.fd</loader>

After that, Windows would still crash during the boot, with a bluescreen and the error "SYSTEM THREAD EXCEPTION NOT HANDLED". For some reason, it didn't like the "Copy host CPU configuration" CPU setting. I changed to "core2duo", and that booted. Right now I'm using "SandyBridge" and that also works. (For what it's worth, I did create another, separate Win10 VM doing a fresh install from scratch. That VM did work with the "Copy host CPU configuration". My CPU is AMD Ryzen 5 2400G.)

Next problem I encountered was that Windows 10 ran unbearably slow. Somehow I managed to create the VM with the "QEMU TCG" hypervisor, rather than "KVM". This makes sense, as the former is emulation and dreadfully slow, while the latter is true hardware-assisted virtualization. (How this happened: while trying to get this to work, I also did a BIOS upgrade on the physical system, which reset all my BIOS settings, one of which disabled virtualization (called "SVM" in my BIOS). Once I corrected that, I was able to use the near-native speed KVM hypervisor.)

Next issue was that the screen resolution was stuck at 800×600. Windows wouldn't let me change it. I could do a one-time fix by pressing Esc as soon as the machine boots, right when the TianoCore splash appears. That takes me into UEFI settings, where I can force a higher resolution. But this isn't a permanent fix.

Since my virtual machine specified QXL as the video device, I needed to install the QXL drivers in Windows. This page, Creating Windows virtual machines using virtIO drivers explains how to do that. The short version is this: download the virtio-win iso on the host machine. Add it to the VM as a CD-ROM drive. Then, boot into the VM, navigate to the right folder on the CD-ROM, and install all the needed VirtIO drivers. Specifically, for QXL video on Windows 10, the "qxldod" folder has the right driver.

Best Answer

  • QEMU/Libvirt expect you to provide virtual disk: your QCOW2 files should be disk and not partitions. By doing what you did, you got 4 qcow2 files, each with a single partition. You broke the previous structure, it is not a surprise that GRUB cannot boot your system anymore.

    I suggest you to convert the whole physical drive to a single QCOW2 file, and then attach this virtual drive to your VM.

    You should be able to remove the GRUB EFI file from the EFI partition (see libguestfs tools) and get ride of the boot menu, as the Windows boot loader should be loaded by the VM's UEFI.