Author: Siro Mugabi

Category: qemu

Summary:

This entry describes the process of installing the GRUB bootloader in a (QEMU) disk image. Tested on Ubuntu development platforms 12.04 and 13.04.

Tags: qemu linux grub

Update required.

Scenario

The raw format disk image used here already contained a Buildroot compiled filesystem1. It was partitioned as follows:

$ sudo losetup /dev/loop0 brdisk-img.raw
$ sudo fdisk -l -u /dev/loop0

Disk /dev/loop0: 2684 MB, 2684354560 bytes
255 heads, 63 sectors/track, 326 cylinders, total 5242880 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x56a8c587

    Device Boot      Start         End      Blocks   Id  System
/dev/loop0p1            2048     1026047      512000   83  Linux
/dev/loop0p2         1026048     5242879     2108416   83  Linux

where:

  • The first partition will be used as the boot partition. Note that this partition will be (re-)formatted here before having grub installed in it.

  • The second partition already contained the rest of the Buildroot compiled root filesystem i.e. everything other than /boot. In addition, the current working directory also contained the bzImage used to QEMU boot against this disk image2

    $ ls 
    bzImage brdisk-img.raw
    
    $ WORKDIR=$PWD
    
    $ mkdir boot
    

NOTE: GRUB, if used incorrectly, can overwrite the bootloader of your system. So, preferably, the instructions in Sections link and link should be performed from within another QEMU VM environment (running a full blown distro). In fact, the instructions contained here were performed from within a QEMU VM instance running Ubuntu 12.04. Otherwise, if working from a physical machine environment, proceed with extreme caution.

GRUB

The GRand Unified Bootloader (GRUB), or more precisely GRUB 2, is the successor of the older GRUB Legacy (also known as GRUB version 0.9x). For the differences between GRUB Legacy and GRUB, follow this link.

Download, configure and build GRUB:

$ cd $WORKDIR
$ wget -c ftp://ftp.gnu.org/gnu/grub/grub-2.00.tar.xz
$ tar xJf grub-2.00.tar.xz 
$ mkdir {gtemp,gbuild,gv2}
$ GRUB_INSTALL=${WORKDIR}/gv2
$ GRUB_TEMP=${WORKDIR}/gtemp
$ GRUB_SOURCE=${WORKDIR}/grub-2.00
$ cd $GRUB_TEMP

$ sudo apt-get install bison flex autoconf automake autotools-dev libtool gettext libdevmapper-dev

$ ${GRUB_SOURCE}/configure --prefix=${GRUB_INSTALL}/usr --enable-device-mapper
...
*******************************************************
GRUB2 will be compiled with following components:
Platform: i386-pc
With devmapper support: Yes
With memory debugging: No
With disk cache statistics: No
efiemu runtime: Yes
grub-mkfont: No (need freetype2 library)
grub-mount: No (need FUSE library)
starfield theme: No (No grub-mkfont)
With libzfs support: No (need zfs library)
*******************************************************

$ make [-jN]
$ make install

This should result in the following GRUB installation:

$ cd $WORKDIR

$ tree -L 3 gv2/
gv2/
└── usr
    ├── bin
    │   ├── grub-editenv
    │   ├── grub-fstest
    │   ├── grub-kbdcomp
    │   ├── grub-menulst2cfg
    │   ├── grub-mkfont
    │   ├── grub-mkimage
    │   ├── grub-mklayout
    │   ├── grub-mkpasswd-pbkdf2
    │   ├── grub-mkrelpath
    │   ├── grub-mkrescue
    │   ├── grub-mkstandalone
    │   └── grub-script-check
    ├── etc
    │   ├── bash_completion.d
    │   └── grub.d
    ├── lib
    │   └── grub
    ├── sbin
    │   ├── grub-bios-setup
    │   ├── grub-install
    │   ├── grub-mkconfig
    │   ├── grub-mknetdir
    │   ├── grub-ofpathname
    │   ├── grub-probe
    │   ├── grub-reboot
    │   ├── grub-set-default
    │   └── grub-sparc64-setup
    └── share
        ├── grub
        ├── info
        └── locale

