Toolchain

Vorkompilierte Cross-Compiler

Viele Distributionen bieten in ihren Paketverwaltungssystem bereits fertige Binärpakte mit allen möglichen Cross-Compilern an. Wir werden an dieser Stelle die Installation auf Ubuntu, Arch-Linux und Gentoo vorstellen:

Installation auf Ubuntu:

apt-get install gcc-arm-linux-gnueabi make ncurses-dev

Installation auf ArchLinux:

# arm-linux-gnueabi-gcc in AUR Repository
# use --force to avoid the already exists error of glibc files
# tested on 11.06.2015 - Ferdinand Saufler
yaourt -S --force arm-linux-gnueabi-gcc

Installation auf Gentoo:

crossdev -S -v -t arm-unknown-linux-gnueabi

Cross-Toolchain mit crosstool-ng

Eine weitere gute Möglichkeit um an eine Cross-Toolchain zu gelangen ist die Verwendung von crosstool-ng.

Am besten ist es eine aktuelle Version von crosstool-ng direkt aus den Github Quellen zu bauen:

git clone https://github.com/crosstool-ng/crosstool-ng.git
cd crosstool-ng
./bootstrap
./configure
make
sudo make install

ARM-Toolchain mit crosstool-ng

Bevor wir beginnen, können wir uns alle verfügbaren Konfigurationen anzeigen lassen:

ct-ng list-samples

Wir wählen nun arm-unknown-linux-gnueabi aus und bauen die Toolchain:

ct-ng arm-unknown-linux-gnueabi
ct-ng build

Nach Abschluss findet man die Toolchain unter ~/x-tools/arm-unknown-linux-gnueabi.

Cross-Toolchain selbst bauen

Die Erstellung einer eigenen ARM-Cross-Toolchain erfordert ein hohes Wissen die über die Konfiguration und das Zusammenspiel verschiedenster Softwarepakete.

Zuerst sollte man sich mit der Build-Host-Target Thematik auseinandersetzen:

  • Build Machine

    Auf diesem Rechner wird die Toolchain „gebaut“

  • Host Machine

    Auf diesem Rechner wird die Toolchain ausgeführt

  • Target Machine

    Für diesen Typ von Rechner erzeugt die Toolchain ausführbaren Code

An dieser Stelle unterscheiden wir noch 4 verschiedene Toolchain typen:

  • Native Toolchain

    Die Art von Toolchain läuft nativ auf einem System was bedeutet: Sie wurde für eine Architektur kompiliert, läuft auf dieser Architektur und erzeugt Code für diesen Architekturtyp.

  • Cross Compilation Toolchain

    Dieser Typ ist am interessantesten für Embedded Systems. Eine solche Toolchain wird beispielsweise für x86 kompiliert, läuft auf einem x86 System und erzeugt Code die Target Architektur (ARM, MIPS, PowerPC).

  • Cross Native Toolchain

    Dieser Typ könnte auf einem x86 System erzeugt worden sein, läuft aber auf der Target-Architektur und erzeugt Code für diese.

  • Canadian Build / Canadian Cross Toolchain

    Hierbei handelt es sich um den ausgefallensten Typ, eine Canadian Cross Toolchain könnte auf einem x86 System kompiliert worden sein, auf einer ARM-Architektur laufen und dort Code für MIPS erzeugen.

Grundlegende Komponenten einer Cross-Toolchain

