Using Docker to host ARM toolchain to cross-compile C code

I'm starting up a project that will see me doing custom software development for an ARM single-board-computer running Linux. The recommendation isn't to do compiles ON the board, but instead to cross compile from a Linux workstation (Debian). But, I use a Mac laptop, as do most software engineers these days. While I could run VirtualBox to set up a Debian cross-compiling environment, Docker is much lighter weight. While Docker was originally targeted for deploying server applications, it is useful for packaging anything. In this case there's a ready-made set of Docker containers for cross-compilation including for ARM CPU's.

My likely ARM hardware vendor conveniently gave excellent instructions for setting up a Debian workstation for cross-compiling: http://wiki.embeddedarm.com/wiki/TS-7680#Debian_Configuration Those instructions rely on cross-compilation tools created by http://emdebian.org/ and while the Emdebian project is essentially dead, they seem to still be maintaining the cross-compilation tools. Even that's not 100% true (it seems) because the Emdebian website refers you over to https://wiki.debian.org/CrossToolchains for instructions for the latest Debian (Jessie). But then those instructions tell you to add the Emdebian repository to your APT sources. Fortunately it's not entirely important for this tutorial to understand the status of the Emdebian project. For whatever reason we are to still use the Emdebian repository to access the compilation toolchain.

Simple Emdebian cross-compiling Docker container

My first stab at a Docker image was to transliterate the instructions on EmbeddedARM.com into a simple Dockerfile. But as we'll see in a minute there's a simpler method.

FROM debian:jessie

RUN apt-get update && apt-get install -y \
    apt-utils \
    curl \
    build-essential \
    automake \
    autogen \
    bash \
    build-essential \
    bc \
    bzip2 \
    ca-certificates \
    curl \
    file \
    git \
    gzip \
    make \
    ncurses-dev \
    pkg-config \
    libtool \
    python \
    rsync \
    sed \
    bison \
    flex \
    tar \
    vim \
    wget \
    runit \
    xz-utils

RUN echo "deb http://emdebian.org/tools/debian jessie main" > /etc/apt/sources.list.d/emdebian.list
RUN curl http://emdebian.org/tools/debian/emdebian-toolchain-archive.key | apt-key add -
RUN dpkg --add-architecture armel
RUN apt-get update && apt-get install -y crossbuild-essential-armel

# The cross-compiling emulator
RUN apt-get update && apt-get install -y \
    qemu-user \
    qemu-user-static

Create an empty directory and save that as Dockerfile. This Dockerfile brings in a lot of useful command-line tools that developers normally use.

With this you can run.

docker build --tag emdebian-for-technologic .
docker run -it --volume `pwd`:/workdir emdebian-for-technologic bash

The first command builds the Dockerfile, giving the image name emdebian-for-technologic. The second gives you a running shell inside the container. Use ls to look in /usr/bin and you'll see commands with names like arm-linux-gnueabi-gcc that are the cross-compilation toolchain.

Then you can create a file named hello.c containing:

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

Then compile and run it as so:

root@c8007516bb30:/workdir# arm-linux-gnueabi-gcc hello.c -o hello
root@c8007516bb30:/workdir# file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=007ea7c5826104168af839653c3075b474a4d084, not stripped
root@c8007516bb30:/workdir# ./hello
Hello World

We've proved that GCC produced for us an ARM binary for ARMv5. The last command works because we installed qemu-user-static and qemu-user. The qemu tools let you run binaries for other architectures, like ARM, on your regular x86 workstation.

Dockcross - simplifying cross compilation for a wide range of platforms

While that's useful and straight-forward, I promised there was a simpler method. The Dockcross project - https://github.com/dockcross/dockcross - makes cross-compilation using a Docker container simple. They provide Docker images for a long list of CPU architectures, and a simple way to use their Docker images from a regular command-line.

The first step is to install Docker on your laptop - presumably you're running either Mac OS X and need to install Docker for Mac, or you're using Windows and need to install Docker for Windows. Both are the modern currently recommended way to run Docker on a non-Linux workstation.

The next step is to set up Dockcross for the given cross-compilation target. Unfortunately it's not clear from the EmbeddedARM website whether to use ARMv5 or what. By reading the Dockerfiles in the Dockcross project I see that the "armel" architecture corresponds to ARMv5. Therefore I ran this:

docker run --rm dockcross/linux-armv5 >dockcross-linux-armv5
chmod +x dockcross-linux-armv5
mv dockcross-linux-armv5 ~/bin

I can then compile code like so:

dockcross-linux-armv5 bash -c '$CC hello.c -o hello'

And run it as so:

$ file hello
hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped
$ file hello.c
hello.c: ASCII c program text
$ dockcross-linux-armv5 ./hello
Hello World

Since the "hello" binary is compiled for ARM, it shouldn't work. But the Dockcross container includes qemu-user-static allowing the binary to run.

You can also get a command-line shell in the container with this command:

dockcross-linux-armv5 bash