Toolchains

Unter einer Toolchain versteht man eine Sammlung verschiedener Programme, die neben Compilern für verschiedene Programmiersprachen (u.a. C, C++ und Fortran), Linker und Debugger weitere Werkzeuge umfasst. Hierzu zählen die Programme der sogenannten GNU Binutils. Hierbei handelt es sich um Hilfsmittel (Utilities), die in Verbindung mit binären Dateien verwendet werden.

Cross-Toolchains

Für die Programmierung von Embedded Mikrocontrollern und Embedded Systemen werden sehr häufig sogenannte Cross-Toolchains eingesetzt. Die Ressourcen von Embedded Systemen, wie Arbeitsspeicher, Bildschirm, Tastatur, Computermäusen usw. sind häufig sehr limitiert - oder sogar gar nicht vorhanden. Auch sind die verwendeten Mikrocontroller üblicherweise weniger leistungsstark als die Mikroprozessoren, die in herkömmlichen PCs eingesetzt werden. Aus diesem Grund liegt es nahe, diese PCs für die Entwicklung der Software zu verwenden, die Software dann auf das Embedded System zu übertragen und dort auszuführen.

Da PC-Prozessoren häufig eine völlig andere Architektur besitzen als die in Embedded Systemen eingesetzten Mikrocontroller, ist es erforderlich, eine Art "Übersetzung" durchzuführen: Auf einem herkömmlichen PC mit seiner üblicherweise Intel- bzw. AMD-Architektur wird Software für eine völlig andere Architektur entwickelt. Hierfür werden sogenannte Cross-Toolchains eingesetzt.

GCC - Die GNU Compiler Collection

Früher wurde der Begriff GCC häufig mit GNU C-Compiler gleichgesetzt. In Wirklichkeit umfasst die GCC viel mehr Programme: Neben dem Compiler stellt die GCC auch Linker, Debugger und viele weitere Programme bereit, sodass heute die Bezeichnung GNU Compiler Collection verwendet wird.

Bei der GCC handelt es sich um eine Open-Source-Software, die von vielen Freiwilligen so erweitert wurde, dass heute deutlich mehr als 20 Prozessor-Architekturen von ihr unterstützt werden.

Die Linaro Cross-Toolchain

Bei der Programmierung von ARM-Architekturen (und somit auch für den sehr beliebten Raspberry Pi) hat die Linaro Cross-Toolchain große Beliebtheit erlangt. Diese sgroße Beliebtheit äußert sich unter anderem darin, dass man im Internet sehr viel Hilfestellung erhalten kann, wenn beim Einsatz dieser Cross-Toolchain Probleme oder Fragen auftreten. In meinem Buch "Embedded Linux mit Raspberry Pi & Co." habe ich daher ebenfalls diese Cross-Toolchain eingesetzt.

Beschaffung der Linaro Cross-Toolchain

Auf Seite 115 in meinem Buch "Embedded Linux auf dem Raspberry Pi & Co." hatte ich beschrieben, dass die Linaro-Toolchain von der Website http://gnutoolchains.com/raspberrypi heruntergeladen werden kann. Inzwischen ist diese URL wohl nicht mehr gültig (weshalb sie hier auch nicht als Link formatiert ist). Die Toolchain ist jetzt an einem anderen Ort, nämlich auf github.com verfügbar. Die vollständige URL lautet jetzt https://github.com/raspberrypi/tools. Zum Herunterladen der Toolchain verwenden Sie jetzt das Kommando

git clone https://github.com/raspberrypi/tools .

Der Punkt am Ende des Kommandos ist nicht als Satzzeichen zu verstehen! Er ist Bestandteil des Kommandos und bezeichnet das Verzeichnis, in das die Toolchain heruntergeladen wird (es ist somit das gleiche Verzeichnis, in dem Sie dieses Kommando ausführen). Mit diesem Kommando laden Sie aber insgesamt sogar fünf Cross-Toolchains sowie einige weitere Pakete herunter. Die einzelnen Toolchains finden Sie im Unterverzeichnis arm-bcm2708. Hierbei handelt es sich um die folgenden Toolchains:

  • arm-bcm2708hardfp-linux-gnueabi
  • arm-bcm2708-linux-gnueabi
  • arm-rpi-4.9.3-linux-gnueabihf
  • gcc-linaro-arm-linux-gnueabihf-raspbian
  • gcc-linaro-arm-linux-gnueabihf-raspbian-x64