Allgemein besteht eine Toolchain immer aus vier grundlegenden Komponenten, so wird es auch bei unserer selbsterstellten Toolchain sein:

  • Binutils

    Die GNU-Binutils. sind immer das erste Glied in der Kette. Sie beinhalten zwei wesentliche Komponenten:

    • as: den Assembler, der Assembler-Code in Maschinen-Code übersetzt

    • ld: den Linker, er linkt mehrere Objekte zu Bibliotheken oder ausführbaren Dateien zusammen.

  • C, C++, Java, Ada, Fortran, Objective-C compiler

    Die zweite Hauptkomponente einer Toolchain ist der Compiler. Ein möglicher Kandidat ist die GNU Compiler Collection, GCC. Sie unterstützt als Input Programmierspachen wie C, C++, Java Fortran oder Objective-C und als Ausgabe eine Vielzahl von Architekturen.

  • C-Bibliothek

    Eine C-Bibliothek implementiert die POSIX-API die für Programme der Userspace-Ebene benötigt wird. Sie interagiert mit dem Kernel über System-Calls und stellt darüber hinaus weitere High-Level Funktionen bereit. Wichtige Vertreter bekannter C-Bibliotheken sind unter anderem:

    • GLibc: Die am häufigigsten eingesetzte C-Bibliothek, die praktisch in jedem Desktop- und Server-System steckt. Sie bietet eine große Anzahl an Features ist dadurch aber auch etwas überladen.

    • uclibc: eine glibc Alternative die den Fokus auf wenig Platz- und Speicherverbrauch legt.

    • newlib: Die newlib ist eine speziell für Embedded Systms geschaffende Bibliothek, Sie wird von Redhat entwickelt und gewartet

    • musl: Eine relativ junge c-Bibliothek. Motto: lightweight, fast, simple, free.

  • Debugger

    Der Debugger ist nicht zwingend zum Erstellen von Applikationen notwendig ist aber dennoch meist Teil der Toolchain. In der Embedded Linux Welt ist der gebräuchlichste Vertreter der GNU Debugger GDB.

Allgemeiner Ablauf

Im allgemeinen ist die Erstellung einer Toolchain ein wiederkehrender Ablauf, der die folgenden Punkte beinhaltet:

  1. Verzeichnisstruktur anlegen

  2. Quellen laden und entpacken

  3. Spezifische Patches hinzufügen

  4. Werkzeuge auf dem Hostrechner überprüfen

  5. Für jedes ausgewählte Paket: Für den Host-/Targetrechner konfigurieren, kompilieren und installieren

Reihenfolge der Paketkonfiguration

Beim Bau einer Toolchain muss in der Regel eine bestimmte Reihenfolge eingehalten werden, da die resultierenden Komponenten auf einander aufbauen:

  1. Binutils

  2. GCC Stage 1

  3. Linux API Headers

  4. GCC Stage 2

  5. C-Bibliothek (glibc, newlib, etc)

  6. GCC Final

Quellverzeichnis

Bevor wir uns den Paketen widmen legen wir uns ein Quellverzeichnis an das wir tarballs nennen. In dieses Verzeichnis werden alle *.tar.gz Quelldateien der einzelnen Projekte geladen.

mkdir ~/tarballs
export _tarballdir="${HOME}/tarballs"

Binutils 2.25

Beginnen wir mit binutils, zuerst werden die Quellen heruntergeladen und entpackt:

cd _tarballdir
wget http://ftp.gnu.org/gnu/binutils/binutils-2.25.tar.gz
tar -xvf binutils-2.25.tar

Nun fahren wir mit der Konfiguration und Installation fort:

export _target=arm-linux-gnueabi
export _host=$(uname -m)
export _basedir=$(pwd)
export _pkgdir="${_basedir}/pkg"

mkdir binutils-build
cd binutils-build

../binutils-2.25/configure \
        --prefix=/usr \
        --with-lib-path=/usr/lib/binutils/${_target} \
        --program-prefix=${_target}- \
        --enable-shared \
        --disable-multilib \
        --with-local-prefix=/usr/lib/${_target} \
        --with-sysroot=/usr/${_target} \
        --disable-nls \
        --target=${_target} \
        --host=$_host \
        --build=$_host \
        --disable-werror

make configure-host
make tooldir=/usr
rm -rf "$pkgdir"

make prefix="$pkgdir/usr" tooldir="$pkgdir/usr" install

rm -f "$pkgdir"/usr/bin/{ar,as,ld,nm,objdump,ranlib,strip,objcopy}
rm -f "$pkgdir"/usr/lib/libiberty.a
rm -rf "$pkgdir"/usr/{share,man}

GCC 5.1.0 Stage 1

Fahren wir fort mit der Gnu Compiler Collection - Phase 1 fort.

cd _tarballdir
wget https://ftp.gnu.org/gnu/gcc/gcc-5.1.0/gcc-5.1.0.tar.gz
tar -xvf gcc-5.1.0.tar.gz

Konfiguration und Installation:

export _target=arm-linux-gnueabi
export _host=$(uname -m)
export CFLAGS="-O2 -pipe"
export CXXFLAGS="-O2 -pipe"
export _basedir=$(pwd)
export _pkgdir="${_basedir}/pkg"

mkdir gcc-stage1-build
cd gcc-stage1-build

