FYI: In this tutorial, I’m using an Ubuntu 20.04.1 LTS VM as a host system.
Since processors don’t understand high-level source code directly, we need to convert our C code into machine-code using a compiler. However the GCC compiler you have on your system compiles your code for the architecture of the system it runs on, in this case x86_64. In order to compile our code for the Arm architecture, we need to use a cross-compiler.
Let’s start with Arm64 and install the following packages:
azeria@ubuntu:~$ sudo apt update -y && sudo apt upgrade -y
azeria@ubuntu:~$ sudo apt install qemu-user qemu-user-static gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu binutils-aarch64-linux-gnu-dbg build-essential
Once installed, create a file containing a simple C program for testing, e.g.
#include <stdio.h>
int main(void) {
return printf("Hello, I'm executing ARM64 instructions!\n");
}
To compile the code as a static executable, we can use aarch64-linux-gnu-gcc with the -static flag.
azeria@ubuntu:~$ aarch64-linux-gnu-gcc -static -o hello64 hello.c
But what happens if we run this Arm executable on a different architecture? Executing it on an x86_64 architecture would normally result in an error telling us that the binary file cannot be executed due to an error in the executable format.
azeria@ubuntu:~$ ./hello64
bash: ./hello64: cannot execute binary file: Exec format error
We can’t run our Arm binary on an x84_64 architecture because instructions are encoded differently on these two architectures.
Lucky for us, we can bypass this restriction with the QEMU user emulator which allows us to run binaries for other architectures on our host system. Let’s try it out.
Below you can see that our host is a x86_64 GNU/Linux system. The binary we have previously compiled is ARM aarch64.
azeria@ubuntu:~$ uname -a
Linux ubuntu 5.4.0-58-generic #64-Ubuntu SMP Mon Dec 29 08:16:25 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
azeria@ubuntu:~$ file hello64
hello64: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=66307a9ec0ecfdcb05002f8ceecd310cc6f6792e, for GNU/Linux 3.7.0, not stripped
Let’s execute it!
azeria@ubuntu:~$ ./hello64
Hello, I'm executing ARM64 instructions!
Voilà, our statically linked aarch64 binary is running on our x86_64 host thanks to qemu-user-static. But can we execute a dynamically linked Arm executable? Yes, we can. This time, the package that makes this possible is qemu-user.
First, compile the C code without the -static flag. In order to run it, we need to use qemu-aarch64 and supply the aarch64 libraries via the -L flag.
azeria@ubuntu:~$ aarch64-linux-gnu-gcc -o hello64dyn hello64.c
azeria@ubuntu:~$ qemu-aarch64 -L /usr/aarch64-linux-gnu ./hello64dyn
Hello, I'm executing ARM64 instructions!
Nice. Works like a charm. Moving on to Arm32!