Skip to content

Instantly share code, notes, and snippets.

@CharlieQiu2017
Last active July 2, 2025 21:47
Show Gist options
  • Select an option

  • Save CharlieQiu2017/8a12ab79bd7f50586a0e36fb33a55997 to your computer and use it in GitHub Desktop.

Select an option

Save CharlieQiu2017/8a12ab79bd7f50586a0e36fb33a55997 to your computer and use it in GitHub Desktop.
Running an AArch64 Linux Environment on x86 Desktop

Running an AArch64 Linux Environment on x86 Desktop

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.

Building the Linux kernel

Follow the steps described at https://gist.github.com/CharlieQiu2017/9cb214683ec0f078f0fdab6d33a68b2c to build a mainline kernel. The following features must be enabled:

  1. The ext4 filesystem, or another filesystem that supports Unix features like ownership and special files.
  2. POSIX file locking API, otherwise package managers like APT cannot work properly.
  3. TCP/IP and Packet socket support, otherwise DHCP clients won't work properly.
  4. DOS or GPT partition table support, depending on how the virtual hard disk is formatted later.
  5. Under "Security options", set "Allow /proc/pid/mem access override" to "Require active ptrace() use for access override", otherwise GDB won't work properly.
  6. 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.

Creating the hard disk image

Execute qemu-img create -f qcow2 ubuntu.img 10G to create a hard disk image.

Downloading the Ubuntu Base 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.

Downloading the Alpine Linux image

Download the image of Alpine Linux virtual edition from https://www.alpinelinux.org/downloads/.

Booting into Alpine Linux

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 64M

We 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.iso

Alpine Linux does not connect to the Internet automatically. Execute setup-interfaces and rc-service networking start to setup the Internet connection.

Preparing the disk image

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.

Installing necessary packages

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.

Preparing the init scripts

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 -l

We can now exit the chroot environment, and unmount the hard disk image.

Running the system

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-device

Network 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment