QVINTVS · SCRIBET

Crosscompiler für OpenWRT-Systeme

In diesem Artikel beschreibe ich, wie man das OpenWRT-Buildroot so baut, dass lediglich der Crosscompiler erstellt wird. Ich führe durch die Voraussetzungen und baue den Logging-Daemon Metalog für OpenWRT.

OpenWRT ist eine speziell für Router und Access Points vorgesehene Linux-Distribution. Ich habe mir vor kurzem einen TP-Link TL-WR1043ND zugelegt, der zurzeit für mein Netzwerk verantwortlich ist.

Mit OPKG besitzt OpenWRT als eine der wenigen Linux-Distributionen für eingebettete Systeme einen voll funktionstüchtigen Paketmanager, und auch die Anzahl der verfügbaren Pakete ist durchaus ansehnlich. Allerdings gibt es immer mal wieder Software, die eben doch nicht in den Repositories ist, insbesondere vermisse ich einen schlanken (!) Syslog-Daemon. Ich bin in dieser Hinsicht auf Metalog gestoßen, der kaum Abhängigkeiten hat, einfach zu konfigurieren ist, und für die Erfordernisse eines kleinen Router-Systems völlig ausreicht.

Metalog ist allerdings nicht im OPKG-Repositorium vorhanden, d.h. er muss händisch kompiliert und installiert werden. Eigentlich eine einfache Sache, da gibt es nur ein kleines Problem: OpenWRT-Systeme haben keinen C-Compiler, auch nicht optional in den Repositories. Warum nicht, ist mir nicht recht begreiflich, aber die manuelle Kompilation von GCC ist so kompliziert, dass ich nur empfehlen kann, von einem Installationsversuch Abstand zu nehmen. Stattdessen nutze ich das Buildsystem von OpenWRT, ein angepasstes Buildroot, das unter anderem auch einen Kreuzcompiler bereitstellt.

Ich habe bereits früher Kontakt mit Buildroot gehabt, als ich einige Tools für die Fritz!Box kompiliert habe, daher war das immerhin nicht gänzlich fremdes Terrain für mich. Im OpenWRT-Wiki findet sich auch eine „Anleitung“ zum Bau des Buildsystems, die aber dieser Bezeichnung unwürdig ist. So toll ich OpenWRT sonst finde, das Projekt leidet unter einem akuten Dokumentationsproblem, welches sich darin manifestiert, dass alle verfügbare Dokumentation ausschließlich über ein (ständig veraltetes) Wiki bereitgestellt wird. Ich erwarte von Projekten dieser Größe eine offizielle, von den Entwicklern bereitgestellte und gepflegte Grunddokumentation, die der Bearbeitung durch die Community entzogen ist, damit die Dokumentation nicht in einem Wirrwarr von Skripten, veralteten und unwesentlichen Informationen wie diese Wiki-Seite endet. Diese Dokumentation hat zu umfassen:

Alles, was darüber hinausgeht, ist für Wikis geeignet, da informationeller Aktionismus dort dann nicht schädlich ist.

Wie gesagt, fehlt dies alles. Nirgendwo findet man etwa eine Information darüber, wie man das Buildroot so baut, das nur der Crosscompiler erstellt wird, was, wenn man nicht gerade ein komplettes OpenWRT-Image bauen will, die häufigste Anwendung sein dürfte. Und selbst für den Bau eines vollständigen OpenWRT-Images fehlen die für Nicht-Hardware-Experten relevanten Informationen, die schon bei der Frage anfangen, wie man die Prozessorarchitektur für sein Gerät bestimmt und bei den erforderlichen Treibern noch lange nicht zu Ende sind.

Vorbereitungen

Genug gerantet. Zunächst ist das Buildroot für die jeweils installierte OpenWRT-Version herunterzuladen:

$ git clone git://git.openwrt.org/14.07/openwrt.git

“14.04” ist durch die korrekte Versionsnummer zu ersetzen, eine Liste der unterstützen Versionen gibt es im OpenWRT-Wiki (wo auch sonst — so Gott will, ist sie vielleicht auch aktuell).

Danach sind die für die Kompilation erforderlichen Pakete zu installieren. Da es hier nicht darum geht, eine komplette OpenWRT-Distribution zu erstellen, sondern bloß einen funktionstüchtigen Kreuz-Compiler, können alle für Pakete („Feeds“) erforderlichen Abhängigkeiten ignoriert werden. Daher ist die Abhängigkeitenliste für Buildroot erfreulich kurz:

Die Prüfung, ob alles installiert ist, erfolgt wie folgt:

$ make prereq

Bau des Buildsystems

Danach muss die Buildroot-Konfiguration angepasst werden:

$ make menuconfig

Dort ist insbesondere die Zielarchitektur („Target System“) auszuwählen, die man durch Raten aus den Informationen der OpenWRT-Wiki-Seite des betroffenen Gerätes entnimmt (*hust*); im Falle meines TL-WR1043ND handelt es sich dabei um „AR7xxx/AR9xxx“. Danach muss man noch den Punkt „Build the OpenWrt based Toolchain“ angeben, den Rest kann man unverändert lassen. Konfiguration durch Auswahl von „Save“ nach .config abspeichern.

Sodann führt man den folgenden, geheimen (weil undokumentierten) Befehl aus (Vorsicht, dauert sehr lange!):

$ make toolchain/compile

Ich habe ihn durch Raten aus den Informationen aus einer Wiki-Seite entnommen. Dieser Befehl baut nun in einem langwierigen Prozess die gesamte Crosscompilation-Toolchain einschließlich eines entsprechenden GCC. Diese steht anschließend im Verzeichnis /staging_dir/toolchain-<platform>-<gcc_ver>-<libc_ver> erstellt wird. Einer anderen Wiki-Seite zufolge muss man nun die eigene PATH-Variable entsprechend anpassen, was auch sinnvoll erscheint, wenn man Zugriff auf den Kreuzcompiler haben möchte:

$ export PATH="$HOME/openwrt/staging_dir/host/bin:$HOME/openwrt/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin:$PATH"

Was dieses Statement in meiner ~/.bashrc zu suchen haben soll, weiß ich nicht, schließlich habe ich auch noch andere Compiler installiert (und nutze überhaupt keine Bash, sondern die Z-Shell). Daher genügt das Ausführen des obigen Befehls völlig.

$ mips-openwrt-linux-uclibc-gcc --version
mips-openwrt-linux-uclibc-gcc (OpenWrt/Linaro GCC 4.8-2014.04 r46178)
4.8.3
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Test des Crosscompilers

Damit ist der Crosscompiler einsatzfähig. Ob er funktioniert, teste ich bei dieser Art von Compilern grundsätzlich immer, bevor ich auch nur mit der Kompilation großer Projekte anfange — schließlich könnte man sich aus naheliegenden Gründen in der Zielarchitektur vertan haben. Außerdem werden durch den Test andere Fehler ausgeschlossen, die nicht mit dem gewünschten Projekt zutun haben (etwa, wenn Header der Standardbibliothek nicht gefunden werden).

Zum Test hat es sich bewährt, das einfache Hallo-Welt-Programm zu kompilieren, auf das Zielsystem zu kopieren, und es dort auszuführen. Wenn es dort korrekt ausgeführt wird, steht der eigentlichen Arbeit nichts mehr im Wege.

hello.c:

#include <stdio.h>

int main(int argc, char* argv[])
{
  printf("Hello world!\n");
  return 0;
}

Kompilation (man beachte die statische Verlinkung auch der C-Standardbibliothek, um jede Abhängigkeit zu eliminieren):

$ mips-openwrt-linux-uclibc-gcc -Wall -static -static-libgcc \
  hello.c -o hello
mips-openwrt-linux-uclibc-gcc: warning: environment variable 'STAGING_DIR' not defined
mips-openwrt-linux-uclibc-gcc: warning: environment variable 'STAGING_DIR' not defined
mips-openwrt-linux-uclibc-gcc: warning: environment variable 'STAGING_DIR' not defined
/home/quintus/openwrt/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/lib/gcc/mips-openwrt-linux-uclibc/4.8.3/../../../../mips-openwrt-linux-uclibc/bin/ld: skipping incompatible /usr/lib/libc.a when searching for -lc

