Open Source im professionellen Einsatz

© Bruce Amos, Fotolia

Workshop: Crosscompiling für Embedded-Systeme

GNU-Tools mal kreuzweise

Beim Cross-Kompilieren für ein Embedded-System übersetzen Sie Ihren Quellcode für eine andere CPU als die im PC des Entwicklers eingebaute. Der folgende Workshop erklärt zwei Wege, wie das mit dem GCC als Crosscompiler zu bewerkstelligen ist.

Wenn Sie für ein Embedded-System, zum Beispiel einen Router, Telefon, Steuergerät und so weiter, Software entwickeln wollen, brauchen Sie eine zur Hardware passende Kette aus Werkzeugen, also Crosscompiler, Assembler, Linker und Debugger. Die gibt es fertig als Open Source oder proprietär, es kann aber auch zweckmäßig sein, selbst einen GCC [1] zu konfigurieren. Entweder weil es für die Zielplattform keinen vorkompilierten Compiler gibt, weil dieser zu alt ist und neue CPU-Funktionen nicht unterstützt oder um selbst am GCC Veränderungen vorzunehmen.

Obwohl die GNU Compiler Collection und die Binutils (Assembler, Linker und so weiter, [2]) Autoconf verwenden, ist ein Cross-GCC nicht ganz einfach einzurichten, denn er will genau auf das Zielbetriebssystem und die CPU abgestimmt sein: Als Erstes extrahieren Sie die System-Header des Betriebssystems und der C-Bibliothek. Das ist notwendig, weil diese Header die Datenstrukturen und Systemaufrufe des Betriebssystems für die jeweilige CPU definieren und bereits der Compiler sie benötigt.

Entweder zu Fuß oder mit dem Crosstool-Projekt

Danach kompilieren Sie mit den extrahierten Headerdateien die Binutils und den GCC. Mit dem neuen Crosscompiler übersetzen Sie wiederum die C-Bibliothek. Der Kasten "Cross-GCC selbst übersetzen" liefert Ihnen eine Schritt-für-Schritt-Anleitung. Wegen des erheblichen Konfigurationsaufwands rief Dan Kegel, der zurzeit bei Google arbeitet, das Crosstool-Projekt [3] ins Leben. Crosstool automatisiert die Konfigurationsschritte und integriert zudem diverse Patches sowie Tests, um die korrekte Funktion zu überprüfen.

Cross-GCC selbst
übersetzen

Das folgende Beispiel benutzt nicht das fertige Kit Crosstool [3], sondern Sie übersetzten den GCC selbst. Die Anleitung in Listing 1 benutzt »$DESTDIR« als Pfad, in dem Sie das Embedded-System erstellen wollen, und »$TOOLSDIR« als Aufenthaltsort für den Crosscompiler und die anderen Werkzeuge. »$TOOLSDIR« könnte »/home/user/arm-tools« sein und »$DESTDIR« »/home/user/arm-linux« - ganz nach Belieben. Den Pfad »$TOOLSDIR/bin« müssen Sie der »$PATH«-Variablen hinzufügen, damit die Shell den Crosscompiler und Assembler findet.

Das Beispiel verwendet ARM als Zielsystem (Abbildung 1). Da der Compiler Details des Zielsystems benötigt, extrahieren Sie mit den Zeilen 1 bis 4 zunächst einige Header aus dem Linux-Kernel. Die Zeilen 5 bis 10 erledigen das Gleiche für die Glibc. Jetzt können Sie die gewohnten Configure-Skripte der Binutils und des GCC-Pakets verwenden, müssen jedoch darauf achten, die richtige Zielplattform anzugeben. Für die Binutils gehen Sie wie in den Zeilen 12 bis 15 vor, für den GCC gelten die Kommandos aus den Zeilen 16 bis 20.

Der ARM-Compiler ist geschafft

Nun haben Sie den Crosscompiler »arm-linux-gnu-gcc« fertig gebaut und können andere Pakete übersetzen. Als erstes Paket müssen Sie in den Zeilen 22 bis 25 unbedingt die C-Bibliothek kompilieren, damit andere Programme dagegen linken können. Ab jetzt lässt sich eigener C- und Assembler-Code für das Zielsystem übersetzen und auch linken. Um zu überprüfen, ob das wirklich klappt, füttern Sie den »arm-linux-gnu-gcc« mit einem gängigen Testprogramm:

arm-linux-gnu-gcc -o hello hello.c
file hello

Auf das File-Kommando von eben müsste eine Ausgabe der Art

hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs)

erfolgen, die ein ARM-Binary meldet. Geschafft: Sie haben Ihr eigenes Embedded-Kit!

Sind Sie auf einem der beiden Wege zu einem Crosscompiler gelangt, können Sie Ihre Software für die Zielplattform übersetzen. Zuerst setzen Sie die Umgebungsvariable »$CC« auf den Namen des Crosscompiler-Binary und beim Configure die Zielarchitektur hier für ARM:

export CC=arm-linux-gnu-gcc
./configure --host=arm-linux-gnu

Die verschiedenen Arten von Zuweisungen überschreiben eventuell innerhalb der Makefiles die Umgebungsvariable »$CC« wieder. In diesen Fällen reicht es gewöhnlich, »$CC« mittels »make«-Argument zu setzen:

make CC=arm-linux-gnu-gcc

Zuweisungen in Makefiles sind nun sicher, es sei denn, eine explizite »override«-Anweisung hagelt dazwischen.

