Mercredi 28 novembre 2018 (Modifié le Mercredi 28 novembre 2018)

BuildRoot

buildroot
https://buildroot.org/

Largement inspiré de l’ article Création d’un système complet avec Buildroot de Christophe Blaess Ingénierie et formations sur les systèmes libres

  • Préparer une chaîne de cross-compilation
  • Construire un système minimal
  • Générer un système personnalisé

Répertoire de base : /mnt/dplus ou ~/media/dplus
Installer cpio : yay -S cpio

Environnement de travail

Créer une arborescence de travail qui contiendra tous les fichiers personnalisés, les répertoires de construction, la chaîne de compilation, etc.

mkdir br-tree
cd br-tree

Au sein de cet environnement, nous allons essayer de respecter l’organisation des fichiers proposée par le projet Buildroot : une arborescence board/ contenant un sous-répertoire pour chaque carte que nous supporterons (ici le Raspberry Pi 3 uniquement). Tous nos fichiers personnalisés se trouveront dans cette sous-arborescence.

mkdir -p board/rpi-3/

Nous téléchargeons les sources de la dernière version de Buildroot, les décompressons et entrons dans ce répertoire :

wget https://buildroot.org/downloads/buildroot-2018.08.tar.bz2
tar xf buildroot-2018.08.tar.bz2
cd buildroot-2018.08/

Toolchain

J’ai l’habitude de commencer mes projets embarqués par la construction d’une toolchain de cross-compilation. Il s’agit d’obtenir un compilateur – et tous les outils associés – fonctionnant sur la machine de développement (un PC par exemple) et produisant du code pour la plate-forme cible (ici le Raspberry Pi 3).

Il est tout à fait possible de se procurer une toolchain pré-compilée, mais je trouve qu’il est dommage de se priver de cette étape de construction, d’autant que cela nous permet de maîtriser exactement les versions de la bibliothèque C, du noyau, des outils, etc. que nous souhaitons.

Si j’isole cette étape de la production du système complet, c’est qu’elle prend un temps significatif (une vingtaine de minutes sur un petit processeur i7 avec une connexion Internet fibre). La toolchain est donc compilée et installée une fois pour toutes, et ne sera plus modifiée même si nous réitérons autant de génération du système que nous le désirons.

J’ai l’habitude de placer la toolchain dans le répertoire board/\target\/cross.

Pour produire la toolchain, nous demandons une configuration de Buildroot par défaut pour la cible choisie, et l’élaguons pour ne laisser que la production du compilateur et de ses outils. Les configurations par défaut disponibles sont visibles dans le sous-répertoire configs/. Nous choisissons celle pour Raspberry Pi 3.

make raspberrypi3_defconfig
#
# configuration written to /mnt/dplus/br-tree/buildroot-2018.08/.config
#
make menuconfig