Installing GRUB in the Disk Image

Apparently, GRUB only works on partitions attached to loopback devices via /dev/mapper/loopN, as opposed to those attached via /dev/loopN. So proceeding with kpartx(8)3:

$ cd $WORKDIR
$ sudo apt-get install kpartx # if not yet installed
$ sudo kpartx -l brdisk-img.raw 
loop0p1 : 0 1024000 /dev/loop0 2048
loop0p2 : 0 4216832 /dev/loop0 1026048
loop deleted : /dev/loop0

$ sudo kpartx -a brdisk-img.raw 
$ sudo mkfs.ext3 /dev/mapper/loop0p1 
$ sudo mount /dev/mapper/loop0p1 boot

Now, if performing these instructions in a physical machine environment, be warned that the grub-install command below could overwrite the bootloader of your system if used incorrectly:

  • The value against --root-directory must be set to the boot partition mount of the QEMU disk image.

  • The specified device must be the loopback device attached to the entire disk image - in this case, /dev/loop0 rather than /dev/loop0p1.

Install GRUB in the disk image:

$ cd $WORKDIR
$ sudo ${GRUB_INSTALL}/usr/sbin/grub-install --no-floppy --root-directory=./ /dev/loop0

Note: On my system, the grub-install command emitted a slew of warnings:

device-mapper: table ioctl failed: No such device or address
device-mapper: table ioctl failed: No such device or address
device-mapper: table ioctl failed: No such device or address
device-mapper: table ioctl failed: No such device or address
...

...but still completed successfully...

Installation finished. No error reported.

The grub-install command should result in the following installation in ${WORKDIR}/boot:

$ cd $WORKDIR
$ tree -L 2 boot/ 
boot/
├── grub
│   ├── grubenv
│   ├── i386-pc
│   ├── locale
│   └── themes
└── lost+found

Now create grub.cfg such that:

$ cat boot/grub/grub.cfg

set default="0"
set timeout="3"

menuentry "Buildroot" {
    insmod gzio
    insmod part_msdos
    insmod ext2
    linux (hd0,msdos1)/bzImage root=/dev/sda2 rw console=tty0 console=ttyS0
}

The console=tty0 console=ttyS0 kernel commandline parameters are not essential. They are included here for debugging purposes.

Finally, install bzImage and cleanup:

$ sudo cp -a bzImage boot
$ ls boot/
bzImage  grub  lost+found

$ sudo umount boot
$ sudo kpartx -d brdisk-img.raw 
loop deleted : /dev/loop0

Testing

Your QEMU commandline may vary somewhat. The important thing to note is the fact that no -kernel option and kernel image are specified here:

$ cd $WORKDIR
$ qemu-system-i386 -machine accel=kvm:tcg brdisk-img.raw -serial stdio

QEMU Grub boot
Strike e on the keyboard to view (and, if desired, edit) the GRUB boot entry:

QEMU Grub Menu

Hit F10 to proceed with the boot:

QEMU FB VT Serial Console

The buildroot login: prompt on both the Linux FB VT and the QEMU launch xterm was possible due to the combination of the -serial stdio QEMU commandline option, the kernel configuration CONFIG_SERIAL_8250_CONSOLE=y setting, the console=tty0 console=ttyS0 Linux commandline settings, and the Buildroot root filesystem /etc/inittab configuration4:

[root@buildroot ~]# cat /etc/inittab
...
1:1:respawn:/sbin/getty 38400 tty1
2:1:respawn:/sbin/getty 38400 tty2

S0::respawn:/sbin/getty -L ttyS0 115200 vt100
...

UUID

Persistent device naming for block devices via the Universally Unique IDentifier (UUID) has been made possible courtesy of udev. It presents some advantages over the use of traditional bus-based names such as /dev/sda1 (or /dev/hda1).
Specifying a partition's by UUID guarantees that the partition will be found even if its disk assignment changes between system boots.

The UUID of a partition can be retrieved by way of the blkid command. For instance:

$ sudo blkid /dev/mapper/loop0p1
/dev/mapper/loop0p1: UUID="47c6fa91-6482-4956-912f-574ada8f5cd5" TYPE="ext3"

$ sudo blkid /dev/mapper/loop0p2
/dev/mapper/loop0p2: UUID="4a73b1bf-af94-436e-ba88-d6c9c3f71d99" TYPE="ext3"

With respect to GRUB, this translates to:

$ cat boot/grub/grub.cfg

set default="0"
set timeout="3"

menuentry "Buildroot" {
    insmod gzio
    insmod part_msdos
    insmod ext2
    set root='(hd0,msdos1)'
    search --no-floppy --fs-uuid --set=root 47c6fa91-6482-4956-912f-574ada8f5cd5
    linux /bzImage root=/dev/sda2 rw console=tty0 console=ttyS0
}

Note that in order to specify the UUID value against root= i.e:

...
 set root='(hd0,msdos1)'
   search --no-floppy --fs-uuid --set=root 47c6fa91-6482-4956-912f-574ada8f5cd5
 linux /bzImage root=UUID=4a73b1bf-af94-436e-ba88-d6c9c3f71d99 rw  
    ...

use of an initramfs (or initrd) is required5. The kernel does not understand UUID and the boot process will fail with a message similar to:

[    0.373251] sr0: scsi3-mmc drive: 4x/4x cd/rw xa/form2 tray
[    0.374700] cdrom: Uniform CD-ROM driver Revision: 3.20
[    0.375843] sr 1:0:0:0: Attached scsi generic sg1 type 5
[    0.377311] VFS: Cannot open root device "UUID=4a73b1bf-af94-436e-ba88-d6c9c3f71d99" or unknown-block(0,0): error -6
[    0.379753] Please append a correct "root=" boot option; here are the available partitions:
[    0.381611] 0800         2621440 sda  driver: sd
[    0.383407]   0801          512000 sda1 00000000-0000-0000-0000-000000000000
[    0.385464]   0802         2108416 sda2 00000000-0000-0000-0000-000000000000
[    0.387316] 0b00         1048575 sr0  driver: sr
[    0.388405] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[    0.390412] Pid: 1, comm: swapper Not tainted 3.5.1 #4
[    0.392356] Call Trace:
[    0.393497]  [<c1584314>] panic+0x78/0x166
[    0.394777]  [<c1817d4e>] mount_block_root+0x1b2/0x23a

Alternatively, PARTUUID could be specified instead of using an initramfs. In otherwords, while the kernel does not support root=UUID=, it understands root=PARTUUID=. But this will not be discussed any further here. See Section "Resources" below for pointers on using PARTUUID.

Resources

  • https://wiki.archlinux.org/index.php/Creating_Arch_Linux_disk_image
  • https://wiki.debian.org/Part-UUID#Via_UUIDs
  • http://www.linuxfromscratch.org/lfs/view/development/chapter06/grub.html
  • http://www.linuxfromscratch.org/lfs/view/development/chapter08/grub.html
  • See the root= entry in Documentation/kernel-parameters.txt and comments in init/do_mounts.c for more details on UUIDs, PARTUUIDs etc

Footnotes

1. See Transfering Buildroot compiled filesystem data into a QEMU disk image [go back]

2. The images used in this entry were x86 (32-bit) [go back]

3. To avoid complications, make sure no files are attached to the loop devices /dev/loopN before proceeding; run losetup -a to view any attached files and losetup -d /dev/loopN to dettach [go back]

4. See QEMU Serial port system console [go back]

5. See Initramfs Tutorial [go back]