In this post, we will use Qemu multiarch inside Docker to compile source
codes for ARM processors.

If you want to know how to run an ARM64 Debian inside Docker skip to Qemu multiarch

ARM architecture

ARM
refers to a large group of CPU architectures designed
for embedded systems and low-cost computing. There are many variations of ARM
instruction code and unfortunately, they are not very compatible with each
other.  It's important to identify the target architecture otherwise the
built files may not be compatible with the target machine.

For this post, I am going to compile for arm64 (aka aarch64 as GCC calls
it). This architecture is used on AWS ARM instances which are available for a
cheaper price than the X86_64 ones.

Let's check the architecture of a Debian machine running on aarch64:

uname -a
Linux hostname [version] #1 SMP Debian 4.9.210-1 [date] aarch64 GNU/Linux
dpkg --print-architecture
arm64

As you can see arm64 and aarch64 are used almost interchangeably.

Building using Docker

There are two ways we can go about building a source file for a different
architecture of your current machine (e.g. your machine runs on an X86_64 CPU
and you want to compile for arm64).

One way is cross-compiling inside a machine with a different architecture
using aarch64-linux-gnu-gcc which is a GCC cross-compiler for arm64
architecture.

The other (and less complicated) way is to make the package inside a Virtual
Machine. I will use Docker with Qemu multiarch which acts similar to a VM
and allows running Docker images built for other architectures including
aarch64.

Qemu multiarch

First, we will need to enable
qemu-user-static
which allows us to run the Docker image built for ARM.

Note: This setup uses binfmt, read more:
binfmt_misc

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

This will setup the multiarch files and now we can run Docker images that are
built for different architectures seamlessly.

Debian for aarch64

After enabling mutliarch, we can simply run the Debian image built for ARM:

docker run -it arm64v8/debian:stretch
[email protected]:/#
The Docker image for
aarch64
is officially deprecated in favor of
arch64v8
which has support for broader variants of the architecture.

Now we can see the architecture is shown to be ARM:

uname -a
Linux 3b853bce5181 5.3.0-46-generic #38-Ubuntu SMP [date] aarch64 GNU/Linux
dpkg --print-architecture
arm64

That's it, we should now be able to download the desired source code and build
it for aarch64 inside that container.

For this post we will stick to a very simple program written in C.

Building a simple helloworld in C

Just for the sake of the demonstration, let's write a simple hello world C and
compile it for ARM.

#include <stdio.h>
int main() {
   printf("Hello, World!\n");
   return 0;
}

First we will need to install gcc to compile our source code:

apt update && apt install gcc file -y

Then we can compile our source code and run it:

gcc -o helloworld helloworld.c
./helloworld
Hello, World!

We can also use the file command to tell us about the binary format of the
executable we just built:

file ./helloworld
helloworld: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV) ...

It's correctly built for ARM :tada:.

One little trick we can do with GCC is that we can see the assembler code,
therefore we can see what the instruction set looks like:

gcc -o helloworld.asm -S helloworld.c

Contents of helloworld.asm will look like this:

        .arch armv8-a
        .file   "helloworld.c"
        .section        .rodata
        .align  3
.LC0:
        .string "Hello, World!"
        .text
        .align  2
        .global main
        .type   main, %function
main:
        stp     x29, x30, [sp, -16]!
        add     x29, sp, 0
        adrp    x0, .LC0
        add     x0, x0, :lo12:.LC0
        bl      puts
        mov     w0, 0
        ldp     x29, x30, [sp], 16
        ret
        .size   main, .-main
        .ident  "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
        .section        .note.GNU-stack,"",@progbits

Raspberry Pi

This method can be used to build applications that run on other ARM processors.
For example on Raspberry Pi devices, instead of aarch64 and arm64, you
would expect something similar to armv7l and armhf.

uname -a
Linux raspberrypi 4.19.97-v7l+ #1294 SMP [date] armv7l GNU/Linux
dpkg --print-architecture
armhf