../gcc-5.1.0/configure \
        --prefix=/usr \
        --program-prefix=${_target}- \
        --target=${_target} \
        --host=$_host \
        --build=$_host \
        --disable-shared \
        --disable-nls \
        --disable-threads \
        --enable-languages=c \
        --enable-multilib \
        --with-local-prefix=/usr/${_target} \
        --with-sysroot=/usr/${_target} \
        --with-as=/usr/bin/${_target}-as \
        --with-ld=/usr/bin/${_target}-ld \
        --enable-softfloat \
        --with-float=soft \
        --with-newlib \
        --disable-libgomp \
        --enable-interwork \
        --enable-addons

make all-gcc all-target-libgcc
make DESTDIR="$pkgdir" install-gcc install-target-libgcc

rm -rf "$pkgdir"/usr/share/man/man7/
rm -rf "$pkgdir/usr/share/info"

cp -r "$pkgdir"/usr/libexec/* "$pkgdir/usr/lib/"
rm -rf "$pkgdir/usr/libexec"

# strip it manually
strip "$pkgdir"/usr/bin/* 2>/dev/null || true
find "$pkgdir"/usr/lib -type f -exec /usr/bin/${_target}-strip \
--strip-unneeded {} \; 2>/dev/null || true

Linux 4.1.2 API Headers

fahren wir mit den Linux API Headern fort:

cd _tarballdir
wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.1.2.tar.xz
tar -xvf linux-4.1.2.tar.xz

Konfiguration und Installation:

export _basedir=$(pwd)
export _pkgdir="${_basedir}/pkg"
export _target_arch="arm"

cd linux-4.1.2

make ARCH=${_target_arch} mrproper
make ARCH=${_target_arch} headers_check
make INSTALL_HDR_PATH="${_pkgdir}/usr/${_target}/" ARCH=${_target_arch} V=0 headers_install

GCC 5.1.0 Stage 2

Fahren wir fort mit der Gnu Compiler Collection - Phase 2 fort.

cd _tarballdir
rm -rf gcc-5.1.0
tar -xvf gcc-5.1.0.tar.gz

Konfiguration und Installation:

export _target=arm-linux-gnueabi
export _host=$(uname -m)
export CFLAGS="-O2 -pipe"
export CXXFLAGS="-O2 -pipe"
export _basedir=$(pwd)
export _pkgdir="${_basedir}/pkg"

mkdir gcc-stage2-build
cd gcc-stage2-build

../gcc-5.1.0/configure \
    --prefix=/usr \
    --program-prefix=${_target}- \
    --target=${_target} \
    --host=$_host \
    --build=$_host \
    --disable-nls \
    --enable-languages=c \
    --enable-multilib \
    --with-local-prefix=/usr/${_target} \
    --with-sysroot=/usr/${_target} \
    --with-as=/usr/bin/${_target}-as \
    --with-ld=/usr/bin/${_target}-ld \
    --enable-softfloat \
    --with-float=soft \
    --disable-libgomp \
    --enable-interwork \
    --enable-addons

make all-gcc all-target-libgcc

make DESTDIR="$pkgdir" install-gcc install-target-libgcc

rm -rf "$pkgdir"/usr/share/{man/man7,info}/

cp -r "$pkgdir"/usr/libexec/* "$pkgdir"/usr/lib/
rm -rf "$pkgdir/usr/libexec"

# strip it manually
strip "$pkgdir"/usr/bin/* 2>/dev/null || true
find "$pkgdir"/usr/lib -type f -exec /usr/bin/${_target}-strip \
--strip-unneeded {} \; 2>/dev/null || true

Glibc 2.21

Fahren wir fort mit der Glibc fort.

cd _tarballdir
wget http://ftp.gnu.org/gnu/glibc/glibc-2.21.tar.gz
tar -xvf glibc-2.21.tar.gz

Konfiguration und Installation:

export _target=arm-linux-gnueabi
export _host=$(uname -m)
export CFLAGS="-U_FORTIFY_SOURCE -mlittle-endian -msoft-float -O2"
export CPPFLAGS="-U_FORTIFY_SOURCE -O2"
unset LD_LIBRARY_PATH

export BUILD_CC=gcc
export CC=${_target}-gcc
export CXX=${_target}-g++
export AR=${_target}-ar
export RANLIB=${_target}-ranlib

mkdir glibc-build
cd glibc-build

../glibc-2.21/configure \
    --prefix=/ \
    --target=${_target} \
    --host=${_target} \
    --build=$_host \
    --with-headers=/usr/${_target}/include \
    --enable-add-ons \
    --enable-obsolete-rpc \
    --enable-kernel=2.6.32 \
    --enable-bind-now \
    --disable-profile \
    --enable-stackguard-randomization \
    --enable-lock-elision \
    --enable-shared \
    --with-tls \
    --with-__thread \
    --without-cvs \
    --without-gd \
    --disable-werror

make
make "install_root=${pkgdir}/usr/${_target}" install

GCC 5.1.0 Final

Fahren wir fort mit der Gnu Compiler Collection - Final fort.

cd _tarballdir
rm -rf gcc-5.1.0
tar -xvf gcc-5.1.0.tar.gz

Konfiguration und Installation:

export _target=arm-linux-gnueabi
export _host=$(uname -m)
export CFLAGS="-O2 -pipe"
export CXXFLAGS="-O2 -pipe"
export _basedir=$(pwd)
export _pkgdir="${_basedir}/pkg"

mkdir gcc-final-build
cd gcc-final-build

../gcc-5.1.0/configure \
    --prefix=/usr \
    --program-prefix=${_target}- \
    --target=${_target} \
    --host=$_host \
    --build=$_host \
    --enable-shared \
    --disable-nls \
    --enable-threads=posix \
    --enable-languages=c,c++ \
    --enable-multilib \
    --with-sysroot=/usr/${_target} \
    --with-build-sysroot=/usr/${_target} \
    --with-as=/usr/bin/${_target}-as \
    --with-ld=/usr/bin/${_target}-ld \
    --enable-softfloat \
    --with-float=soft \
    --enable-interwork \
    --disable-libgomp \
    --enable-__cxa_atexit \
    --enable-addons

make all-gcc all-target-libgcc all-target-libstdc++-v3

make "DESTDIR=$pkgdir" install-gcc install-target-libgcc \
install-target-libstdc++-v3

rm -rf "$pkgdir"/usr/share/{man/man7,info}/

cp -r "$pkgdir"/usr/libexec/* "$pkgdir/usr/lib/"
rm -rf "$pkgdir/usr/libexec"

rm -rf "$pkgdir/usr/share/gcc-${pkgver}/python"

# strip it manually
strip "$pkgdir"/usr/bin/* 2>/dev/null || true
find "$pkgdir"/usr/lib -type f -exec /usr/bin/${_target}-strip \
--strip-unneeded {} \; 2>/dev/null || true

Vorgefertigte Toolchains

Neben Toolchain-Baukästen und der Möglichkeit sich seine eigene Toolchain zusammenzustellen gibt es auch eine Handvoll vorgefertigter Toolchains, die man nur Installieren muss.

  • DENX ELDK (Embedded Linux Development Kit)

    Toolkit Sammlung der Firma Denx Software Engeneering

  • Scratchbox

    Scratchbox stellt Toolchains für ARM und x86 Target-Architekturen zu Verfügung. Es werden uClibc und glibc unterstützt.

  • CodeSourcery

    CodeSourcery stellt eine auf Eclipse basierende IDE zu Verfügung die mit GNU Toolchain ausgeliefert wird.

  • Linaro (ARM)

    Linaro Produkte sind optimierte Toolchains für diverse ARM CPUs

Toolchain Baukästen

Mittlerweile gibt es eine beträchtliche Anzahl an Toolchain Baukästen, hier die wichtigsten Vertreter:

  • Buildroot

    Buildroot generiert Rootfile-System-Images fertig fürs Schreiben auf SD-Karte. Unter anderem stellt es auch eine Toolchain bereit um diese Pakete aus den Quellen zu kompilieren.

  • OpenADK

    OpenADK ist ein komplette Build System basierend auf dem Linux Kernel Konfigurations-System. Es unterstützt eine große Menge von Architekturen.

  • Crossdev (Gentoo)

    Crossdev ist ein Script, welches eine Toolchain generiert. Nur für Development-PCs auf denen Gentoo läuft.

  • Crosstool-NG

    Crosstool-ng ist ein gut geplegter Fork von crosstool. Leichte Konfiguration, unterstützt uClibc und glibc.

  • OSELAS.Toolchain()_

    Die OSELAS.Toolchain benutzt PTXdist, ein Userland Build-System basierend auf Kconfig. Beides Produkte von Pengutronix.