Voici la liste des modifications apportées :

  • Menu Target options : pas de changement, mais on peut en profiter pour observer et vérifier le support du processeur cible.
  • Menu Build options : * Download dir : nous extrayons de l’arborescence de Buildroot ce répertoire dans lequel il stocke les fichiers téléchargés. Ainsi les compilations successives ne nécessiterons pas de nouveaux téléchargements. Nouvelle valeur : $(TOPDIR)/../dl * Host dir : l’emplacement où se trouvera la toolchain compilée. Comme indiqué plus haut, j’ai pour habitude de la placer dans le répertoire board//cross de notre arborescence de travail. Nouveau chemin : **$(TOPDIR)/../board/rpi-3/cross**
  • Menu Toolchain : * C library : c’est un choix qui dépend beaucoup du code métier. La bibliothèque C est un point-clé du système ; c’est elle qui permet d’entrer dans le noyau pour bénéficier de ses services (appels-système). Pour être le plus générique possible, nous choisissons la Gnu C library, un peu plus volumineuse que les autres, mais plus riche également. Nouvelle valeur : glibc * Custom kernel headers series : la compilation de la bibliothèque C nécessite de charger les fichiers headers du noyau afin de connaître ses points d’entrée (voir cet article pour plus de détails). Par défaut Buildroot utilise la même version de noyau que celui qui sera construit pour le système (Linux 4.9.x). Toutefois nous n’allons pas compiler tout de suite de noyau aussi se rabat-il sur la version la plus récente qu’il connaît (Linux 4.15.x) pour produire la libC. Si nous laissons en l’état, une fois que nous aurons compilé tout le système et booterons le Raspberry Pi 3, la GlibC compilée pour un 4.15 refusera de s’initialiser sur un 4.9. et affichera le message “Kernel too old” avant qu’un Kernel Panic se produise puisque le processus init ne peut démarrer. Nouvelle valeur : Linux 4.9.x kernel headers. Le menu Custom kernel headers series disparaît alors. * Build cross gdb for the host : en prévision des éventuelles séances de débogage distant du code applicatif, j’ajoute dans la toolchain un débogueur qui fonctionne sur PC mais peut analyser du code au format de la cible. Nouvelle valeur : [*] Build cross gdb for the host.
  • Menu System configuration : * Init system : cette option contient au préalable BusyBox mais nous la désactivons pour pouvoir éliminer ce package. Nouvelle valeur : None * Custom scripts to run before creating filesystem images : nous n’allons pas produire de système complet, il ne faut donc pas lancer le script qui finalise l’arborescence avant de créer les images des systèmes de fichiers. Nouvelle valeur : (). * Custom scripts to run after creating filesystem images : pour la même raison nous ne lançons pas le script qui assemble les images des systèmes de fichiers pour créer une image complète de la carte microSD pour Raspberry Pi. Nouvelle valeur : ().
  • Menu Kernel : * Linux Kernel : nous ne voulons, dans un premier temps, produire que la toolchain et rien d’autre. Nous désactivons cette option. Nouvelle valeur : [ ]
  • Menu Target packages : * BusyBox : c’est le seul package initialement présent. Nous le désactivons. Nouvelle valeur : [ ]
  • Menu Filesystem images : * ext2/3/4 root filesystem : inutile, nous ne voulons pas de filesystem pour le moment. Nouvelle valeur : [ ]
  • Menu Legacy config options : Je jette toujours un œil à ce menu lorsque j’utilise une nouvelle version de Buildroot avec un fichier de configuration pré-existant car il nous indique les fonctionnalités incompatibles (modifiées, disparues, etc.) entre deux versions. Comme nous avons généré un fichier de configuration “from scratch“, ce n’est pas nécessaire ici.

Sauvegardons la configuration et lançons la compilation. Une nouvelle cible de compilation “toolchain” est disponible depuis quelques versions de Buildroot. Je suppose que certaines de nos modifications dans les menus “System configuration“, “Kernel” et “Target Packages” sont redondantes avec cette nouvelle cible mais je préfère les appliquer quand même.

cp .config ../board/rpi-3/buildroot-2018.08-toolchain.config
make toolchain   # patienter de 5 à 20 minutes (suivant processeur)

Au terme de la compilation

-e 's#@@STAGING_SUBDIR@@#arm-buildroot-linux-gnueabihf/sysroot#' 
-e 's#@@TARGET_CFLAGS@@#-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Os#' 
-e 's#@@TARGET_CXXFLAGS@@#-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Os#' 
-e 's#@@TARGET_FCFLAGS@@#-Os#' -e 's#@@TARGET_LDFLAGS@@##' 
-e 's#@@TARGET_CC@@#bin/arm-buildroot-linux-gnueabihf-gcc#' 
-e 's#@@TARGET_CXX@@#bin/arm-buildroot-linux-gnueabihf-g++#' 
-e 's#@@TARGET_FC@@#bin/arm-buildroot-linux-gnueabihf-gfortran#' 
-e 's#@@CMAKE_SYSTEM_PROCESSOR@@#armv8l#' -e 's#@@TOOLCHAIN_HAS_FORTRAN@@#0#' 
-e 's#@@CMAKE_BUILD_TYPE@@#Release#' 
/mnt/dplus/br-tree/buildroot-2018.08/support/misc/toolchainfile.cmake.in > /mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross/share/buildroot/toolchainfile.cmake

Vérifions la toolchain produite :

cd ..
ls board/rpi-3/cross/usr/bin/
arm-buildroot-linux-gnueabihf-addr2line    arm-buildroot-linux-gnueabihf-gcc                arm-buildroot-linux-gnueabihf-nm       arm-linux-c++filt            arm-linux-gcc-ranlib   arm-linux-size
arm-buildroot-linux-gnueabihf-ar           arm-buildroot-linux-gnueabihf-gcc-7.3.0          arm-buildroot-linux-gnueabihf-objcopy  arm-linux-cc                 arm-linux-gcc.br_real  arm-linux-strings
arm-buildroot-linux-gnueabihf-as           arm-buildroot-linux-gnueabihf-gcc-7.3.0.br_real  arm-buildroot-linux-gnueabihf-objdump  arm-linux-cc.br_real         arm-linux-gcov         arm-linux-strip
arm-buildroot-linux-gnueabihf-c++          arm-buildroot-linux-gnueabihf-gcc-ar             arm-buildroot-linux-gnueabihf-ranlib   arm-linux-cpp                arm-linux-gcov-dump    bison
arm-buildroot-linux-gnueabihf-c++.br_real  arm-buildroot-linux-gnueabihf-gcc-nm             arm-buildroot-linux-gnueabihf-readelf  arm-linux-cpp.br_real        arm-linux-gcov-tool    gawk
arm-buildroot-linux-gnueabihf-c++filt      arm-buildroot-linux-gnueabihf-gcc-ranlib         arm-buildroot-linux-gnueabihf-size     arm-linux-elfedit            arm-linux-gprof        lzip
arm-buildroot-linux-gnueabihf-cc           arm-buildroot-linux-gnueabihf-gcc.br_real        arm-buildroot-linux-gnueabihf-strings  arm-linux-g++                arm-linux-ld           m4
arm-buildroot-linux-gnueabihf-cc.br_real   arm-buildroot-linux-gnueabihf-gcov               arm-buildroot-linux-gnueabihf-strip    arm-linux-g++.br_real        arm-linux-ld.bfd       tar
arm-buildroot-linux-gnueabihf-cpp          arm-buildroot-linux-gnueabihf-gcov-dump          arm-linux-addr2line                    arm-linux-gcc                arm-linux-nm           toolchain-wrapper
arm-buildroot-linux-gnueabihf-cpp.br_real  arm-buildroot-linux-gnueabihf-gcov-tool          arm-linux-ar                           arm-linux-gcc-7.3.0          arm-linux-objcopy      yacc
arm-buildroot-linux-gnueabihf-elfedit      arm-buildroot-linux-gnueabihf-gprof              arm-linux-as                           arm-linux-gcc-7.3.0.br_real  arm-linux-objdump
arm-buildroot-linux-gnueabihf-g++          arm-buildroot-linux-gnueabihf-ld                 arm-linux-c++                          arm-linux-gcc-ar             arm-linux-ranlib
arm-buildroot-linux-gnueabihf-g++.br_real  arm-buildroot-linux-gnueabihf-ld.bfd             arm-linux-c++.br_real                  arm-linux-gcc-nm             arm-linux-readelf

La toolchain de cross-compilation regroupe tous les outils dont les noms sont préfixés par l’architecture (arm), l’outil de production (buildroot), le système d’exploitation de la cible (linux) et les conventions d’interfaçage binaire entre applications et système (gnueabi). Pour simplifier l’appel des outils, des liens symboliques existent raccourcissant le préfixe pour ne garder que l’architecture et le système d’exploitation. On invoquera donc arm-linux-gcc ou arm-linux-g++ par exemple.

board/rpi-3/cross/usr/bin/arm-linux-gcc -v
Utilisation des specs internes.
COLLECT_GCC=/mnt/dplus/br-tree/board/rpi-3/cross/bin/arm-linux-gcc.br_real
COLLECT_LTO_WRAPPER=/mnt/dplus/br-tree/board/rpi-3/cross/bin/../libexec/gcc/arm-buildroot-linux-gnueabihf/7.3.0/lto-wrapper
Cible : arm-buildroot-linux-gnueabihf
Configuré avec: ./configure --prefix=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross --sysconfdir=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross/etc --enable-static --target=arm-buildroot-linux-gnueabihf --with-sysroot=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross/arm-buildroot-linux-gnueabihf/sysroot --disable-__cxa_atexit --with-gnu-ld --disable-libssp --disable-multilib --with-gmp=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross --with-mpc=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross --with-mpfr=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross --with-pkgversion='Buildroot 2018.08' --with-bugurl=http://bugs.buildroot.net/ --disable-libquadmath --enable-tls --disable-libmudflap --enable-threads --without-isl --without-cloog --disable-decimal-float --with-abi=aapcs-linux --with-cpu=cortex-a53 --with-fpu=neon-vfpv4 --with-float=hard --with-mode=arm --enable-languages=c,c++ --with-build-time-tools=/mnt/dplus/br-tree/buildroot-2018.08/../board/rpi-3/cross/arm-buildroot-linux-gnueabihf/bin --enable-shared --disable-libgomp
Modèle de thread: posix
gcc version 7.3.0 (Buildroot 2018.08) 

Si l’on souhaite pouvoir invoquer directement le cross-compiler depuis la ligne de commande sans préciser tout le chemin (par exemple pendant une phase de développement de code métier hors Buildroot), on peut éditer le fichier ~/.bashrc afin d’y ajouter à la fin la ligne suivante :

PATH=$PATH:/home/yannick/media/dplus/br-tree/board/rpi-3/cross/usr/bin/

On peut aussi adopter une autre approche qui consiste à créer un script qui modifie dynamiquement le PATH, script que l’on sourcera (que l’on invoquera en le précédant d’un point) avant chaque session de travail sur le code applicatif.

Système complet

Nous allons construire à présent une image d’un système complet, y compris le noyau, en utilisant la toolchain obtenue précédemment. Il nous faut effacer les fichiers objets, fichiers temporaires, etc. produits auparavant et l’on serait tenté de faire un make clean. Abstenons-nous en néanmoins car cela aurait pour effet d’effacer la toolchain compilée. La solution la plus simple pour éviter les erreurs de manipulation est de supprimer le répertoire de compilation de Buildroot et de décompresser à nouveau l’archive téléchargée.

cd /home/yannick/media/dplus/br-tree
rm -rf buildroot-2018.08
tar xf buildroot-2018.08.tar.bz2 
cd buildroot-2018.08/

Puis nous préparons une nouvelle configuration, toujours, en partant de celle par défaut.

make raspberrypi3_defconfig
make menuconfig

Passons en revue les menus pour observer ce qu’il faut modifier :

  • Target options : rien à changer
  • Build options : * Download dir : configurons le répertoire de téléchargement pour retrouver le précédent. Nouvelle valeur : $(TOPDIR)/../dl
  • Toolchain : plusieurs modifications sont nécessaires pour retrouver la toolchain précédente. * Toolchain type : nous souhaitons que Buildroot considère la toolchain comme préexistante, même si c’est lui qui l’a créée auparavant. Nouvelle valeur : External toolchain * Toolchain : elle a été compilée spécifiquement. Nouvelle valeur : Custom toolchain * Toolchain origin : il n’est pas nécessaire de la télécharger. Valeur conservée : Pre-installed toolchain * Toolchain path : le répertoire dans lequel se trouve le sous-répertoire bin de la chaîne de compilation. Nouvelle valeur : $(TOPDIR)/../board/RPI-3/cross/usr * External toolchain gcc version : si vous n’avez pas noté ce numéro de version lors de la configuration de la toolchain, vous pouvez l’obtenir en appelant arm-linux-gcc -v comme ci-dessus. Nouvelle valeur : 7.x * External toolchain kernel headers series : on peut retrouver le numéro de version si on ne l’a pas noté, mais c’est plus compliqué. Il faut regarder le contenu du fichier ../board/rpi-3/cross/usr/arm-buildroot-linux-gnueabihf/sysroot/usr/include/linux/version.h. On y trouve une valeur LINUX_VERSION_CODE 266515. Il faut convertir ce nombre en hexadécimal, par exemple en saisissant sur la ligne de commande du shell printf ‘%x\n’ 266515. Ceci nous affiche 41113 qui représente chiffre par chiffre le numéro de noyau 4.11.13. Ce sont les deux premiers chiffres qui comptent, le troisième n’est pas nécessairement le même que celui qui sera effectivement sélectionné pour compiler le système complet. Nouvelle valeur : 4.17.x * External toolchain C library : en tant que bibliothèque C, nous avons choisi de compiler une GlibC. Nouvelle valeur : glibc/eglibc * Toolchain has SSP support? : l’option Stack Smashing Protection (qui limite les dégâts en cas de débordement de buffer dans la pile) est active par défaut dans la GCC. Nouvelle valeur : []**. * **Toolchain has RPC support?** : le support pour les Remote Procedure Call est activé dans la chaîne de compilation produite. Nous pouvons l’ajouter ici. Nouvelle valeur : **[]. * Toolchain has C++ support : l’intégration d’un compilateur C++ dans la toolchain était activée par défaut lors de la compilation précédente. Nouvelle valeur : [*]
  • System Configuration : rien à changer dans ce menu, mais nous l’ajusterons un peu plus tard.
  • Kernel : rien à changer
  • Target packages : rien à changer
  • Filesystem images : rien à changer
  • Bootloaders : rien à changer
  • Host utilities : rien à changer
  • Legacy config options : rien à changer

À nouveau, sauvegardons notre configuration pour pouvoir la réutiliser directement si besoin et lançons la compilation.

cp .config ../board/rpi-3/buildroot-2018.08-system-01.config
make

La compilation se termine au bout de quelques minutes avec en haut de la page de message une ligne plutôt surprenante :

/usr/bin/install -m 0644 support/misc/target-dir-warning.txt /mnt/dplus/br-tree/buildroot-2018.08/output/target/THIS_IS_NOT_YOUR_ROOT_FILESYSTEM
>>> skeleton-init-sysv  Extracting
>>> skeleton-init-sysv  Patching
>>> skeleton-init-sysv  Configuring
>>> skeleton-init-sysv  Building
>>> skeleton-init-sysv  Installing to target
rsync -a --ignore-times --exclude .svn --exclude .git --exclude .hg --exclude .bzr --exclude CVS --chmod=u=rwX,go=rX --exclude .empty --exclude '*~' package/skeleton-init-sysv//skeleton/ /mnt/dplus/br-tree/buildroot-2018.08/output/target/
>>> skeleton  Extracting
>>> skeleton  Patching
>>> skeleton  Configuring
>>> skeleton  Building
>>> skeleton  Installing to target
>>> toolchain-external-custom  Extracting
>>> toolchain-external-custom  Patching
>>> toolchain-external-custom  Configuring
Cannot execute cross-compiler '/mnt/dplus/br-tree/buildroot-2018.08/../board/RPI-3/cross/usr/bin/arm-linux-gcc'
make: *** [package/pkg-generic.mk:222: /mnt/dplus/br-tree/buildroot-2018.08/output/build/toolchain-external-custom/.stamp_configured] Error 1