Alle Toolchains, in deren Namen gnueabihf auftritt, unterstützen die im Mikrocontroller des Raspberry Pi integrierte Fließkommaeinheit (Floating Point Unit), während die Toolchains ohne hf die Fließkommaeinheit nur emulieren. Abhängig von der Architektur Ihres Entwicklungssystems (verwenden Sie einen 32-Bit- oder 64-Bit-Mikroprozessor?) wählen Sie eine dieser Toolchains aus. Die anderen können Sie wieder löschen und somit Speicherplatz sparen.

Installation der Toolchain

Unter Linux wird nahezu sämtliche Software irgendwo im Verzeichnis /usr/bin installiert. Dies passiert z.B. auch dann, wenn Sie auf einem Debian-basierten Linux, wie Debian, Ubuntu oder Linux Mint, die Toolchain mit

sudo apt-get install gcc-arm-linux-gnueabihf

installieren. Dies bietet einige Vorteile gegenüber einer manuellen Installation: So können Sie bei Aktualisierungen der Software auf die Kommandos sudo apt-get update und sudo apt-get upgrade neuere Versionen eines Softwarepakets automatisch installieren. Ein weiterer Vorteil ist, dass das Verzeichnis /usr/bin standardmäßig im Suchpfad für Programme eingetragen ist und die frisch installierte Software somit ohne weiteren Eingriff in Konfigurationsdateien ausgeführt werden kann.

Eine Alternative besteht darin, optionale Software in das Systemverzeichnis /opt zu installieren. Unter Linux Mint (und möglicherweise auch anderen Distributionen) existiert dieses Verzeichnis aber nicht, sodass Sie es mit

sudo mkdir /opt

erst erzeugen müssen. Unterhalb dieses Verzeichnisses empfiehlt es sich dann, ein weiteres Verzeichnis mit dem Namen toolchain zu erzeugen. Kopieren Sie die Toolchain nun aus Ihrem Downloadverzeichnis mit dem Kommando

sudo cp -r <Ausgewählte Toolchain> /opt/toolchain/

in das Zielverzeichnis /opt/toolchain.

Abschließende Arbeiten

Damit die Toolchain anschließend aus jedem Verzeichnis unterhalb Ihres home-Verzeichnisses ohne vorangestelltes /opt/toolchain/bin ausgeführt werden kann, sollten Sie die Umgebungsvariable PATH in der Datei .profile erweitern. Hängen Sie am Ende dieser Datei die beiden folgenden Zeilen an:

PATH=/opt/toolchain/bin:$PATH
export CCPREFIX=arm-linux-gnueabihf-

Sobald Sie sich erneut unter Linux an Ihrem PC anmelden, wird die Toolchain von jeder Stelle in Ihrem home-Verzeichnis gefunden.

Funktionsweise einer Toolchain

Im Buch "Embedded Linux Primer 2nd Edition" von Christopher Hallinan (Erschienen 2011 bei Prentice Hall, 4. Druck im Jahr 2014, ISBN-13: 978-0-137-01783-6) habe ich eine schöne Beschreibung gefunden, die Einsteigern bei der Behebung von Problemen mit Toolchains sehr helfen kann.

Bestandteil jeder Toolchain sollten include-und Bibliotheksdateien sein, die auf die Zielarchitektur zugeschnitten und optimiert sind. Es sind diese Dateien, die dafür sorgen, dass ein Programm auf einer bestimmten Architektur, dem sogenannten Target (Ziel) funktionieren.

Die Software-Entwicklung für Embedded Systeme erfolgt überwiegend auf "normalen" PCs. Ob es sich beim verwendeten Betriebssystem um ein Microsoft Windows, ein Linux oder ein Mac OS X handelt, spielt hierbei keine besondere Rolle: Wichtig ist, dass für die Entwicklungsplattform geeignete Werkzeuge, wie Cross-Toolchain und Entwicklungsumgebung, zur Verfügung stehen. Der Einsatz sogenannter virtueller Maschinen macht Entwickler sogar noch unabhängiger von der Entwicklungs-Architektur. Ein Computer, der für die Softwareentwicklung verwendet wird, wird in der Literatur als Host bezeichnet. Das System, für das die Entwicklung erfolgen soll, wird entsprechend als Target (Ziel) bezeichnet. Im einfachsten Fall sind Host und Target identisch: Dann reicht auch eine einfache Toolchain aus, eine Cross-Toolchain ist in diesem Fall nicht erforderlich.

