In this article, I would like to show you how to compile the newest gcc cross-compiler for Raspberry Pi. As a result we will be able to cross compile C, C++, Go and D for the Raspberry Pi.

Why do we need a cross compiler

Compiling a large project written in C++ on a raspberry may take days compared to several minutes on a x86 CPU. For example: Compiling Qt5 native on a Raspberry Pi lasts for more than 30 hours. Additionally, the Raspberry Pi may have very low RAM. Linking the final binaries will only succeed with a large and slow swapfile. In comparison, cross-compiling and linking the same code on a modern machine lasts around 15 minutes.

“host” system vs “target” system

The host system is the device on which the compiler will run. Most likely this is your workstation on which you write your code. The target system is the device on which the actual compiled executable will run. In our case the Raspberry Pi.

Creating a sysroot

First we need a proper sysroot to compile against. In this article, we are using the latest Raspian. I tested it against Raspian Stretch, but any Linux should do.
Flash a SD-Card with the image, enable ssh and connect to it via ssh.
Now let’s install some dependencies needed for the cross-compiler.

apt install linux-libc-dev

The root directory of our target Raspberry Pi '/' is the sysroot we will compile against.

In a later step, we will mount this image to our host system. Absolute softlinks on the guest system (Raspberry Pi) will point to the host system files. In example, the softlink /usr/lib/arm-linux-gnueabihf/libm.so points to /lib/arm-linux-gnueabihf/libm.so.6. We’d rather have it point to ../../../lib/arm-linux-gnueabihf/libm.so.6. This permits softlinks to maintain their validity regardless of the mount point used for the filesystem. So we want to change all absolute soft links to relative ones.

ssh pi@raspberrypi.local
apt install symlinks
symlinks -rc /usr

If the command symlinks is not available on your distribution, you may want to get and compile it yourself.

Mount the sysroot

For experiments, I like to mount the target sysroot by sshfs. This is the slowest, but most flexible one. This way you can modify the sysroot easily. For example add another library to compile against.

mkdir sysroot
sshfs -o ro pi@raspberrypi.local:/ ./sysroot
# parameter '-o ro' makes the mount read only

But you also may remove the SD Card from the Raspberry Pi and mount it with an SD-Card reader.
Or you may copy the files to a local directory.

mkdir sysroot
scp pi@raspberrypi.local:/usr pi@raspberrypi.local:/lib ./sysroot

The resulting sysroot folder will from now on be our sysroot to compile against.

Get the source

We need to get two source packages.

  • The binutils, which contain the linker (ld) and the assembler (as).
    curl -Lo binutils.tar.bz2 \
      "https://ftpmirror.gnu.org/binutils/binutils-2.32.tar.bz2"
    
  • The Gnu Compiler Collection, which contains gcc (C), g++ (C++), gccgo (Go) and gdc (D).
    curl -Lo gcc.tar.xz \
      "https://ftp.wayne.edu/gnu/gcc/gcc-9.1.0/gcc-9.1.0.tar.xz"
    

Compilation

Prerequisites

In order to compile gcc you must install some tools and libraries.

apt install \
  build-essential bison flex \
  libgmp3-dev libmpc-dev libmpfr-dev \
  texinfo

For systems not based on Debian, checkout the following site.

Variables

You must compile the binutils and gcc out-of-source! You will get weird errors if you try to compile it in-source!
First we set some environment variables which we will be using in later compilation process.

# absolute path to the resulting compiler
install_dir="$(pwd)/install"
sysroot_dir="$(realpath ./sysroot)"
arch=armv6
tune=cortex-a53
  • sysroot_dir: this variable shall point to the directory containing the sysroot. This may be the mounted SD-Card or the network mounted root. Ensure sysroot_dir contains an ABSOLUTE path!
  • arch: Setting the arch to armv6 will make the resulting compiler compatible with all raspberries. Including the first one which got the ARM1176JZF-S CPU. If you want to target Raspberry Pi 3 or later you may want to set arch to armv8-a. This will result in faster binaries because the compiler may use a more advanced instruction set.
  • tune: by setting this value, gcc will fine tune the resulting binary for the given processor. I chose cortex-a53 for the Raspberry Pi 3. But you may want to use cortex-a72 for the Raspberry Pi 4.

binutils

Let us extract the binutils source to the folder binutils_source

mkdir binutils_source
cd binutils_source
tar --strip-components 1 -xf ../binutils.tar.bz2
cd ..

Let us configure the project in a separate build directory

mkdir binutils_build
cd binutils_build
../binutils_source/configure \
  --prefix="$install_dir" \
  --target=arm-linux-gnueabihf \
  --with-arch=$arch \
  --with-tune=$tune \
  --with-fpu=vfp \
  --with-float=hard \
  --disable-multilib \
  --with-sysroot="$sysroot_dir"
  • --prefix defines where to install the resulting binaries to.
  • --target defines the target triplet only needed when creating a cross-compiler. We are using the same target-triple like the raspberry delivers:
    ssh pi@raspberrypi.local
    gcc -dumpmachine
    
  • --with-arch (optional) defines which architectures the compiler shall generate by default. You find a list of possible architectures for arm at the official documentation. You can chose the final architecture with the flag -march=armv6. For example: arm-linux-gnueabihf-g++ hello_world.cpp -march=armv8-a will result in a binary compatible with processors with the instructionset armv8-a or newer (Raspberry Pi 3 or newer).
  • --with-tune (optional) like --with-arch it defines the standard setting for the parameter -mtune.
  • --with-fpu (optional) sets the default value for what floating-point hardware is available on the target.
  • --with-float (optional) tells the compiler to use the “hardware floating point support” for floating point operations.
  • --with-sysroot gets used in the compilation process to link against system provided libraries. Like libm or libc.
  • --enable-gold (optional) builds the gold linker, which is a faster alternative to the default bfd linker
  • --enable-lto (optional) enable link time optimization support. For release builds this leads to faster code.

In the final step, we build and install the binutils.

make -j$(nproc)
make install

If you get any errors about missing files, ensure you fixed the symlinks and did an out-of-source build!
You now should have a working linker (ld), assembler (as) and additional tools in the install directory.

gcc

Let us extract the gcc source to the folder to gcc_source

mkdir gcc_source
cd gcc_source
tar --strip-components 1 -xf ../gcc.tar.xz
cd ..

Let us configure the project in a separate build directory

mkdir gcc_build
cd gcc_build
../gcc_source/configure \
  --prefix="$install_dir" \
  --target=arm-linux-gnueabihf \
  --with-arch=$arch \
  --with-tune=$tune \
  --with-fpu=vfp \
  --with-float=hard \
  --disable-multilib \
  --with-sysroot="$sysroot_dir" \
  --enable-languages=c,c++,go,d

The parameters are the same as building the binutils. Except for --enable-languages which describes the enabled languages the compiler shall support. You find a list of supported languages at the official documentation.

In the final step, we build and install gcc.

make -j$(nproc)
make install

Now we got a ready to use cross-compiler for Raspberry Pi!

Test the compilers

C++

create a C++ file called hello.cpp and append the following C++ code

#include <iostream>

int main(int, char*[]) {
  std::cout << "Hello, Rasperry Pi from C++" << std::endl;
  return 0;
}

Compile it with the default architecture defined in the configure step, when building gcc.

./install/bin/arm-linux-gnueabihf-g++ hello.cpp -o hello_cpp

Or select an architecture at build time

./install/bin/arm-linux-gnueabihf-g++ hello.cpp -o hello_cpp -march=armv8-s

Or compile for a specific CPU

./install/bin/arm-linux-gnueabihf-g++ hello.cpp -o hello_cpp -mcpu=cortex-a53

By using -mcpu=cortex-a53 the compiler derives -march, -mtune and hardware floating point support from that parameter.
For more machine flags (-m) checkout the official documentation

Upload the resulting binary and run it

scp hello_cpp pi@raspberrypi.local:
ssh pi@raspberrypi.local
./hello_cpp

If everything went well, it will print "Hello, Rasperry Pi from C++"

If you get an error like

/usr/lib/arm-linux-gnueabihf/libstdc++.so.6: version `GLIBCXX_3.4.26' not found 

then your C++ compiler is newer than the one installed on the target system. In this case, copy the libstdc++.so from your cross compiler to the target system and run the executable again.

scp ./install/arm-linux-gnueabihf/lib/libstdc++.so* pi@raspberrypi.local
ssh pi@raspberrypi.local
LD_LIBRARY_PATH=./ ./hello_cpp

You also could add a -rpath to your executable or link the libstdc++ statically.

D

Create a small D file called hello.d

import std.stdio;

void main()
{
    writeln("Hello, Rasperry Pi from D");
}

Compile it by

./install/bin/arm-linux-gnueabihf-gdc hello.d -o hello_d

Upload the resulting binary and run it

scp hello_d pi@raspberrypi.local:
ssh pi@raspberrypi.local
./hello_d

If everything went well, it will print "Hello, Rasperry Pi from D". You may want to use the same processor flags, as in the C++ test.

Summary

Finally, a summary script that will get and compile everything

#!/bin/bash

# stop script on first error
set -e

mkdir gcc_for_raspberrypi
cd gcc_for_raspberrypi

# variables that define the result
install_dir="$(pwd)/install"
sysroot_dir="$(pwd)/sysroot"
arch=armv6
tune=cortex-a53
gcc_version='9.1.0'
binutils_version='2.32'

# mount raspberry root
mkdir sysroot
sshfs -o ro pi@raspberrypi.local:/ ./sysroot

# get sources
curl -Lo binutils.tar.bz2 \
  "https://ftpmirror.gnu.org/binutils/binutils-$binutils_version.tar.bz2"
curl -Lo gcc.tar.xz \
  "https://ftp.wayne.edu/gnu/gcc/gcc-$gcc_version/gcc-$gcc_version.tar.xz"

# build binutils
mkdir binutils_source
cd binutils_source
tar --strip-components 1 -xf ../binutils.tar.bz2
mkdir ../binutils_build
cd ../binutils_build
../binutils_source/configure \
  --prefix="$install_dir" \
  --target=arm-linux-gnueabihf \
  --with-arch=$arch \
  --with-tune=$tune \
  --with-fpu=vfp \
  --with-float=hard \
  --disable-multilib \
  --with-sysroot="$sysroot_dir" \
  --enable-gold=yes \
  --enable-lto
make -j$(nproc)
make install
cd ..

# build gcc
mkdir gcc_source
cd gcc_source
tar --strip-components 1 -xf ../gcc.tar.xz
mkdir ../gcc_build
cd ../gcc_build
../gcc_source/configure \
  --prefix="$install_dir" \
  --target=arm-linux-gnueabihf \
  --with-arch=$arch \
  --with-tune=$tune \
  --with-fpu=vfp \
  --with-float=hard \
  --disable-multilib \
  --with-sysroot="$sysroot_dir" \
  --enable-languages=c,c++,go,d
make -j$(nproc)
make install
cd ..

echo "Successfully compiled 'binutils' and 'gcc'"

Links: