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.
Absolute softlinks
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 containsgcc
(C
),g++
(C++
),gccgo
(Go
) andgdc
(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 thesysroot
. This may be the mounted SD-Card or the network mounted root. Ensuresysroot_dir
contains an ABSOLUTE path!arch
: Setting thearch
toarmv6
will make the resulting compiler compatible with all raspberries. Including the first one which got theARM1176JZF-S
CPU. If you want to target Raspberry Pi 3 or later you may want to setarch
toarmv8-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 chosecortex-a53
for the Raspberry Pi 3. But you may want to usecortex-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 instructionsetarmv8-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. Likelibm
orlibc
.--enable-gold
(optional) builds the gold linker, which is a faster alternative to the defaultbfd
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'"