Ganz anders sieht es im Regelfall bei der Softwareentwicklung für Embedded Systeme aus. Da diese sehr oft eine andere Prozessorarchitektur haben als der Host, wird eine sogenannte Cross-Toolchain benötigt. Dies dürfte Sie aber nicht mehr überraschen. Es stellt sich vielmehr die Frage: Was macht eine Cross-Toolchain anders als eine "normale" Toolchain?

Einfaches Beispiel

Weiter oben wurde bereits erwähnt, dass zu einer Cross-Toolchain include- und Bibliotheksdateien für das Zielsystem mitgeliefert werden (sollten). Diese wurden für das Target erzeugt und optimiert. Aufgrund der unterschiedlichen Architektur der Plattformen, können diese Dateien auf dem Host nicht verwendet werden.

Am einfachen Hello-World-Beispiel hat Hallinan dies erläutert:

#include <stdio.h>

int main (int argc, char **argv)

{

    printf ("Hello World\n");

    return 0;

}

Sicher ist Ihnen bekannt, dass die printf-Funktion nicht Bestandteil der Programmiersprache C ist: Die Kombination aus Compiler und Linker muss sich diese Funktion von einem anderen Ort beschaffen. Kompilieren Sie dieses Beispiel zunächst mit dem Kommando

gcc -v -o hello hello.c

Die Option -v sorgt für eine weitschweifige (verbose, daher auch die Option -v) Ausgabe des Kompilierungs-Vorgangs.

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04.3' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/4.8/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -fstack-protector -Wformat -Wformat-security -o /tmp/cczDNhRG.s
GNU C (Ubuntu 4.8.4-2ubuntu1~14.04.3) version 4.8.4 (x86_64-linux-gnu)
compiled by GNU C version 4.8.4, GMP version 5.1.3, MPFR version 3.1.2-p3, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/4.8/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.

Die durch Fettschrift hervorgehobenen Zeilen zeigen, dass das Target eine x86_64-linux-gnu-Plattform ist und dass der Compiler auf Unterverzeichnisse von /usr zugreift. Im weiteren Verlauf sehen Sie, dass der C-Preprozessor, der dem eigentlichen Kompiliervorgang vorgeschaltet ist, nach den zu dieser Plattform gehörenden include-Dateien sucht. Dies wird eingeleitet durch die Ausgaben

#include "..." search starts here und

#include <...> search starts here.

Direkt im Anschluss erkennen Sie, dass die Suche auf die Verzeichnisse /usr/lib, /usr/local und /usr/include sowie deren Unterverzeichnisse beschränkt wird. Das Ende der Suche wird durch die abschließende Zeile "End of search list." angezeigt. Der Preprozessor sucht demnach ausschließlich in den genannten Verzeichnissen nach den erforderlichen Dateien. Findet er sie dort nicht, so lässt sich das Beispiel nicht kompilieren.

Wenn Sie den gleichen Quelltext nun mit dem Kommando

arm-linux-gnueabihf-gcc -v -o hello hello.c

für den Raspberry Pi kompilieren, so ändert sich die Ausgabe folgendermaßen:

Using built-in specs.
COLLECT_GCC=arm-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/opt/toolchain/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.3/lto-wrapper
Target: arm-linux-gnueabihf
Configured with: /home/dom/projects/crosstool-ng/install/bin/.build/src/gcc-4.9.3/configure --build=x86_64-build_pc-linux-gnu --host=x86_64-build_pc-linux-gnu --target=arm-linux-gnueabihf --prefix=/home/dom/x-tools6h-new/arm-rpi-linux-gnueabihf --with-sysroot=/home/dom/x-tools6h-new/arm-rpi-linux-gnueabihf/arm-linux-gnueabihf/sysroot --enable-languages=c,c++ --with-arch=armv6 --with-fpu=vfp --with-float=hard --with-pkgversion='crosstool-NG crosstool-ng-1.22.0-88-g8460611' --disable-sjlj-exceptions --enable-__cxa_atexit --disable-libmudflap --enable-libgomp --disable-libssp --enable-libquadmath --enable-libquadmath-support --disable-libsanitizer --with-gmp=/home/dom/projects/crosstool-ng/install/bin/.build/arm-linux-gnueabihf/buildtools --with-mpfr=/home/dom/projects/crosstool-ng/install/bin/.build/arm-linux-gnueabihf/buildtools --with-mpc=/home/dom/projects/crosstool-ng/install/bin/.build/arm-linux-gnueabihf/buildtools --with-isl=/home/dom/projects/crosstool-ng/install/bin/.build/arm-linux-gnueabihf/buildtools --with-cloog=/home/dom/projects/crosstool-ng/install/bin/.build/arm-linux-gnueabihf/buildtools --enable-lto --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --enable-threads=posix --enable-target-optspace --with-linker-hash-style=gnu --disable-nls --disable-multilib --with-local-prefix=/home/dom/x-tools6h-new/arm-rpi-linux-gnueabihf/arm-linux-gnueabihf/sysroot --enable-long-long --with-arch=armv6 --with-float=hard --with-fpu=vfp
Thread model: posix
gcc version 4.9.3 (crosstool-NG crosstool-ng-1.22.0-88-g8460611)
COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-march=armv6' '-mfloat-abi=hard' '-mfpu=vfp' '-mtls-dialect=gnu'
/opt/toolchain/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.3/cc1 -quiet -v -iprefix /opt/toolchain/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/ -isysroot /opt/toolchain/bin/../arm-linux-gnueabihf/sysroot hello.c -quiet -dumpbase hello.c -march=armv6 -mfloat-abi=hard -mfpu=vfp -mtls-dialect=gnu -auxbase hello -version -o /tmp/cc6k8se9.s
GNU C (crosstool-NG crosstool-ng-1.22.0-88-g8460611) version 4.9.3 (arm-linux-gnueabihf)
compiled by GNU C version 4.8.4, GMP version 6.0.0, MPFR version 3.1.3, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring duplicate directory "/opt/toolchain/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.3/include"
ignoring nonexistent directory "/opt/toolchain/bin/../arm-linux-gnueabihf/sysroot/home/dom/x-tools6h-new/arm-rpi-linux-gnueabihf/arm-linux-gnueabihf/sysroot/include"
ignoring duplicate directory "/opt/toolchain/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.3/include-fixed"
ignoring duplicate directory "/opt/toolchain/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.3/../../../../arm-linux-gnueabihf/include"
#include "..." search starts here:
#include <...> search starts here:
/opt/toolchain/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/include
/opt/toolchain/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/include-fixed
/opt/toolchain/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/../../../../arm-linux-gnueabihf/include
/opt/toolchain/bin/../arm-linux-gnueabihf/sysroot/usr/include
End of search list.

Sie sehen, dass als Target nun arm-linux-gnueabihf angegeben wird. An der zweiten und dritten Markierung erkennen Sie darüber hinaus, dass nun auf Verzeichnisse unterhalb von /opt/toolchain zugegriffen wird.

Die Suche nach zielplattformabhängigen Dateien, die der Preprozessor durchführt, beschränkt sich also auf bestimmte Verzeichnisse und deren Inhalte. Vergleichen Sie nun einmal die Größe der Datei libgcc.a innerhalb von /usr/lib/gcc/x86_64-linux-gnu/4.8.4 mit der Größe der gleichen Datei unterhalb von /opt/toolchain/lib/arm-linux-gnueabihf/4.9.3: Sie werden feststellen, dass sich die beiden Bibliotheken unterscheiden.

Die Konfiguration der Toolchain, die u.a. den Suchbereich des Preprozessors bestimmt, und die Bereitstellung der entsprechenden include- und Bibliotheksdateien sind die Aufgabe der Entwickler der plattformspezifischen (Cross-)Toolchain. Dies alles sorgt dafür, dass auch auf Hosts, deren Architektur sich von der des Targets unterscheidet, Software für das Target entwickelt werden kann.