Build and Run on kernel using QEMU

Showcase two ways to run custom kernel using QEMU...

Just for personal note, haven’t test on others.

Kernel Config

1
make defconfig CC=clang

if want to include other module which is not enable be default in defconfig. run for e.g.

1
2
3
4
5
./scripts/config --module CONFIG_MAC80211_HWSIM
./scripts/config --module CONFIG_IWLWIFI=y
./scripts/config --module CONFIG_IWLWIFI_LEDS=y
./scripts/config --module CONFIG_IWLDVM=m
./scripts/config --module CONFIG_IWLMVM=m

build-kernel.sh

1
2
3
4
5
6
7
#!/bin/bash
set -e
 
make CC=clang -j$(nproc)
python3 scripts/clang-tools/gen_compile_commands.py
 
echo "Done — bzImage ready at arch/x86/boot/bzImage"

For subsequent small changes just run make CC=clang -j$(nproc) directly. Make only recompiles changed files, so a single .c change takes a few seconds.


Raw Kernel + Busybox Initramfs

QEMU loads the kernel and a cpio archive, the kernel unpacks it into a tmpfs, runs /init, and drops you into a busybox shell.

build-initramfs.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
set -e
 
KBUILD=$(pwd)
 
rm -rf initramfs
mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,lib/modules}
 
cp /usr/bin/busybox initramfs/bin/
cd initramfs/bin
./busybox --install .
cd "$KBUILD"
 
cat > initramfs/init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev 2>/dev/null || mdev -s
 
echo "Kernel $(uname -r) booted!"
exec /bin/sh
EOF
 
chmod +x initramfs/init
 
cd initramfs
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
cd ..
 
echo "Done — initramfs.cpio.gz ready"

boot.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash
set -e
 
KBUILD=$(pwd)
 
qemu-system-x86_64 \
    -kernel "$KBUILD/arch/x86/boot/bzImage" \
    -initrd "$KBUILD/initramfs.cpio.gz" \
    -append "console=ttyS0,115200 earlyprintk=serial,ttyS0,115200 rdinit=/init nokaslr" \
    -nographic \
    -serial mon:stdio \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 \
    -m 512M \
    -no-reboot

Rebuild

1
2
3
bash build-kernel.sh
bash build-initramfs.sh
bash boot.sh

Exit QEMU with Ctrl-A X or run shutdown.


Full Arch Linux Rootfs

When you need pacman, ssh, or any real tooling, install Arch onto a qcow2 image and boot your custom kernel directly into it.

Create the Disk

1
qemu-img create -f qcow2 rootfs.qcow2 10G

Install Arch from ISO

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
qemu-system-x86_64 \
    -cdrom archlinux-x86_64.iso \
    -boot d \
    -drive file=rootfs.qcow2,format=qcow2 \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 \
    -m 2G \
    -enable-kvm \
    -no-reboot \
    -vnc :0

Connect with vncviewer localhost:5900. Inside the installer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
fdisk /dev/sda << 'EOF'
g
n
1
 
 
w
EOF
 
mkfs.ext4 /dev/sda1
mount /dev/sda1 /mnt
 
# base only, no need linux package
pacstrap /mnt base
genfstab -U /mnt >> /mnt/etc/fstab
arch-chroot /mnt
 
passwd root
 
# Network (optional)
cat > /etc/systemd/network/20-wired.network << 'NET'
[Match]
Name=ens3
 
[Network]
DHCP=yes
NET
 
systemctl enable systemd-networkd systemd-resolved
 
exit
umount -R /mnt
poweroff

boot.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash
set -e
 
KBUILD=$(pwd)
 
qemu-system-x86_64 \
    -kernel "$KBUILD/arch/x86/boot/bzImage" \
    -drive file="$KBUILD/rootfs.qcow2",format=qcow2 \
    -append "console=ttyS0,115200 earlyprintk=serial,ttyS0,115200 root=/dev/sda1 rw nokaslr" \
    -nographic \
    -serial mon:stdio \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 \
    -m 2G \
    -enable-kvm \
    -no-reboot

SSH in with:

📝 Note

Need to install and start openssh, and set PermitRootLogin = yes in /etc/ssh/ssh_config.

1
ssh -p 2222 root@127.0.0.1

Rebuild

1
2
3
bash build-kernel.sh
bash build-initramfs.sh
bash boot.sh

Exit QEMU with Ctrl-A X or run shutdown.

Licensed under CC BY-NC-SA 4.0