How to make an ARM virtual machine (armhf/aarch64)
Update (2022-03-19): I wrote about a new way to create an ARM virtual machine that’s simpler and handles kernel updates properly. I highly suggest you follow those instructions instead, unless you are building a chroot.
I noticed that very few people seem to know how to create a full ARM virtual machine, so I decided to create a quick guide.
This tutorial will use aarch64 and Debian as examples, but the same methodology should work for 32-bit ARM and other distributions. The instructions can also be adapted to create a simple chroot.
Step 1: Image Creation
First, we will need to create a disk image. We’ll be using a raw image, which is the most convenient option given you can loop mount. If you are just building a chroot, you can skip this step.
To create an image, we first have to create a file. Just run the following command (change 8G to however big you want the image to be):
$ fallocate -l 8G arm64-vm.img
Then, you should create an ext4 filesystem on the image:
$ sudo mkfs.ext4 arm64-vm.img
mke2fs 1.44.5 (15-Dec-2018)
Discarding device blocks: done
Creating filesystem with 2097152 4k blocks and 524288 inodes
Filesystem UUID: b8455983-417b-4c1c-b170-fd4a4e63c060
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
Record the filesystem UUID. This will be important later.
Then we mount this image somewhere, for example, /mnt/test
:
$ mkdir -p /mnt/test
$ sudo mount -o loop arm64-vm.img /mnt/test
Your image is ready to be populated with a chroot! If you are just doing a
chroot, instead of /mnt/test
, you should replace all instances of /mnt/test
with the desired path for the chroot.
Step 2: Setup qemu
for executables
We will need to install the qemu-arm-static
binary. On Debian, simply run:
$ sudo apt install binfmt-support qemu-user-static
Then, we will need to make sure /usr/bin/qemu-arm-static
is available inside
the chroot:
$ sudo mkdir -p /mnt/test/usr/bin
$ sudo cp /usr/bin/qemu-arm-static /mnt/test/usr/bin
Step 3: Bootstrap the system
We will use debootstrap
to populate the chroot. On Debian and similar systems,
this can be installed with:
$ sudo apt install debootstrap
Once installed, simply run:
$ sudo debootstrap --arch=arm64 buster /mnt/test/ http://deb.debian.org/debian/
Replace buster
with the version of Debian (or Ubuntu) you want to use.
Replace http://deb.debian.org/debian/
if you have a better mirror (or want
to use Ubuntu).
Replace arm64
with armhf
if you are building a 32-bit ARM system.
If you want to use another Linux distribution, please follow their guide to create
the chroot
.
Step 4: Enter the chroot
To enter the chroot
, we first need to make critical directories available through
bind mounting. To do this, run:
$ for dir in /dev /proc /sys; do sudo mount --bind $dir /mnt/test$dir; done
Then, we can just spawn bash inside the chroot:
$ sudo chroot /mnt/test /bin/bash
If you are doing a simple chroot, this is the end.
Step 5: Prepare chroot for execution as virtual machine
The chroot was built without a kernel, so you will not be able to boot it as a VM with QEMU. So we simply install the kernel:
# apt install linux-image-arm64
If you are building a 32-bit ARM VM, you should use linux-image-armmp
instead.
Setting root password
You should also configure a root password so you could login:
# passwd
New password:
Retype new password:
passwd: password updated successfully
Configuring fstab
You should configure /etc/fstab
by changing it to:
UUID=b8455983-417b-4c1c-b170-fd4a4e63c060 / ext4 errors=remount-ro 0 1
Replace b8455983-417b-4c1c-b170-fd4a4e63c060
with the UUID from step 1.
Configuring networking
You should change /etc/network/interfaces.d/eth0
to:
auto eth0
iface eth0 inet dhcp
Finishing up
Once you are done, exit the chroot.
# exit
Step 6: Prepare for boot
We need to copy the kernel and initrd image out of the VM into some location on
the host. For example, /var/lib/libvirt/images
:
$ sudo cp /mnt/test/boot/{initrd.img-,vmlinuz-}* /var/lib/libvirt/images
We will then need to unmount the image:
$ for dir in /dev /proc /sys /; do sudo umount /mnt/test$dir; done
Creating the VM with libvirt
If you are using libvirt, the following command should create the VM:
$ sudo virt-install \
--name arm64-vm \
--arch aarch64 \
--machine virt \
--os-type Linux \
--os-variant debian10 \
--ram 2048 \
--vcpus 2 \
--import \
--disk /var/lib/libvirt/images/arm64-vm.img,bus=virtio \
--graphics none \
--network user,model=virtio \
--features acpi=off \
--boot kernel=/var/lib/libvirt/images/vmlinuz-4.19.0-13-arm64,initrd=/var/lib/libvirt/images/initrd.img-4.19.0-13-arm64,kernel_args='console=ttyAMA0,115200 root=/dev/vda net.ifnames=0 biosdevname=0'
If you are doing 32-bit ARM, use this instead:
$ sudo virt-install \
--name arm-vm \
--arch armv7l \
--machine virt \
--os-type Linux \
--os-variant debian10 \
--ram 2048 \
--vcpus 2 \
--import \
--disk /var/lib/libvirt/images/arm-vm.img,bus=virtio \
--graphics none \
--network user,model=virtio \
--boot kernel=/var/lib/libvirt/images/vmlinuz-4.19.0-13-arm64,initrd=/var/lib/libvirt/images/initrd.img-4.19.0-13-arm64,kernel_args='console=ttyAMA0,115200 root=/dev/vda net.ifnames=0 biosdevname=0'
Obviously, replace the --disk
path with the path to your image, and similarly
for kernel
and initrd
. Feel free to change --name
.
If all goes well, the virtual machine should boot and you should see the serial
console. Login as root
and run shutdown now
.
Your VM should now be created.
Creating the VM with plain QEMU
To run a 64-bit ARM with QEMU directly, do:
$ qemu-system-aarch64 -m 2048M -drive file=arm64-vm.img,if=virtio -M virt -cpu cortex-a57 \
-kernel vmlinuz-4.19.0-13-arm64 -initrd initrd.img-4.19.0-13-arm64 \
-append 'console=ttyAMA0,115200 root=/dev/vda net.ifnames=0 biosdevname=0' -no-reboot
If you are doing 32-bit ARM, use this instead:
$ qemu-system-arm -m 2048M -drive file=arm-vm.img,if=virtio -M virt -cpu cortex-a15 \
-kernel vmlinuz-4.19.0-13-armmp -initrd initrd.img-4.19.0-13-armmp \
-append 'console=ttyAMA0,115200 root=/dev/vda net.ifnames=0 biosdevname=0' -no-reboot
Obviously, replace the -drive
path with the path to your image, and similarly
for kernel
and initrd
.
Conclusion
Congratulations! Now you have your very own ARM virtual machine (or chroot).