Manually booting the Linux kernel inside QEMU
Shivering in this cold winter one would certainly stumble upon the question that the Linux Kernel, which operates most of your operating system, is just a huge C program.
Shouldn’t it, then, be possible to compile and run it as you do to all your C programs ?
Here, I’d be using the famous qemu emulator to run a pre compiled linux kernel. So, let’s begin.
Theory⌗
We all hate this part but it deserves its position.
Here’s how a typical linux boot works after the kernel is loaded.
The kernel initializes and looks for an initramfs
. This is a temporary root filesystem. Now, the kernel doesn’t know if your real root is residing on a USB stick or a hard disk or RAID or god-knows-what. It cannot support everything, so it’s the job of initramfs to store the loadable kernel modules and assist the kernel. In short, it complements the kernel.
After the Kernel has initialized keyboard, mouse etc, now it’s the time to mount your hard disk (or say, the root filesystem) and then present you the fancy login screen where you can carry on with your journey.
Get your tools⌗
- QEMU
- The Kernel
- fakeroot
The Kernel⌗
You can find a pre-compiled kernel at /boot/vmlinuz-linux
Initramfs⌗
We’d be creating this one. The tool at our disposal is mkinitcpio
Step zero would be to enter into the fakeroot
environment. It makes you appear as root but you’re not. This is required to make a proper initramfs.
$ fakeroot
then create a config file named mkinitcpio.conf
with the following contents:
MODULES=(ext4)
HOOKS=(base systemd modconf sd-vconsole filesystems block keyboard)
and the last step is to generate the initramfs
$ mkinitcpio -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g initramfs.img
Now, you have the initramfs in a file named initramfs.img
.
You can now safely exit the fakeroot environent by typing exit
.
Boot the kernel using QEMU⌗
After you have your hands on a compiled kernel and initramfs, you’d be eager to boot it in a live environment. We can simulate the same using QEMU.
Looking at man qemu
, here are some interesting options
-kernel bzImage
Use bzImage as kernel image. The kernel can be either a Linux kernel or in multiboot forβ
mat.
-append cmdline
Use cmdline as kernel command line
-initrd file
Use file as initial ram disk.
Cool, we’ve a kernel image, an initramfs, so why wait ? Let’s boot right in.
$ qemu-system-x86_64 -kernel vmlinuz-linux -initrd initramfs.img
To your surprise, it yields:
The Kernel panicked. It typically happens when you try to boot without a RAM.
So, let’s try adding a 1G of RAM.
$ qemu-system-x86_64 -kernel vmlinuz-linux -initrd initramfs.img -m 1G
Another roadblock!
Now, the kernel and the initramfs were done but you’d need a hard disk where everything should carry on i.e. the Operating System.
Add a hard disk⌗
First, we’d be creating a sparse file of 2GB
$ dd if=/dev/zero of=kernel-hd bs=1M count=2048
2048+0 records in
2048+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 9.87468 s, 217 MB/s
Now, a hard disk in itself isn’t enough. You’d need a filesystem (typically called formatting the hard disk). Let’s create one.
$ mkfs.ext4 kernel-hd
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done
Creating filesystem with 524288 4k blocks and 131072 inodes
Filesystem UUID: 3654d72f-3f6d-4d90-8848-2900d7c271d0
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
We’ve successfully created a filesystem in the virtual hard disk. Yay!
Boot the kernel with the hard disk⌗
Now, we’ve everything set up. The only task at hand is to plug in the newly created hard disk to a QEMU VM and instruct the kernel to treat that hard disk as the new_root.
$ qemu-system-x86_64 -kernel vmlinuz-linux -initrd initramfs.img -m 1G -hda kernel-hd -append "root=/dev/sda"
Wait, what !?
Hmm, something is not quite right.
It turns out the kernel cannot continue to boot without some basic files (the OS) in the root filesystem (the hard disk)
Populating the Hard Disk⌗
There are plently of ways you can find or generate the required files (say, a kernel-less Operating System). Here we’ll use the packages provided by Arch.
Feel free to experiment about this.
Install pacstrap
by
$ sudo pacman -S arch-install-scripts
Now mount the kernel-hd
at /mnt
$ sudo mount kernel-hd /mnt
And populate the hard disk with Arch’s base packages.
$ sudo pacstrap /mnt base
This will start a download of around 115MB. Note, that after this step, you should update your host Arch system soon. (pacman -Syu
)
The OS is ready but you’re gonna need some login credentials to log in.
Set a root password in your little OS that you’ve just created.
$ sudo passwd --root /mnt root
And finally unmount kernel-hd
$ sudo umount kernel-hd
Boot our handcrafted system⌗
Rerun the following command
$ qemu-system-x86_64 -kernel vmlinuz-linux -initrd initramfs.img -m 1G -hda kernel-hd -append "root=/dev/sda"
and finally you’ll be greeted with
Login with the credentials you set before and enjoy!
WTH ?⌗
Root account is locked⌗
If something goes wrong in the initramfs phase and you see root account is locked
, you can unlock the root account by creating a mkinitcpio.conf
file as follows:
# Required
MODULES=(ext4)
HOOKS=(base systemd modconf sd-vconsole filesystems block keyboard)
# Unlocking the root account
echo 'root:x:0:0:root:/root:/bin/sh' > /tmp/passwd
echo 'root:$6$drlHj0v2/B7liRyL$YH0ZHsG4d05mS6moBPkdzI5dlt9RjrPbWTCwDk7r5ZCWIGHFEx9A/atj/hPImYPq7qGzi8zGHOKqRgfHSfq7b/:18611:0:99999:7:::' > /tmp/shadow
add_file /tmp/passwd /etc/passwd 0644
add_file /tmp/shadow /etc/shadow 0600
and then regenerating the initramfs.img
as shown before.
Remember to enter the fakeroot
environment before generating the initramfs.img
.
This will enable you to login with the password 12345678
Why not use the default /boot/initramfs-linux.img⌗
We have been using the same kernel that your host is using, located at /boot/vmlinuz-linux
. So, it might sound better to use the host initramfs as well.
Theoretically, you should be able to just copy and use that but for reasons I’ve faced and bashed my head over multiple times, I’d recommend generating your own initramfs.
Waiting for /dev/sda… or /dev/sda not detected⌗
It might be a qemu issue. You can try emulating SATA as mentioned here.
Basically, you’d need to drop the -hda kernel-hd
flag and use the one mentioned in the answer.
Thanks to sheep
from freenode IRC for the suggestion.
Why Arch ?⌗
Because it’s the best. Feel free to write your own OS files. Maybe another time.
Still don’t want Arch⌗
Sure, you should head into Linux From Scratch. Also, you may unlock the root account and use journalctl
to look into what the kernel is error-ing for.
Don’t want an OS at all⌗
First, unlock the root account and don’t create a Hard Disk at all. You’d be able to log into the initramfs after boot. Explore, enjoy.
Afterwords⌗
Now you know that Linux itself is not an operating system. It’s just a kernel. A part of the operating system. The entire operating system (or distro) consists of the kernel, accompanying initramfs, the root file system and the community.