Wenn Sie neben dem Compiler auch Linker (LD), Assembler (AS) oder andere Programme in der Toolchain verwenden wollen, geben Sie diese mit

export LD=arm-linux-gnu-ld
export AS=arm-linux-gnu-as
make CC=arm-linux-gnu-gcc LD=arm-linux-gnu-ld AS=arm-linux-gnu-as

bekannt. Das Installations-Zielverzeichnis erfährt Make mit:

make DESTDIR=/home/user/arm-linux

Da ohne »DESTDIR« schnell Dateien des eigenen Systems dran glauben, sollten Sie in der Sektion »install:« des Makefile prüfen, ob es die Angabe unterstützt oder ob die Variable dort nicht anders heißt, zum Beispiel »INSTALL_ROOT«. Achtung: Verwechseln Sie »DESTDIR« nicht mit dem »--prefix«-Argument von Configure! Obwohl der Aufruf

./configure --prefix=/home/user/arm-linux

die Dateien, die aus dem »make install«-Lauf resultieren, in dasselbe Zielverzeichnis installiert, ist die Variante mit

./configure ; make installU
 DESTDIR=/home/user/arm-linux

zu bevorzugen. Diese setzt nämlich die Pfade in Ihrem Programm. Fehlt sie, kann es Daten wie Bilder, Plugins, Einstellungen und so weiter nicht laden, da sie auf dem Zielsystem nicht relativ zu »/home/user/arm-linux« liegen.

Ein funktionierender Compiler ist ein guter Anfang, allerdings gilt es darüber hinaus noch, die eigentlichen Programme zu übersetzen. Viele Projekte entwickeln Software allerdings ohne Rücksicht auf Crosscompiling, und so lauern in den Buildsystemen allerlei Hindernisse.

Listing 1: Schritte zum
GCC

01 tar xvfj linux-$ver.tar.bz2
02 cd linux-$ver
03 mkdir -p $DESTDIR/usr/include/asm
04 make ARCH=arm INSTALL_HDR_PATH=$DESTDIR/usr headers_install
05 tar xvfj glibc-$ver.tar.bz2
06 cd glibc-$ver
07 mkdir objdir; cd objdir
08 CC=gcc ../configure --host=arm-linux-gnu --prefix=/usr --with-header=$DESTDIR/usr/include
09 make -k cross-compiling=yes DESTDIR=$DESTDIR install-headers
10 touch $DESTDIR/usr/include/gnu/stubs.h
11 
12 tar xvfj binutils-$ver.tar.bz2
13 cd binutils-$ver
14 ./configure --target=arm-linux-gnu --prefix=$TOOLSDIR
15 make; make install
16 tar xvfj gcc-$ver.tar.bz2
17 cd gcc-$ver
18 mkdir objdir; cd objdir
19 ../configure --target=arm-linux-gnu --disable-cpp --disable-shared --disable-multilib
   --enable-languages="c" --prefix=$TOOLSDIR
   --with-headers=$DESTDIR/usr/include
20 make all; make install
21 
22 cd glibc-$ver/objdir
23 ../configure --host=arm-linux-gnu --prefix=/usr --with-header=$DESTDIR/usr/include
24 make
25 make DESTDIR=$DESTDIR install

Abbildung 1: Der Workshop verwendet als Beispiel für die Zielplattform ARM. Im Bild der ARM-Singleboard-Computer Taskit Portux920T mit dem Controllerchip Atmel AT91RM9200, zwei seriellen Schnittstellen, 100 MBit/s Ethernet und JTAG-Unterstützung.

Abbildung 1: Der Workshop verwendet als Beispiel für die Zielplattform ARM. Im Bild der ARM-Singleboard-Computer Taskit Portux920T mit dem Controllerchip Atmel AT91RM9200, zwei seriellen Schnittstellen, 100 MBit/s Ethernet und JTAG-Unterstützung.

Alle Achtung

Ein Problem ist der Mix von Host- und Target-Compiler. Einige Makefiles enthalten »gcc«- oder »cc«-Aufrufe für einzelne oder alle Dateien. Auch vom Entwickler bevorzugte maschinenabhängige Optimierungen stellen sich oft als problematisch heraus. Das führt entweder zu Programmen, die nicht auf dem Zielsystem ausführbar sind, oder - falls nur einige Dateien betroffen sind - zu Inkompatibilitätsfehlern beim Linken. Ein möglicher Ausweg ist das Ändern von Regeln sowie das Setzen der »$(CC)«-Variablen.

Einige Autoconf-Skripte versuchen übersetzte Testprogramme auszuführen, um Charakteristika des Systems zu erkennen. Cross-kompilierte Programme lassen sich aber nicht auf dem Entwicklungsrechner starten. Hier hilft es, das »configure«-Skript anzupassen oder zur Not fixe Werte einzutragen, entweder direkt in das Configure-Skript oder in die Cachedatei »config.cache«.

Einige Programme generieren wiederum Quellcode, zum Beispiel systemspezifische Werte, aber auch vorbereitete Tabellen für mathematische Berechnungen. Falls das Programm hierfür nicht auf Skripte, sondern auf das Übersetzen von Hilfsprogrammen angewiesen ist, kommt es dann zu Problemen, wenn der Systemcompiler durch andere Wortbreiten, Endianness oder Genauigkeiten inkompatible Werte generiert. Als letzte Möglichkeit bleibt auch hier, das cross-kompilierte Programm auf dem Zielsystem auszuführen, um eine korrekte Ausgabe zu erhalten.

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook