Using Grub 2 as a bootloader for Xen PV guests
Background: Introduction to Xen PV Bootloaders
In the very early days of Xen it was necessary for the host (domain 0)
administrator to explicitly supply a kernel (and perhaps initial
ramdisk) from the domain 0 filesystem in order to start a new guest.
This mostly worked and for some use cases, i.e. those where the host
admin wants very strict control over what each guest runs, was
desirable and remains so today.
However for other use cases it was rather inflexible since it meant
that the host administrator needed to be involved in what many
considered to be a guest administrator, or even distribution level,
decision i.e. the selection of which kernel to run, with what
parameters etc.
The first solution to this problem to come along was
pygrub
. pygrub
is an application (written in Python) which
can be used by the Xen toolstack in domain 0 as a kind of
pseudo-bootloader. pygrub
will open the guest file system (using a
userspace filesystem access library), extract a configuration file,
parse it and extract the referenced kernel, (optional) initial ramdisk
and kernel command line to be booted almost as if they were provided
from the domain 0 filesystem.
pygrub
initially supported configuration files in the GNU
grub (known as “grub-legacy” by upstream today) menu.lst
syntax, which was a common option for distributions at the
time. pygrub
even supported an optional curses frontend menu similar
to the native grub legacy
interface, allowing boot time selection
between multiple kernels, as well as editing of the command line etc.
This allowed host admins configure a guest to use pygrub
and thereby
delegate the management and selection of the guest kernel to the guest
administrator. Guest administrators could use the usual tools which
they expect (i.e. distribution packaging and grub integration) and
configuration file syntax from their non-Xen systems.
Since it was introduced pygrub
has gone from supporting the
grub-legacy menu.lst
configuration files to supporting a variety of
configuration files including the syntaxes of GNU grub 2,
[syslinux][syslinux] (which includes pxelinux
, syslinux
, and isolinux
)
and LiLo. pygrub
remains part of the Xen releases today (and
will be for the foreseeable future) however it has some short comings:
- The configuration file parsers are simplistic and cannot cope with
all of the constructs which can be used in practice. In particular
the grub 2 syntax is a particularly expressive shell-like language
whichpygrub
can only cope with basic elements of and that support
is fragile requiring updates as grub-upstream and distributions find
new ways to make use of the flexibility allowed. This is
particularly troublesome now that grub 2 is the default in many
distributions. - It ultimately boots a potentially untrusted guest kernel as if it
was a kernel supplied from the domain 0 filesystem (which would
normally be somewhat implicitly trustworthy). This means that the
code to build a domain now must take special care to treat the
kernel image as hostile.
As a result of these shortcomings pvgrub
was created (it’s an
unfortunate source of much confusion that pygrub
and pvgrub
differ
only in the descender on a single letter). pvgrub
was a port of GNU
grub (AKA “grub-legacy”) to run as a Xen PV kernel. This had
several advantages:
- Since the
pvgrub
kernel is supplied by the host administrator it
is trusted and can be expected not to deliberately attack the domain
builder. All handling of the untrusted guest inputs now happens
within guest context where the harm which can be done is contained. - Since the code is actually real
grub-legacy
it has the same
configuration file parser and features as grub running on a native
system.
One minor downside of pvgrub
is that it did not support syslinux
or LiLo
configuration files (that would need to be achived via a PV
port of those respective bootloaders), although in practice they are
not so widely used so this was a minor shortcoming.
A second shortcoming was that it is not possible for a PV guest to
switch between 32- and 64-bit operation. This means that the user
needs to know a-priori the type of kernel which they will be booting
and the host administrator needs to provide the ability to select
between 32- and 64-bit builds of pvgrub
.
The most serious problem today though is the move of most
distributions from grub-legacy
to grub 2
, with its radically
different architecture and configuration file syntax. This meant that
admins could no longer simply reuse their existing grub 2 based
workflows and distribution integration. Some workarounds have evolved
such as pv-grub-menu
but something better was needed…
PV Grub 2
In November 2013 upstream grub maintainer Vladimir ‘phcoder’
Serbinenko announced that:
pvgrub2 has just became part of upstream grub as ports i386-xen and x86_64-xen.
This meant that it was now possible to compile the upstream grub 2
code base to run as a pvgrub2
Xen PV guest, in much the same way as
the original pvgrub-legacy
port of Grub legacy.
This has the same advantages as the pvgrub-legacy
port originally
had, except using the more modern grub 2
code base. In addition
since this support was part of upstream grub there was no fork to
maintain and therefore no risk that the Xen PV support would languish.
In the remainder of this blog post I’m going to explain how to install
and use pvgrub2
on your Xen hosts and guests.
Guest Setup
This guide assumes that the PV guest is already setup with a grub2
configuration file, as if it were a native system (i.e. usually
/boot/grub/grub.cfg
). Many distribution installers (at least those
for distributions which use grub2) will do this automatically even
within a PV guest. If not then you may need to install manually,
e.g. on Debian by installing the grub-pc
package.
Building and installing pvgrub2
Getting the source
The last release of grub was 2.00, released in June 2012, which is
before PV Xen support was added. Since then the grub development team
have released beta versions of grub 2.02. In particular the latest,
2.02~beta2, contains support for Xen. (Don’t be scared off by the beta
tag, in reality several distributions are shipping this version as
their primary bootloader).
Grub 2.02~beta2 can be downloaded from
http://alpha.gnu.org/gnu/grub/grub-2.02~beta2.tar.gz.
Alternatively you can fetch the code from the grub git repository:
git clone git://git.savannah.gnu.org/grub.git
Compiling
The file INSTALL in the code tree contains detailed information on how
to build grub
, including a full list of dependencies.
Other than the obvious things, such as make
and gcc
and slightly
less obvious things such as flex
and bison
it is worth noting
that:
- compiling 64-bit grub on a 64-bit platform needs to be able to build
some 32-bit components and therefore requires a biarch capable gcc
(i.e. one which accepts the-m32
option, pretty much all 64-bit
distribution gcc packages today include this feature in the default
compiler) as well as a 32-bit libc (e.g. from the libc6-dev-i386
package on Debian). - the Xen headers must be installed, if you’ve installed Xen from
distribution packages this might require you to install the relevant
-dev package (e.g.libxen-dev
in Debian). If you’ve installed Xen
from source withmake install
then you should have the headers
already.
The process is pretty much the standard configure/make/make install
routine associated with all autoconf
based projects.
If you are compiling from git then you will need to start by
generating the configure
script. This can be skipped if you are
using the tarball.
$ ./autogen.sh
Next, configure grub2 for use on an x86_64-xen
platform:
$ ./configure --target=amd64 --with-platform=xen
To target 32-bit/i386 Xen domains instead use:
$ ./configure --target=i386 --with-platform=xen
The rest of this guide will assume x86_64-xen
, but you can substitute
i386-xen
throughout if you want to boot a 32-bit guest.
If you want to avoid the possibility of messing with the native
bootloader on the system then add --prefix=/opt/grub2
to the
configure
parameters in order to install pvgrub2
in an out of the
way location (if you do this then you may want to add
/opt/grub2/sbin:/opt/grub2/bin
to your $PATH
or else remember to
give an explicit path to the relevant commands).
Once things are configured then:
$ make
Finally, as root:
# make install
Creating a basic pvgrub2
image
Now that grub is installed on the host the next step is to build the
actual pvgrub2
image which will be used to boot the guest. Grub is
highly modular and allows you to construct images with various
features embedded. It also supports loading additional modules at
runtime.
To create a basic image first create a file named grub.cfg
which
contains:
normal (xen/xvda,msdos1)/boot/grub/grub.cfg
This tells grub to read the /boot/grub/grub.cfg
configuration file
from the first partition of the xvda
device (which must be using
MSDOS style partitioning) and run it using the normal
command interpreter.
Finally we can build the grub image using the grub-mkimage
utility:
grub-mkimage -O x86_64-xen -c grub.cfg \
-o grub-x86_64-xen.bin $prefix/lib/grub/x86_64-xen/*.mod
Where:
-O x86_64-xen
: Is the target platform-c grub.cfg
: Includes the configuration file which created above-o grub-x86_64-xen.bin
: Is the output file$prefix/lib/grub/x86_64-xen/*.mod
: Includes all loadable modules
in the image. ($prefix
is wherever you installed grub to, which
is/usr/local
by default, or/opt/grub2
if you added the
--prefix=/opt/grub2
option as discussed above).
The reason for the include *.mod
is that since this image is going
to running in guest context it is not going to be able to load
additional modules from domain 0 at runtime, since it will only see
the guest filesystem.
Now you can start a guest using grub-x86_64-xen.bin
as the
kernel. e.g. by writing in your guest configuration file:
kernel = "grub-x86_64-xen.bin"
This is all that is required. There is no need to give any other
ramdisk
, bootloader
, cmdline
, root
, extra
etc options. You
will still need to configure the name, disks, network devices etc in
the normal way.
Then, assuming your in-guest grub.cfg is indeed located at
(xen/xvda,msdos1)/boot/grub/grub.cfg
, you should be presented with
the usual grub menu when you connect to the guest console.
Loading grub.cfg from any partition
The above simple example is all well and good, but it is rather
inflexible and if the guest uses a separate /boot
partition or
otherwise differs from the expectations encoded in the initial
configuration then it won’t find the real configuration file and you
will be dumped to a grub prompt.
Luckily the grub configuration file syntax is far richer than we’ve
used so far, so lets try something more flexible.
As well embedding a bootstrap configuration grub-mkimage
is also
able to embed a memdisk (essentially an initramfs for the bootloader)
into the pvgrub2
image, we are going to use this in order to bootstrap
into a more capable grub shell.
First we need to create two configuration files which will be embedded
into the pvgrub2
image:
grub-bootstrap.cfg:
normal (memdisk)/grub.cfg
grub.cfg:
if search -s -f /boot/grub/grub.cfg ; then
echo "Reading (${root})/boot/grub/grub.cfg"
configfile /boot/grub/grub.cfg
fi
if search -s -f /grub/grub.cfg ; then
echo "Reading (${root})/grub/grub.cfg"
configfile /grub/grub.cfg
fi
The reason for using two configuration files in this manner is that
the inbuilt grub interpreter is rather simple, whereas features such
as if
and search
are only available from the more
feature complete normal
interpreter. Bootstrapping into this more
complex grub.cfg
allows us to cope with guests which have a separate
/boot
partition by searching for a file named /boot/grub/grub.cfg
or /grub/grub.cfg
on any partition. Note that ${root}
is set by
the search
command and should be entered literally, not substituted
while creating this file.
Next we need to embed the grub.cfg into a memdisk, which is really
just a tarball:
$ tar cf memdisk.tar grub.cfg
Finally we can build the grub image using the grub-mkimage
utility:
grub-mkimage -O x86_64-xen \
-c grub-bootstrap.cfg \
-m memdisk.tar \
-o grub-x86_64-xen.bin \
$prefix/lib/grub/x86_64-xen
Where in addition to the example above we now use:
-c grub-bootstrap.cfg
: Includes the bootstrap configuration-m memdisk.tar
: Includes our memdisk, which includes our more
featureful configuration snippet.
On booting this grub image will now try much harder to find a
grub.cfg
to run and will work on far more guests without special
tweaking.
Chainloading guest pvgrub1
from domain 0 pvgrub2
The above works well in many situations but has one major drawback
which is that the bootloader is still ultimately under the control of
the host admin. This may present a problem for example if the guest
admin wishes to run a guest which makes use of new features found in a
newer version of grub, or if there is a need to load a module which is
not included in the host grub image from the guest filesystem, since
the module may not be compatible with the running grub.
To address this issue the Xen team has published the Xen x86 PV
Bootloader Protocol specification which describes the
in-guest path where a PV bootloader, such as pvgrub2
, should be
installed in order that a host grub can chainload it. Specifically the
bootloader should be installed to either /boot/xen/pvboot-i386.elf
or /boot/xen/pvboot-x86_64.elf
depending on the guest bit size.
Support for this protocol can be implemented in the host grub using
the same grub-bootstrap.cfg
+ memdisk.tar
method as above and a
grub.cfg
which contains:
if search -s -f /boot/xen/pvboot-x86_64.elf ; then
echo "Chainloading (${root})/boot/xen/pvboot-x86_64.elf"
multiboot "/boot/xen/pvboot-x86_64.elf"
boot
fi
if search -s -f /xen/pvboot-x86_64.elf ; then
echo "Chainloading (${root})/xen/pvboot-x86_64.elf"
multiboot "/xen/pvboot-x86_64.elf"
boot
fi
On the guest side you can build and install a grub within the guest
just like for the host. Then run, as root, within the guest:
# grub-install --target=x86_64-xen
Installing for x86_64-xen platform.
grub-install: warning: no hints available for your platform. Expect reduced performance.
grub-install: warning: WARNING: no platform-specific install was performed.
Installation finished. No error reported.
(the two warnings can safely be ignored, there is no actual
performance impact)
Then the Grub image needs to be moved to the standardised location:
# mkdir /boot/xen/
# cp /boot/grub/x86_64-xen/core.elf /boot/xen/pvboot-x86_64.elf
Alternatively you can apply a patch (submitted
upstream but not yet applied) which causes grub-install
to do the
right thing by default and rebuild grub-mkimage
on your host.
For maximum flexibility you can combine both the chainloading and
loading the guest grub.cfg
directly by putting both options in the
host grub’s memdisk grub.cfg, I recommend trying to chainload first
and only then falling back to loading a grub.cfg from the guest with:
if search -s -f /boot/xen/pvboot-x86_64.elf ; then
echo "Chainloading (${root})/boot/xen/pvboot-x86_64.elf"
multiboot "/boot/xen/pvboot-x86_64.elf"
boot
fi
if search -s -f /xen/pvboot-x86_64.elf ; then
echo "Chainloading (${root})/xen/pvboot-x86_64.elf"
multiboot "/xen/pvboot-x86_64.elf"
boot
fi
if search -s -f /boot/grub/grub.cfg ; then
echo "Reading (${root})/boot/grub/grub.cfg"
configfile /boot/grub/grub.cfg
fi
if search -s -f /grub/grub.cfg ; then
echo "Reading (${root})/grub/grub.cfg"
configfile /grub/grub.cfg
fi
Chainloading from pvgrub-legacy
What about all those host systems which provide only the legacy PV
grub by default, as is common in some cloud environments? Fortunately
it is possible to chainload pvgrub2
from pvgrub-legacy
. Simply
create a grub-legacy
configuration file in your guest at
/boot/grub/menu.lst
which contains:
default 0
timeout 1
title Chainload grub2
kernel (hd0,0)/boot/xen/pvboot-x86_64.elf
You will need to adjust (hd0,0)/boot/xen/pvboot-x86_64.elf
to suit
your actual partition layout (sadly I don’t know of any useful tricks
to find it automatically with grub-legacy
).
This will cause the host provided pvgrub-legacy
to chainload into a
guest supplied pvgrub2
.
Distribution Integration
The above describes how to go about using pvgrub2
manually. Ideally
this would all happen automatically as part of the standard
distribution setup.
Debian
The forthcoming Debian 8.0 (Jessie) release will contain support
for both host and guest pvgrub2
. This was added in version
2.02~beta2-17
of the package (bits were present before then, but
-17
ties it all together as described below).
The package grub-xen-host
contains grub binaries configured for
the host, these will attempt to chainload an in-guest grub image
(following the specification and falling back to search for
a grub.cfg in the guest filesystems as described
above). grub-xen-host
is Recommended
by the Xen meta-packages in
Debian or can be installed by hand.
The package grub-xen-bin
contains the grub binaries for both the
i386-xen
and x86_64-xen
platforms, while the grub-xen
package integrates this into the running system by providing the
actual pvgrub2 image (i.e. running grub-install
at the appropriate
times to create an image tailored to the system) and integration with
the kernel packages (i.e. running update-grub
at the right times),
so it is the grub-xen
which should be installed in Debian guests.
At this time the grub-xen
package is not installed automatically so
it will need to be done manually (something which perhaps could be
addressed for Stretch).
Others
I’m not aware of any other distributions which have integrated grub2
support for Xen. I’d be glad to be corrected or equally happy to help
advise on any integration efforts.