Die Warnungen können wohl ignoriert werden, jedenfalls habe ich keine negativen Auswirkungen feststellen können.

hello kann jetzt auf das OpenWRT-Gerät kopiert werden.

$ scp hello user@openwrt:/tmp

Dort kann das Programm dann testweise ausgeführt werden:

$ /tmp/hello
Hello world!

Metalog

Mit dem nunmehr funktionalen Crosscompiler kann man darangehen, ein „echtes“ Projekt wie Metalog zu kompilieren. Metalog weist genau eine Abhängigkeit auf, die Perl-Compatible Regular Expressions (PCRE). Diese müssen folglich zuerst kompiliert werden. Seit der Fertigstellung von Metalog ist 2015 eine stark revidierte Version der PCRE veröffentlicht worden, PCRE2, mit welcher Metalog aller Voraussicht nach nicht kompatibel ist und es wohl auf absehbare Zeit auch nicht sein wird. Es sollte daher die alte PCRE-Lib gewählt werden, die zZt. bei Version 8.37 liegt.

PCRE

$ wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.37.tar.bz2
$ tar -xvf pcre-8.37.tar.bz2
$ cd pcre-8.37
$ ./configure --prefix=$HOME/openwrt-cross --disable-shared \
  --enable-static --host=mips-openwrt-linux-uclibc \
  --enable-unicode-properties --enable-pcre16 --enable-pcre32
$ make
$ make install

Zu beachten sind bei der obigen Kommandofolge vor allen Dingen die Optionen zur Abschaltung dynamischer Bibliotheken (die nicht benötigt werden), die Einschaltung statischer Bibliotheken (diese dafür umso mehr), und die Angabe von --host zur Durchführung einer Kreuzkompilation. Der dort übergebene Wert bestimmt sich nach dem „Host Triplet“ des Ziel-C-Compilers, das durch ein einfaches ls auf dem Verzeichnis staging_dir/toolchain-*/bin herausgefunden werden kann — das Triplet ist alles vor den eigentlichen Programnamen. Die übrigen Optionen dienen zur Konfiguration von PCRE in einer halbwegs sinnvollen Art und Weise.

Metalog

Metalog benötigt zum Bau noch pkg-config, welches es zum Auffinden von PCRE benutzt. Dazu bietet es sich an, einen kleinen Wrapper um das pkg-config des Hostsystems zu bauen, welcher auf das oben gewählte Zielverzeichnis verweist. Dazu die Datei ~/openwrt-cross/bin/pkg-config mit folgendem Inhalt anlegen:

#!/bin/bash

export PKG_CONFIG_LIBDIR=$HOME/openwrt-cross/lib/pkgconfig
/usr/bin/pkg-config "$@"

Und dafür sorgen, dass dieses pkg-config das erste ist, was gefunden wird:

$ export PATH=$HOME/openwrt-cross/bin:$PATH

Kompilation durchführen:

$ wget 'http://sourceforge.net/projects/metalog/files/metalog-3.tar.xz/download' \
  -O metalog-3.tar.xz
$ tar -xvf metalog-3.tar.xz
$ cd metalog-3
$ ./configure --host=mips-openwrt-linux-uclibc --sysconfdir=/etc \
  --with-unicode
$ make

--host funktioniert wie schon vorhin, --sysconfdir dient dazu, dass Metalog seine Konfiguration unterhalb von /etc sucht und nicht unterhalb von /usr/local. --with-unicode erlaubt Unicode-Unterstützung in Metalog.

Der Installationsschritt kann entfallen. Vielmehr werden jetzt die benötigten Dateien auf das OpenWRT-System kopiert:

$ scp src/metalog user@openwrt:/tmp
$ scp metalog.conf user@openwrt:/tmp

Von da aus können sie in die korrekten Verzeichnisse verschoben werden:

$ mv /tmp/metalog /usr/sbin
$ mv /tmp/metalog.conf /etc

Die Konfiguration sei dem verständigen Leser überlassen. Die Metalog-Quellen enthalten entsprechende Manpages.

Valete.