When developing applications for AArch64 single-board computers, it is often more convenient to test the application in an emulated environment first, before going through the steps to deploy the application onto the board.
In this document, I describe how to build such an (emulated) AArch64 Linux environment on an x86 Linux desktop.
Follow the steps described at https://gist.github.com/CharlieQiu2017/9cb214683ec0f078f0fdab6d33a68b2c to build a mainline kernel. The following features must be enabled:
- The ext4 filesystem, or another filesystem that supports Unix features like ownership and special files.
- POSIX file locking API, otherwise package managers like APT cannot work properly.
- TCP/IP and Packet socket support, otherwise DHCP clients won't work properly.
- DOS or GPT partition table support, depending on how the virtual hard disk is formatted later.
- Under "Security options", set "Allow /proc/pid/mem access override" to "Require active ptrace() use for access override", otherwise GDB won't work properly.
- eventfd() support, otherwise libuv will segfault, and CMake depends on libuv.
After building, we only need the Image file (under arch/arm64/boot). The vmlinuz file is not needed.
Execute qemu-img create -f qcow2 ubuntu.img 10G to create a hard disk image.
Download the Ubuntu Base image from https://cdimage.ubuntu.com/ubuntu-base/releases/. We will use this image as the userspace part. However, we cannot directly extract it into the virtual hard disk, as the hard disk is in qcow2 format. Instead, we will boot into Alpine Linux and install Ubuntu Base from there.
To facilitate file transfer between the host and the virtual machine,
it is useful to have an HTTP server running under our working directory.
We can start one with python3 -m http.server.
Download the image of Alpine Linux virtual edition from https://www.alpinelinux.org/downloads/.
Install the package qemu-efi-aarch64. Check that the file /usr/share/qemu-efi-aarch64/QEMU_EFI.fd exists.
Execute the following commands:
truncate -s 64M efi.img
dd if=/usr/share/qemu-efi-aarch64/QEMU_EFI.fd of=efi.img conv=notrunc
qemu-img create -f qcow2 varstore.img 64MWe can now boot the Alpine Linux image with the following command:
qemu-system-aarch64 \
-M virt -m 4G \
-cpu cortex-a72 -smp 2 \
-drive if=pflash,format=raw,file=efi.img,readonly=on \
-drive if=pflash,format=qcow2,file=varstore.img \
-device virtio-blk-device,drive=drive0,id=virtblk0,num-queues=4 \
-drive file=ubuntu.img,if=none,id=drive0,format=qcow2 \
-device virtio-net-device,id=virtnet0,netdev=net0 \
-netdev user,id=net0 \
-drive if=virtio,format=raw,file=alpine-virt-3.21.3-aarch64.isoAlpine Linux does not connect to the Internet automatically.
Execute setup-interfaces and rc-service networking start to setup the Internet connection.
Execute setup-apkrepos -1 to update the package repository.
Then execute apk add cfdisk e2fsprogs nano.
The virtual hard disk probably shows up as /dev/vdb.
Use cfdisk to format the disk.
Only 1 partition is necessary. The "bootable" flag is not needed.
Execute mkfs.ext4 /dev/vdb1 to format the new partition.
Execute mount -t ext4 /dev/vdb1 /mnt to mount the partition.
Now transfer the Ubuntu Base image into the Alpine Linux virtual machine, and extract it into /mnt.
Copy /etc/resolv.conf to /mnt/etc/resolv.conf.
Then execute chroot /mnt to enter the Ubuntu Base environment.
Under /etc/apt/apt.conf.d, create a file named nosuggest with the following content:
APT::Install-Suggests false;
APT::Install-Recommends false;
This will prevent installing unncessary packages.
Now execute apt-get update && apt-get upgrade to update the system.
Finally, install the packages nano, iproute2, and udhcpc.
Build the tty_init program from https://github.com/CharlieQiu2017/tty_init.
Copy the tty_init executable to /mnt.
The correct TTY to use under QEMU is /dev/ttyAMA0.
Create an executable script /mnt/init with the following content:
#!/bin/sh
ip link set lo up
ip link set eth0 up
udhcpc
export TERM=linux
export HOME=/root
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# cols must be exactly equal to the actual number of columns available
# rows can be smaller than the actual number of rows available, but cannot be larger
# DO NOT resize the terminal window, otherwise funny things happen
stty cols 158 rows 39
exec /bin/bash -lWe can now exit the chroot environment, and unmount the hard disk image.
Execute the following command to boot into Ubuntu Base:
#!/bin/sh
qemu-system-aarch64 \
-M virt -m 4G \
-cpu cortex-a72 -smp 1 \
-nographic \
-kernel Image \
-append "root=/dev/vda1 rw init=/tty_init console=ttyAMA0 earlycon=pl011,0x9000000" \
-device virtio-blk-device,drive=drive0,id=virtblk0,num-queues=4 \
-drive file=ubuntu.img,if=none,id=drive0,format=qcow2 \
-device virtio-net-device,id=virtnet0,netdev=net0 \
-netdev user,id=net0,net=10.43.0.0/24 \
-device virtio-rng-deviceNetwork connection should be setup automatically, so one can immediately start installing new packages, and transferring files.
When shutting down the virtual machine, be sure to execute sync,
otherwise data written in the last few seconds might be lost.