Donnerstag, 16. Mai 2013

Converting a Windows Virtual Machine from VirtualBox to QEMU and KVM using libguestfs tools

Virtual Machines and Windows

While I like VirtualBox a lot when working with virtual machines, sometimes I need to use other emulation or virtualisation technologies, such as Bochs, QEMU or KVM. This is not a big deal for Linux virtual machines, as standard Linux distribution kernels are made to run on almost all hardware in existence, so that unless you change the CPU architecture of the virtualised system, a Linux image will boot on almost any virtualisation technology without major issues. This is not the case for Windows systems, at least not for Windows XP, which is what I have used in the past. Windows machines do not react very well to changes to the underlying hardware, probably because for some types of hardware devices, such as hard disk controllers or CPUs, drivers are only installed for the devices present during installation. Thus, when you change your virtualisation technology and thus the emulated hardware, you are likely to see many blue screens of death. I have also run into these issues, but managed to find a few workarounds that I was able to apply to make a VirtualBox Windows XP machine boot in QEMU. The following sections will describe these.

Test Case

I recently discovered that Microsoft offers virtual machines for cross-browser testing,  i.e., virtual machines that come pre-installed with a version of Windows and a matching version of Internet Explorer, for example Internet Explorer 8 on Windows XP, or Internet Explorer 10 on Windows 7. The operating system on these machines comes without a pre-configured product e Windows XP machines can be used for 30 days. If you choose Linux as your host operating system, virtual machine images are offered for VirtualBox. I will use the Windows XP/IE 8 image (MD5 Sum) to describe the conversion process.The .sfx file is a RAR archive that you can extract to obtain the file IE8 - WinXP.ova, which is basically just a TAR archive containing the virtual machine description file IE8 - WinXP.ovf and the virtual disk image IE8 - WinXP-disk1.vmdk in VMware's VMDK format. The actual disk image can be extracted as follows:
$ unrar x IE8.WinXP.For.LinuxVirtualBox.sfx

UNRAR 4.10 freeware Copyright (c) 1993-2012 Alexander Roshal

Extracting from IE8.WinXP.For.LinuxVirtualBox.sfx

Extracting  IE8 - WinXP.ova                          OK
All OK

$ tar xvf "IE8 - WinXP.ova"
IE8 - WinXP.ovf
IE8 - WinXP-disk1.vmdk

Disk Image Conversion and Manipulation

The most interesting part for me is the virtual disk image that contains the installed Windows system. While the VMDK format is supported by QEMU, KVM, and even Bochs, I decided to convert the image to a raw format, as it is easier to handle and manipulate. This can be achieved using the qemu-img tool that comes with QEMU:
$ qemu-img convert -O raw "IE8 - WinXP-disk1.vmdk" windows.raw
This will take a while, afterwards you'll have a raw disk image windows.raw with a size of 127GB, but as it is created as a sparse file, ony 1.1GB on disk:
$ ls -lh windows.raw
-rw-r--r-- 1 xxx xxx 127G Mai 15 23:31 windows.raw
$ du -sh windows.raw
1.1G    windows.raw
You can then start the disk image, for example in QEMU:
$ qemu windows.raw 

 The machine will start booting and then flash a BSOD at you and restart. If you press F8 during boot and select "Disable automatic restart on system failure", you will have chance to actually see the error message:
The error message doesn't give much details about the potential cause, but when you do a web search for STOP: 0x0000007B, you will soon find out that this is a typical problem when moving a Windows hard drive to different hardware, and has been documented by Microsoft in knowledge base article 314082. The article mentions that the reason for the error is that not all hard disk controllers are installed on the system and/or that the registry does not contain the necessary information to actually use the correct controller. The workaround is to copy the drivers and create the necessary registry keys. This can be easily achieved by just booting the original disk image in VirtualBox and performing the necessary steps outlined in the article prior to converting it for use with another virtualisation technology. In the case of the IE compatibility test virtual machines, this has several drawbacks, however:
  • The machine has to be imported into VirtualBox first, which may take some time.
  • Booting the machine starts the 30-day period that one has to use the machine before it must bei either activated, reinstalled, or - as suggested by Microsoft - restoring a snapshot directly created after the intial import.
As I was looking for a way to create a template VM that I use without having to worry about expiration, I looked for a way to fix the VM without booting the installed Windows OS. This can be achieved using the libguestfs tools. Using virt-ls, it can be determined which of the necessary drivers are already present in the C:\WINDOWS\system32\drivers directory:
$ virt-ls -a windows.raw -l /WINDOWS/system32/drivers/{atapi.sys,intelide.sys,pciide.sys,pciidex.sys}
-rwxrwxrwx 1 0 0 96512 Apr 14  2008 /sysroot/WINDOWS/system32/drivers/atapi.sys
-rwxrwxrwx 1 0 0 5504 Apr 14  2008 /sysroot/WINDOWS/system32/drivers/intelide.sys
libguestfs: error: ll: ls: cannot access /sysroot/WINDOWS/system32/drivers/pciide.sys: No such file or directory
-rwxrwxrwx 1 0 0 24960 Apr 14  2008 /sysroot/WINDOWS/system32/drivers/pciidex.sys
As you can see, only one of them is missing in my case, pciide.sys. This driver can be found in the cabinet file C:\WINDOWS\Driver Cache\i386\driver.cab, which can be extracted from the virtual machine image as follows, using the virt-copy-out tool:
$ virt-copy-out -a windows.raw  "/WINDOWS/Driver Cache/i386/driver.cab" .
Next, the necessary driver can be extracted from the cabinet file by using cabextract:
$ cabextract -F pciide.sys driver.cab
Extracting cabinet: driver.cab
  extracting pciide.sys

All done, no errors.
Then, the missing driver can be copied back to the disk image using virt-copy-in:
$ virt-copy-in -a windows.raw pciide.sys /WINDOWS/system32/drivers/
The next step will be to insert the necessary entries into the Windows registry. To do so, copy the registry information listed in the knowledge base article to a file, for example mergeide.reg. This file will work fine if you use within the fully booted VM, however, if you manipulate the registry from an "external" perspective this will not work. The reason is that the CurrentControlSet is more or less a pointer to the active control set of a running Windows system, i.e. either ControlSet001 or ControlSet002. Therefore, to import the correct settings with virt-win-reg, you'll need to change all instances of CurrentControlSet in the registry file to ControlSet001, using sed, for example:
$ sed -i -e 's,CurrentControlSet,ControlSet001,' mergeide.reg
Then, the registry information can be imported into the VM using the virt-win-reg tool:
$ virt-win-reg --merge windows.raw mergeide.reg
These changes (for my test case) suffice to boot the Virtual Machine in QEMU. When you try to boot it in KVM, the machine first starts to boot, but soon after that shows another bluescreen, as shown in the following image:


Searching for the error code STOP: 0x000000CE combined with the driver name intelppm.sys turns up a bug report on the VirtualBox forums and a post on Ben Armstrong's Virtualization Blog on MSDN Blogs. The recommendation there seems to be to disable the intelppm.sys and processr.sys drivers by changing the Start registry keys of

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Processor
and
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Intelppm

to 4. I played around with these and tried to disable these drivers for good, without booting the VM once, by creating registry files with these values and merging them into the VM registry, to no avail. Windows always kept re-enabling the "Processor" driver on first reboot, after detecting "new hardware". However, as the VirtualBox forum posts suggested, removing the actual driver files appears to do no harm, so I experimented with just removing them and the corresponding device information in a file called cpu.inf, using the guestfish utility. This appeared to get rid of the bluescreens and let me finally boot the VM in KVM. Here's what I did:

$ guestfish --rw -a windows.raw  --mount /dev/sda1

Welcome to guestfish, the libguestfs filesystem interactive shell for
editing virtual machine filesystems.

Type: 'help' for help on commands
      'man' to read the manual
      'quit' to quit the shell

> rm /WINDOWS/system32/dllcache/processr.sys 
> rm /WINDOWS/system32/drivers/processr.sys 
> rm /WINDOWS/system32/drivers/intelppm.sys 
> rm /WINDOWS/inf/cpu.inf 
> umount-all 
> exit
This was enough for me to make the VM bootable in KVM. On first boot, it starts setting up the virtual hardware that changed when going from VirtualBox to KVM, and finally asks whether it may connect to the Internet for one unknown piece of hardware (the CPU). After denying that and rebooting the machine, it was working fine, without any BSOD. You'll just have to live with the unknown processor device being displayed in the device manager. I had no success booting it in Bochs so far, as it has issues recognizing the correct drive geometry and thus fails booting the system. I'll update this blog post when I solved that issue as well, and should I run into any other issues.