on
Making a Raspbian Cross Compilation SDK
After setting up your Raspberry Pi with Raspbian jessie lite, this time we will now create the necessary cross compilation Software Development Kit (SDK). The SDK will contain the toolchain, headers and libraries to compile binaries compatible with the Raspberry Pi on a different (more powerful) machine (e.g. your laptop or desktop computer).
While the Raspberry Pi 2 and Raspberry Pi 3 are armv7
and aarch64
the
old Raspberry Pi 1 and Raspberry Zero are armv6
; to simplify matters
Raspbian is compiled for armv6hf+vfpv2
to have a single distribution
supporting all. The target’s triple is then arm-none-linux-gnueabihf
.
We will therefore create a SDK targeting armv6
and the programs built
with it should be compatible with all Raspberry Pis. The downside is
that programs built with this toolchain will not be able to take
advantage of advanced features only available in the Raspberry Pi 2
and 3.
It is instructive to build the SDK by hand to better understand the
how everything fits together, and be able to make educated adjustments
to the SDK’s toolchain, headers and libraries. For our SDK we will
focus on clang
from the llvm project as our underlying compiler for
our toolchain as we will later use GHC’s LLVM cross compilation
backend, and want to keep things simple and consistent. You can find a
script to build the SDK in our zw3rk/scripts GitHub repository.
The SDK
What is a SDK and why do we need one? When compiling on a more powerful
(or better supported) build machine to a different one, we need a
toolchain that contains a compiler that can target the machine
that will finally host the resulting binary. The toolchain will also
contain a linker, to be able to link object files that are foreign to
the build machine as well as an archiver, and a tool to extract
symbols from those object files. In addition to the toolchain, we also
need access to the headers and libraries on the host, against we
want to link. Let us start out by creating a folder raspbian-sdk
for
the SDK, with two subfolders prebuilt
(which will hold the toolchain)
and sysroot
(which will hold the headers and libraries).
mkdir -p raspbian-sdk/{prebuilt,sysroot}
The Compiler
As mentioned above, we’ll use clang
from the llvm
project as our compiler. You can obtain a copy from their
release download website. At
the time of writing LLVM 4.0.0 is the latest release. clang
is a
multi-target compiler, which means that the same compiler can produce
object code for different architectures. This can be controlled via the
--target
flag.
LLVM can be unpacked and installed into raspbian-sdk/prebuilt
as
follows:
curl -L -O http://releases.llvm.org/4.0.0/clang+llvm-4.0.0-x86_64-apple-darwin.tar.xz
xz -d clang+llvm-4.0.0-x86_64-apple-darwin.tar.xz
tar xf clang+llvm-4.0.0-x86_64-apple-darwin.tar \
-C "/path/to/raspbian-sdk/prebuilt" --strip-components=1
Note: This will install the clang+llvm
for macOS, the commands for
linux would be similar.
The Linker and Other Utilities
With the compiler in hand, we can now produce object files =.o=, but
when combining multiple object files, these need to be linked together.
To illustrate this, assume two object files a.o
and b.o
. Object file
b.o
refers to a function f
in a.o
. It does so via an external
symbol reference (by name). The linker will, when combining a.o
and
b.o
, look for the symbol with the specified name in a.o
and resolve
the reference.
GNU Binutils provide a suite of tools containing not only one linker (ld.bfd), but a second more modern one (ld.gold) as well. In addition to the linkers, binutils also contain an archiver (ar), a symbol listing tool (nm), and a few other tools. The latest version can be obtained from their ftp server, the latest version as of this writing is binutils-2.28.
As this is a source distribution, you will need to build binutils yourself.
./configure --prefix="/path/to/raspbian-sdk/prebuilt" \
--target=arm-linux-gnueabihf \
--enable-gold=yes \
--enable-ld=yes \
--enable-targets=arm-linux-gnueabihf \
--enable-multilib \
--enable-interwork \
--disable-werror \
--quiet
make && make install
This should configure, compile and install the suite of binutils into
/path/to/raspbian-sdk/prebuilt
. While binutils can be configured to be
multi-target, I advise against doing so, as it requires passing the
target to tools when there is ambiguity. This becomes more complicated
in cases where the tools are called indirectly.
The Headers and Libraries
We finally need to get hold of a copy of the headers and libraries from
the Raspberry Pi. We need those so that the compiler has access to the
headers and libraries available on the Raspberry Pi. /This also means,
that if you install new libraries (with their headers), you will need to
refresh your SDK; make sure to install the/ -dev
packages if needed.
To copy the headers and libraries from the Raspberry Pi to the build
machine, we will use rsync
. It is not available by default in raspbian
jessie lite and needs to be installed:
ssh pi@raspberrypi 'sudo aptitude install -y rsync'
With that in place, we can start to copy the data to our build system:
Note: as Rob van den Bogaard
kindly
pointed
out/, the/ rsync
that comes with macOS Sierra 10.12 does not
support multiple */remote sources/*/. Using the/ rsync
installed
via brew install rsync
however does.
mkdir sysroot
rsync -rzLR --safe-links \
pi@raspberrypi:/usr/lib/arm-linux-gnueabihf \
pi@raspberrypi:/usr/lib/gcc/arm-linux-gnueabihf \
pi@raspberrypi:/usr/include \
pi@raspberrypi:/lib/arm-linux-gnueabihf \
sysroot/
Running rsync
at a later time again, has the benefit of copying over
only the new data, to keep your local copy in sync with the one on the
Raspberry Pi. You should run the rsync
command in the sysroot
folder again, if you installed additional libraries on your Raspberry
Pi, against which you want to link.
Using the SDK
Assuming we now have the following structure:
├── prebuilt
│ ├── arm-linux-gnueabihf
│ ├── bin
│ ├── include
│ ├── lib
│ ├── libexec
│ └── share
└── sysroot
├── lib
└── usr
(After unpacking clang+llvm
into prebuilt
and setting
prebuilt
as the target for =binutils=/)/
We can compile a simple hello.c
with the following content
#include <stdio.h>
int
main(int argc, char ** argv) {
printf("Hello World!\n");
return 0;
}
via our cross compilation SDK for arm-linux-gnueabihf
like so:
export COMPILER_PATH=sysroot/usr/lib/gcc/arm-linux-gnueabihf/4.9
./prebuilt/bin/clang --target=arm-linux-gnueabihf \
--sysroot=./sysroot \
-isysroot ./sysroot \
-L${COMPILER_PATH} \
--gcc-toolchain=./prebuilt/bin \
-o hello hello.c
Finally copy hello
over to the Raspberry Pi
scp hello pi@raspberrypi:
and execute it
ssh pi@raspberrypi ./hello
Conclusion
We have seen that building and using a cross compilation SDK is not as
simple as clang --target arm-linux-gnueabihf -o hello hello.c
, but is
still something manageable. The tricky part is getting all the compiler
flags right.
Luckily we can hide all of them with a simple shell wrapper. Putting
#!/bin/bash
BASE=$(dirname $0)
SYSROOT="${BASE}/../../sysroot"
TARGET=arm-linux-gnueabihf
COMPILER_PATH="${SYSROOT}/usr/lib/gcc/${TARGET}/4.9"
exec env COMPILER_PATH="${COMPILER_PATH}" \
"${BASE}/clang" --target=${TARGET} \
--sysroot="${SYSROOT}" \
-isysroot "${SYSROOT}" \
-L"${COMPILER_PATH}" \
--gcc-toolchain="${BASE}" \
"$@"
into prebuilt/bin/arm-linux-gnueabihf-clang
and making it executable
with
chmod +x prebuilt/bin/arm-linux-gnueabihf-clang
allows us to simply say
prebuilt/bin/arm-linux-gnueabihf-clang -o hello hello.c
to compile hello.c
on our build machine, into an executable that can
run on the host.