Building a Vexpress-A9 Emulation Environment with QEMU, Custom Kernel, and BusyBox Rootfs

On a machine running Ubuntu 22.04 (with gcc 11.4.0 and host kernel 6.5.0), install QEMU:

sudo apt-get install qemu qemu-system

Download Linux 6.6.16 from https://cdn.kernel.org and extract the archive. Use a separate build directory too keep the source clean:

cd linux-6.6.16
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-
mkdir ../build
make O=../build vexpress_defconfig
make O=../build menuconfig   # exit without changes
make -j$(nproc) O=../build bzImage dtbs

The kernel image and device tree appear at:

  • ../build/arch/arm/boot/zImage
  • ../build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb

Quick validatino (expect a panic due to missing rootfs):

qemu-system-arm \
    -M vexpress-a9 -smp 4 -m 256M \
    -kernel ../build/arch/arm/boot/zImage \
    -dtb ../build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
    -append "console=ttyAMA0" \
    -nographic

Press Ctrl+A then X to stop QEMU.

Root filesystem with BusyBox

Get BusyBox 1.36.1 from https://busybox.net/downloads and build:

tar xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
make defconfig
make menuconfig   # enable Settings -> Build static binary
mkdir ~/my_rootfs
make install CONFIG_PREFIX=~/my_rootfs

Supplement the directory structure:

cd ~/my_rootfs
mkdir -p etc dev mnt sys tmp proc root lib
cp -a /usr/arm-linux-gnueabi/lib/*.so* ./lib/

Create device nodes:

cd dev
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
for i in 1 2 3 4; do sudo mknod -m 666 tty$i c 4 $i; done

Write configuration files:

etc/init.d/rcS:

#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export LD_LIBRARY_PATH=/lib:/usr/lib
/bin/mount -n -t ramfs ramfs /var
/bin/mount -n -t ramfs ramfs /tmp
/bin/mount -n -t sysfs none /sys
/bin/mount -n -t ramfs none /dev
/bin/mkdir /var/tmp /var/modules /var/run /var/log
/bin/mkdir -p /dev/pts /dev/shm
/sbin/mdev -s
/bin/mount -a
echo "ARM emulation ready."

Mark it executable: chmod +x etc/init.d/rcS.

etc/fstab:

proc    /proc      proc    defaults     0 0
none    /dev/pts   devpts  mode=0622    0 0
mdev    /dev       ramfs   defaults     0 0
sysfs   /sys       sysfs   defaults     0 0
tmpfs   /dev/shm   tmpfs   defaults     0 0
tmpfs   /dev       tmpfs   defaults     0 0
tmpfs   /mnt       tmpfs   defaults     0 0
var     /dev       tmpfs   defaults     0 0
ramfs   /dev       ramfs   defaults     0 0

etc/inittab:

::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

SD card image and final launch

Create a raw disk image and populate it:

cd ~
qemu-img create -f raw disk.img 256M
mkfs.ext4 disk.img
mkdir tmp_mnt
sudo mount -o loop disk.img tmp_mnt
sudo cp -r ~/my_rootfs/* tmp_mnt/
sudo umount tmp_mnt
rmdir tmp_mnt

Start the full system:

qemu-system-arm \
    -M vexpress-a9 -m 256M \
    -kernel ../build/arch/arm/boot/zImage \
    -dtb ../build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
    -sd disk.img \
    -nographic \
    -append "root=/dev/mmcblk0 rw console=ttyAMA0"

When the prompt appears, standard Linux commands can be executed.

Tags: qemu vexpress-a9 linux-kernel busybox ARM

Posted on Wed, 04 Nov 2026 16:52:07 +0000 by polson