Comment ajouter un paquet Buildroot pour un projet Cargo

Le support de Rust et Cargo pour Buildroot est disponible dans la branche "feature/rust" de ce dépôt Buildroot (personnel).

Dans cet article, nous expliquerons comment ajouter un paquet Buildroot pour un projet Cargo, (nommé "hello-rust"). L'infrastructure pour des paquets Cargo sera peut-être ajoutée dans le futur, afin de faciliter le développement de paquets.

Nous prendrons l'exemple de la construction d'un système QEMU/Aarch64, qui incluera ce projet.

Création du paquet

Comme le paquet pour le projet "hello-rust" n'est d'aucune utilité pour la version officielle de Buildroot, le paquet sera ajouté dans un répertoire spécifique. Ce type d'environnement est décrit dans la section 9.2 du manuel utilisateur de Buildroot.

Premièrement, créez un répertoire spécifique au projet, contenant le répertoire du paquet:

mkdir -p $HOME/src/br2-ext-rust/package/hello-rust

Ensuite, ajoutez un morceau de Makefile pour déclarer le nouveau paquet:

echo 'include $(sort $(wildcard $(BR2_EXTERNAL)/package/*/*.mk))' \
     > $HOME/src/br2-ext-rust/external.mk

Enfin, ajoutez un fichier pour déclarer une entrée dans le menu de configuration pour le nouveau paquet:

echo 'source "$BR2_EXTERNAL/package/hello-rust/Config.in"' \
     > $HOME/src/br2-ext-rust/Config.in

Il est temps de créer le paquet en lui-même. Créez un fichier nommé $HOME/src/br2-ext-rust/package/hello-rust/Config.in avec le contenu suivant:

config BR2_PACKAGE_HELLO_RUST
       bool "hello-rust"
       depends on BR2_PACKAGE_HOST_CARGO
       help
         "Hello World!" program written in Rust

Ensuite, créez le fichier $HOME/src/br2-ext-rust/package/hello-rust/hello-rust.mk, avec le contenu suivant:

################################################################################
#
# hello-rust
#
################################################################################

HELLO_RUST_VERSION = 0.1.1
HELLO_RUST_SITE = $(HOME)/src/hello-rust
HELLO_RUST_SITE_METHOD = local
HELLO_RUST_LICENSE = Public Domain

HELLO_RUST_DEPENDENCIES = host-cargo

HELLO_RUST_CARGO_ENV = \
    CARGO_HOME=$(HOST_DIR)/usr/share/cargo \
    RUST_TARGET_PATH=$(HOST_DIR)/etc/rustc

HELLO_RUST_CARGO_OPTS = \
    --target=$(GNU_TARGET_NAME) \
    --manifest-path=$(@D)/Cargo.toml

ifeq ($(BR2_ENABLE_DEBUG),y)
HELLO_RUST_CARGO_MODE = debug
else
HELLO_RUST_CARGO_MODE = release
endif
HELLO_RUST_CARGO_OPTS += --$(HELLO_RUST_CARGO_MODE)

define HELLO_RUST_BUILD_CMDS
    $(TARGET_MAKE_ENV) $(HELLO_RUST_CARGO_ENV) \
            cargo build $(HELLO_RUST_CARGO_OPTS)
endef

define HELLO_RUST_INSTALL_TARGET_CMDS
    $(INSTALL) -D \
            $(@D)/target/$(GNU_TARGET_NAME)/$(HELLO_RUST_CARGO_MODE)/hello-rust \
            $(TARGET_DIR)/usr/bin/hello-rust
endef

$(eval $(generic-package))

Construction de l'Image Système

Premièrement, clonez le dépôt Buildroot avec le support de Rust et basculer sur la branche:

git clone https://github.com/elebihan/buildroot
cd buildroot
git checkout --track origin/feature/rust

Pour créer une image pour un système QEMU/Aarch64, un fichier defconfig personnalisé avec la configuration adéquate pour construire des projets Cargo sera utilisé. Créez le fichier defconfig comme ceci:

cat <<EOF > qemu_aarch64_virt_rust_defconfig
BR2_aarch64=y
BR2_TOOLCHAIN_EXTERNAL=y
BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0"
BR2_SYSTEM_DHCP="eth0"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="4.5"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux-4.5.config"
BR2_TARGET_ROOTFS_INITRAMFS=y
# BR2_TARGET_ROOTFS_TAR is not set
BR2_PACKAGE_HOST_CARGO=y
BR2_PACKAGE_HOST_RUST=y
EOF

Ensuite, initialisez l'environnement de construction, en indiquant le répertoire spécifique au projet, ainsi que le defconfig personnalisé:

make O=$HOME/build/demo-rust/qemu/aarch64 \
     BR2_EXTERNAL=$HOME/src/br2-ext-rust \
     BR2_DEFCONFIG=qemu_aarch64_virt_rust_defconfig \
     defconfig

Éditez la configuration pour sélectionner le paquet "hello-rust", disponible dans le menu "User-provided options".

make O=$HOME/build/demo-rust/qemu/aarch64 menuconfig

Démarrez la construction:

make O=$HOME/build/demo-rust/qemu/aarch64

Quand la construction est finie, vérifiez que le programme hello-rust est disponible sur la cible:

$ file -b $HOME/build/demo-rust/qemu/aarch64/target/usr/bin/hello-rust
ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=06d2333136445d9a331ad02fb47861cf10654477, stripped

Exécution du Programme depuis le Système

Maintenant, vous pouvez démarrer votre système avec QEMU:

qemu-system-aarch64 \
    -nographic \
    -M virt \
    -cpu cortex-a57 \
    -smp 1 \
    -kernel $HOME/build/demo-rust/qemu/aarch64/images/Image \
    -append "console=ttyAMA0" \
    -netdev user,id=eth0 \
    -device virtio-net-device,netdev=eth0 \
    -serial mon:stdio

Connectez vous en tant que "root" (pas de mot de passe) et exécutez le programme de test:

Welcome to Buildroot
buildroot login: root
# hello-rust
Hello World!

Et voilà!

Générer des Programmes Liés Dynamiquement avec Cargo

Des Gros Programmes, liés Statiquement

Dans un article précédent, Cargo avait été utilisé pour générer un programme Rust: hello-rust.

$ cd $HOME/src/hello-rust
$ stat -c "%s" target/arm-buildroot-linux-gnueabihf/release/hello-rust
218732

La taille du fichier binaire généré est de 213 Ko. Une fois les symboles enlevés, elle descendra jusqu'à 115 Ko. C'est vraiment un "Hello World!" imposant! Étant donné l'architecture du "runtime", tout le code de gestion des entrées/sorties est inclus dans tout binaire lié statiquement, ce qui est le comportement par défaut de rustc (l'exécution de strings sur le fichier donne un résultat effrayant).

Si plusieurs programmes Rust doivent être inclus dans le système de fichiers d'un système embarqué, ce serait un gâchis de place.

Il est possible d'obtenir un fichier plus petit en ajoutant -C prefer-dynamic lors de l'invocation de rustc. Mais pour que le programme puisse s'exécuter correctement, les versions des bibliothèques partagées contenant les symboles manquants, construites avec le compilateur croisé, doivent être copiées dans le système de fichiers de la cible.

Ces bibliothèques ont été construites en même temps que le compilateur Rust et sont disponible dans $HOME/build/demo-rust/qemu/arm/host/usr/lib/rustlib/arm-buildroot-linux-gnueabihf/lib/.

Generating Dynamically Linked Programs with Cargo

Il est possible de générer des programmes liés dynamiquement avec Cargo. Mais cela requiert une fonctionnalité uniquement disponible dans la version 0.10.0, qui n'est pas encore disponible.

Construction de Cargo depuis la Branche "master"

Allez dans le répertoire de base de Buildroot et récupérez la version courante de Cargo (branche "master").

git clone --recursive https://github.com/rust-lang/cargo \
    $HOME/build/demo-rust/qemu/arm/build/host-cargo-lastest

Configurez, construisez et installez:

export PATH=$HOME/build/demo-rust/qemu/arm/host/usr/bin:$PATH
export PKG_CONFIG=$HOME/build/demo-rust/qemu/arm/host/usr/bin/pkgconf
export LIBRARY_PATH=$HOME/build/demo-rust/qemu/arm/host/usr/lib
pushd $HOME/build/demo-rust/qemu/arm/build/host-cargo-latest
./configure --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \
            --localstatedir=$HOME/build/demo-rust/qemu/arm/host/var/lib \
            --sysconfdir=$HOME/build/demo-rust/qemu/arm/host/etc
make
make install
popd

Construction du Programme de Test

Ensuite, créez une nouvelle version du fichier de configuration de Cargo pour le projet "hello-world":

cd $HOME/src/hello-rust
cat <<EOF > .cargo/config
[target.arm-buildroot-linux-gnueabihf]
linker = "arm-buildroot-linux-gnueabihf-gcc"

[build]
rustflags = ["-C", "prefer-dynamic"]
EOF

Grâce aux deux dernières lignes, Cargo est configuré pour passer une option additionnelle au compilateur Rust. Cette option est assez explicite.

Indiquez au compilateur Rust où la spécification de la cible peut être trouvée:

export RUST_TARGET_PATH=$HOME/build/demo-rust/qemu/arm/host/etc/rustc

Maintenant, lancez la construction du projet:

cargo build \
      --target=arm-buildroot-linux-gnueabihf \
      --release

Une fois la construction finie, vérifiez la taille du fichier binaire généré:

$ stat -c "%s" target/arm-buildroot-linux-gnueabihf/release/hello-rust
6880

Le fichier binaire a beaucoup rétréci! Une fois les symboles enlevés, il rétrécira jusqu'à 3680 octets. Copiez-le dans le système de fichiers de la cible:

cp target/arm-buildroot-linux-gnueabihf/release/hello-rust \
   $HOME/build/demo-rust/qemu/arm/target/usr/bin

Copie des Bibliothèques Manquantes

D'habitude, ldd est utilisé pour lister les dépendances d'un programme lié dynamiquement. Malheureusement, cet outil n'est pas disponible dans l'environement Buildroot. Mais il est possible d'utiliser la version de ld.so, l'éditeur/chargeur dynamique, construite avec le compilateur croisé, pour parvenir au même but.

Sur la cible, le chargeur dynamique est HOME/build/demo-rust/qemu/arm/target/lib/ld-2.22.so. Comme il est uniquement exécutable sur une machine ARM, l'émulateur espace utilisateur de QEMU sera utilisé:

$ qemu-arm -r 4.5.0 -E LD_TRACE_LOADED_OBJECTS=1 -E LD_VERBOSE=1 \
> $HOME/build/demo-rust/qemu/arm/target/lib/ld-2.22.so \
> target/arm-buildroot-linux-gnueabihf/release/hello-rust
        libstd-ca1c970e.so => not found
        libgcc_s.so.1 => not found
        libc.so.6 => not found

        Version information:
                target/arm-buildroot-linux-gnueabihf/release/hello-rust:
                libgcc_s.so.1 (GCC_3.5) => not found
                libc.so.6 (GLIBC_2.4) => not found

Note

L'émulateur espace utilisateur de QEMU est lancé avec l'option -r 4.5.0, qui correspond à la version de Linux utilisé, afin d'éviter l'erreur "FATAL: kernel too old".

Comme illustré, le programme dépend de la bibliothèque C (libc.so.6), la bibliothèque bas niveau de GCC (libgcc_s.so.1) et libstd-ca1c970e.so, la bibliothèque standard de Rust.

Les dépendances sont affichées, mais les bibliothèques partagées ne sont pas trouvées, l'inspection est incomplète. Voici le résultat en passant le chemin des bibliothèques:

$ qemu-arm -r 4.5.0 -E LD_TRACE_LOADED_OBJECTS=1 -E LD_VERBOSE=1 \
> $HOME/build/demo-rust/qemu/arm/target/lib/ld-2.22.so \
> --library-path $HOME/build/demo-rust/qemu/arm/host/usr/lib/rustlib/arm-buildroot-linux-gnueabihf/lib:$HOME/build/demo-rust/qemu/arm/target/lib \
> target/arm-buildroot-linux-gnueabihf/release/hello-rust
        libstd-ca1c970e.so => /home/stewie/build/demo-rust/qemu/arm/host/usr/lib/rustlib/arm-buildroot-linux-gnueabihf/lib/libstd-ca1c970e.so (0xf64be000)
        libgcc_s.so.1 => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1 (0xf6492000)
        libc.so.6 => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6 (0xf6356000)
        libdl.so.2 => /home/stewie/build/demo-rust/qemu/arm/target/lib/libdl.so.2 (0xf6342000)
        libpthread.so.0 => /home/stewie/build/demo-rust/qemu/arm/target/lib/libpthread.so.0 (0xf6319000)
        libm.so.6 => /home/stewie/build/demo-rust/qemu/arm/target/lib/libm.so.6 (0xf629f000)
        /lib/ld-linux-armhf.so.3 => /home/stewie/build/demo-rust/qemu/arm/target/lib/ld-2.22.so (0xf6fcf000)

        Version information:
        target/arm-buildroot-linux-gnueabihf/release/hello-rust:
                libgcc_s.so.1 (GCC_3.5) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libc.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
        /home/stewie/build/demo-rust/qemu/arm/host/usr/lib/rustlib/arm-buildroot-linux-gnueabihf/lib/libstd-ca1c970e.so:
                libdl.so.2 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libdl.so.2
                libgcc_s.so.1 (GCC_4.3.0) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libgcc_s.so.1 (GLIBC_2.0) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libgcc_s.so.1 (GCC_3.0) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libgcc_s.so.1 (GCC_4.0.0) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libgcc_s.so.1 (GCC_3.3.1) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libgcc_s.so.1 (GCC_3.5) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1
                libm.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libm.so.6
                libpthread.so.0 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libpthread.so.0
                libc.so.6 (GLIBC_2.17) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
                libc.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
        /home/stewie/build/demo-rust/qemu/arm/target/lib/libgcc_s.so.1:
                libc.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
        /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6:
                ld-linux-armhf.so.3 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/ld-2.22.so
                ld-linux-armhf.so.3 (GLIBC_PRIVATE) => /home/stewie/build/demo-rust/qemu/arm/target/lib/ld-2.22.so
        /home/stewie/build/demo-rust/qemu/arm/target/lib/libdl.so.2:
                ld-linux-armhf.so.3 (GLIBC_PRIVATE) => /home/stewie/build/demo-rust/qemu/arm/target/lib/ld-2.22.so
                libc.so.6 (GLIBC_PRIVATE) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
                libc.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
        /home/stewie/build/demo-rust/qemu/arm/target/lib/libpthread.so.0:
                ld-linux-armhf.so.3 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/ld-2.22.so
                ld-linux-armhf.so.3 (GLIBC_PRIVATE) => /home/stewie/build/demo-rust/qemu/arm/target/lib/ld-2.22.so
                libc.so.6 (GLIBC_PRIVATE) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
                libc.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
        /home/stewie/build/demo-rust/qemu/arm/target/lib/libm.so.6:
                libc.so.6 (GLIBC_PRIVATE) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6
                libc.so.6 (GLIBC_2.4) => /home/stewie/build/demo-rust/qemu/arm/target/lib/libc.so.6

C'est une liste exhaustive! Mais elle montre que seule libstd-ca1c970e.so est requise, étant donné que les autres dépendances sont déjà présentes dans le système de fichiers de la cible.

Pour la copier, exécutez:

cp $HOME/build/demo-rust/qemu/arm/host/usr/lib/rustlib/arm-buildroot-linux-gnueabihf/lib/libstd-ca1c970e.so \
   $HOME/build/demo-rust/qemu/arm/target/usr/lib
chmod a+wx $HOME/build/demo-rust/qemu/arm/target/usr/lib/libstd-ca1c970e.so

Comme cette bibliothèque était à l'origine installée dans $HOME/build/demo-rust/qemu/arm/host/usr/lib, le chemin de recherche à l'exécution ("rpath") dans le fichier ELF est incorrect. Pour le vérifier et le modifier, construisez patchelf:

make O=$HOME/build/demo-rust/qemu/arm host-patchelf

La valeur courante de rpath est:

$ patchelf --print-rpath $HOME/build/demo-rust/qemu/arm/target/usr/lib/libstd-ca1c970e.so
/home/stewie/build/demo-rust/qemu/arm/host/usr/lib/rustlib/arm-buildroot-linux-gnueabihf/lib

Pour la remplacer par un chemin vide, exécutez:

patchelf --set-rpath '' $HOME/build/demo-rust/qemu/arm/target/usr/lib/libstd-ca1c970e.so

Exécution du Programme de Test sur le Système

Reconstruisez l'image système:

make O=$HOME/build/demo-rust/qemu/arm

Maintenant, vous pouvez démarrer votre système avec QEMU:

qemu-system-arm \
    -M vexpress-a9 \
    -m 256 \
    -kernel $HOME/build/demo-rust/qemu/arm/images/zImage \
    -dtb $HOME/build/demo-rust/qemu/arm/images/vexpress-v2p-ca9.dtb \
    -drive file=$HOME/build/demo-rust/qemu/arm/images/rootfs.ext2,if=sd,format=raw \
    -append "console=ttyAMA0,115200 root=/dev/mmcblk0" \
    -serial stdio \
    -serial mon:stdio \
    -net nic,model=lan9118 \
    -net user

Connectez-vous en tant que "root" (pas de mot de passe) et exécutez le programme de test:

Welcome to Buildroot
buildroot login: root
# hello-rust
Hello World!

Excellent!

Note

Quand on utilise QEMU sur Debian Jessie (version 2.1.2), le programme de test ne peut être exécuté, car le chargeur ne peut lire libstd-ca1c970e.so. L'utilisation de strace montre que le noyau essaie de lire au-delà des limites du système de fichiers, qui est une image EXT2 sur une carte SD émulée. dmesg affiche aussi des plaintes au sujet d'une carte SD ayant une mauvaise géométrie.

C'est un défaut de QEMU, qui ne semble pas aligner la taille du système de fichiers (8192 Ko) correctement. Mettre la valeur de "exact size in blocks" à 16384 dans le menu "Filesystem images" de Buildroot et regénérer l'image règle le problème.

Utiliser Cargo avec Buildroot (construction complète)

Cargo est le gestionnaire de paquets officiel pour Rust. Il téléchargera les dépendances d'un projet Rust et compilera le tout. L'ajout du support de Cargo dans Buildroot permettra à l'utilisateur final de compiler facilement des programmes pour un système embarqué.

Nous supposons qu'un environement basé sur Buildroot a déjà été mis en place comme cela a précédemment été décrit.

Construction de Cargo

Récupérez le code source de la dernière version (0.9.0) et décompressez le:

git clone --recursive https://github.com/rust-lang/cargo \
    -b 0.9.0 \
    $HOME/build/demo-rust/qemu/arm/build/host-cargo-0.9.0

Cargo dépend de OpenSSL et de CMake (et de Python, mais un interpréteur Python est supposé être déjà installé sur la machine de développement):

make O=$HOME/build/demo-rust/qemu/arm host-openssl host-cmake

Il dépend aussi de la version hôte de libssh2, qui n'est malheureusement pas disponible sous Buildroot. Cependant il est simple de l'ajouter:

echo '$(eval $(host-autotools-package))' >> package/libssh2/libssh2.mk
make O=$HOME/build/demo-rust/qemu/arm host-libssh2

Configurez et lancez la construction:

export PATH=$HOME/build/demo-rust/qemu/arm/host/usr/bin:$PATH
export PKG_CONFIG=$HOME/build/demo-rust/qemu/arm/host/usr/bin/pkgconf
export LIBRARY_PATH=$HOME/build/demo-rust/qemu/arm/host/usr/lib
pushd $HOME/build/demo-rust/qemu/arm/build/host-cargo-0.9.0
./configure --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \
            --localstatedir=$HOME/build/demo-rust/qemu/arm/host/var/lib \
            --sysconfdir=$HOME/build/demo-rust/qemu/arm/host/etc
make
make install
popd

Notez que Cargo se construit à partir d'une autre version de lui-même, comme on peut le voir dans le journal de construction:

[...]
curl -o target/dl/cargo-nightly-x86_64-unknown-linux-gnu.tar.gz \
     https://static.rust-lang.org/cargo-dist/2016-01-31/cargo-nightly-x86_64-unknown-linux-gnu.tar.gz
[...]

Test de Cargo

Il est temps de tester Cargo. Créez un nouveau projet (ou "crate") pour le programme "Hello World":

mkdir -p $HOME/src
pushd $HOME/src
cargo new --bin hello-rust

Afin de d'effectuer une compilation croisée d'un projet, quelques paramètres doivent figurer dans le fichier de configuration de Cargo. Créez le fichier ainsi:

mkdir .cargo
cat <<EOF > .cargo/config
[target.arm-buildroot-linux-gnueabihf]
linker = "arm-buildroot-linux-gnueabihf-gcc"
EOF

Cargo est configuré pour utiliser l'éditeur de liens de la chaîne de compilation générée par Buildroot, lorsque la cible choisie est "arm-buildroot-linux-gnueabihf".

Indiquez au compilateur Rust l'endroit où trouver les spéficiations de la cible:

export RUST_TARGET_PATH=$HOME/build/demo-rust/qemu/arm/host/etc/rustc

Maintenant, lancez la construction du projet:

cargo build \
      --target=arm-buildroot-linux-gnueabihf \
      --release

Une fois la construction finie, vérifiez que le fichier exécutable généré correspond bien à une architecture ARM:

$ file -b target/arm-buildroot-linux-gnueabihf/release/hello-rust
ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 4.5.0, not stripped

Enfin, copiez le fichier binaire dans le système de fichier de la cible:

cp -a target/arm-buildroot-linux-gnueabihf/release/hello-rust \
   $HOME/build/demo-rust/qemu/arm/target/usr/bin

Pour plus de détails sur Cargo et la création de projets, veuillez vous référer à la documentation de Cargo.

Utiliser Rust avec Buildroot (construction complète)

Dans un article précédent, nous avons vu comment ajouter le support du langage de programmation Rust dans Buildroot, en utiisant des binaires pré-compilés.

Cette fois-ci, nous ajouterons le support pour Rust en construisant un compilateur croisé dans l'environement Buildroot. Nous reprendrons l'exemple précédent, basé sur un système QEMU ARM Versatile Express.

Construction de l'Image Système

Premièrement, récupérez le code source de Buildroot et initialisez la configuration:

# Clonage du dépôt de Buildroot
git clone https://git.buildroot.net/buildroot
cd buildroot
# Configuration pour le système désiré
make O=$HOME/build/demo-rust/qemu/arm qemu_arm_vexpress_defconfig

Ensuite, modifiez la configuration:

make O=$HOME/build/demo-rust/qemu/arm menuconfig

Allez dans le menu "Toolchain" et sélectionnez "glibc" à la place de "uclibc" en tant que bibliothèque C. Sélectionnez aussi le support du C++. Sauvegardez votre configuration et sortez, puis démarrez la construction:

make O=$HOME/build/demo-rust/qemu/arm

Construction du Compilateur Rust et de la Bibliothèque Standard

Téléchargement du Code Source

Premièrement, récupérez le code source du compilateur Rust et décompressez-le:

# Récupération du dernier code source
pushd dl
wget https://static.rust-lang.org/dist/rustc-1.7.0-src.tar.gz
popd
# Création d'un répertoire de destination
mkdir -p $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0
# Extraction du code source
tar -xzf dl/rustc-1.7.0-src.tar.gz \
    -C $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0 \
    --strip-components=1

Configuration de la Compilation Croisée

En interne, le compilateur Rust utilise LLVM en tant que moteur. LLVM s'attend à ce que le nom de la chaîne de compilation corresponde à un triplet au format GNU: cpu-vendeur-noyau. Comme expliqué dans la documentation Autotools:

Currently configuration names are permitted to have four parts on systems which distinguish the kernel and the operating system, such as GNU/Linux. In these cases, the configuration name is cpu-manufacturer-kernel-operating_system.

Mais, comme le champ "vendeur" a généralement la valeur "unknown" ("inconnu"), les outils GNU autorisent son omission, ce qui entraîne le résultat ambigü "x86_64-linux-gnu" rapporté par gcc -dumpmachine sur un système Debian Jessie (à la place de "x86_64-unknown-linux-gnu"). Sur Fedora, le résultat est "x86_64-redhat-linux" (cette fois, pas de "système").

Afin que le système de construction prenne en compte le compilateur croisé généré par Buildroot (qui n'a pas de nom ambigü), ainsi que la machine cible, de nouveaux fichiers doivent être ajoutés.

Pour cela, allez dès maintenant dans le répertoire des sources de Rust:

pushd $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0

Déclaration de la Cible

Le premier fichier requis est un morceau de Makefile, mk/cfg/arm-buildroot-linux-gnueabihf.mk, qui déclare la nouvelle cible. Ce fichier peut facilement être créé en copiant un des fichiers par défaut, qui corresponde à peu près à la nouvelle cible.

sed -e 's/-unknown-/-buildroot-/g' \
    mk/cfg/arm-unknown-linux-gnueabihf.mk \
    > mk/cfg/arm-buildroot-linux-gnueabihf.mk
sed -i -e 's/arm-linux-gnueabihf-/arm-buildroot-linux-gnueabihf-/g' \
    mk/cfg/arm-buildroot-linux-gnueabihf.mk

Spécification de la Cible

Ensuite, le système de construction doit connaitre l'architecture de la nouvelle cible (type de CPU, etc): c'est la spécification de la cible. Il y a deux méthodes pour y parvenir.

La première méthode consisterait à ajouter un nouveau fichier source Rust, basé sur un existant, afin de donner les détails de la cible:

sed -e 's/unknown/buildroot/g' \
    src/librustc_back/target/arm_unknown_linux_gnueabihf.rs \
    > src/librustc_back/target/arm_buildroot_linux_gnueabihf.rs

Le fichier généré ressemble à ceci:

pub fn target() -> Target {
    let base = super::linux_base::opts();
    Target {
        llvm_target: "arm-buildroot-linux-gnueabihf".to_string(),
        target_endian: "little".to_string(),
        target_pointer_width: "32".to_string(),
        arch: "arm".to_string(),
        target_os: "linux".to_string(),
        target_env: "gnueabihf".to_string(),
        target_vendor: "buildroot".to_string(),

        options: TargetOptions {
            features: "+v6,+vfp2".to_string(),
            .. base
        }
    }
}

Note

Se référer à src/librustc_back/target/linux_base.rs pour les morceaux "hérités"

Pour compiler le nouveau module, le patch suivant devrait être appliqué:

Index: host-rust-1.7.0/src/librustc_back/target/mod.rs
===================================================================
--- host-rust-1.7.0.orig/src/librustc_back/target/mod.rs
+++ host-rust-1.7.0/src/librustc_back/target/mod.rs
@@ -417,6 +417,7 @@ impl Target {
             powerpc64le_unknown_linux_gnu,
             arm_unknown_linux_gnueabi,
             arm_unknown_linux_gnueabihf,
+            arm_buildroot_linux_gnueabihf,
             aarch64_unknown_linux_gnu,
             x86_64_unknown_linux_musl,

Cette méthode ne serait pas très pratique pour Buildroot, car il serait nécessaire de fournir et d'appliquer un ensemble de patchs qui déclarent toutes les cibles supportées.

La seconde méthode est meilleure. Au lieu d'ajouter un nouveau module Rust, il est possible de fournir un fichier JSON qui contient les mêmes informations, comme il est expliqué dans la documentation non-officielle de la gestion des cibles par rustc et RFC 0131.

Comme on peut le voir dans src/librustc_back/target/mod.rs, le fichier JSON peut être stocké dans un répertoire figurant dans la liste de valeurs, séparées par des virgules, déclarée par la variable d'environement RUST_TARGET_PATH. La destination choisie est $HOME/build/demo-rust/qemu/arm/host/etc/rustc.

Pour créer le fichier JSON de spécification de la nouvelle cible, exécutez:

mkdir -p $HOME/build/demo-rust/qemu/arm/host/etc/rustc
cat <<EOF > $HOME/build/demo-rust/qemu/arm/host/etc/rustc/arm-buildroot-linux-gnueabihf.json
{
    "llvm-target": "arm-buildroot-linux-gnueabihf",
    "target-endian": "little",
    "target-pointer-width": "32",
    "target-env": "gnueabihf",
    "target-vendor": "buildroot",
    "arch": "arm",
    "os": "linux",
    "features": "+v6,+vfp2",
    "dynamic-linking": true,
    "executables": true,
    "morestack": true,
    "linker-is-gnu": true,
    "has-rpath": true,
    "pre-link-args": [
        "-Wl,--as-needed"
    ],
    "position-independent-executables": true,
    "archive-format": "gnu"
}
EOF

Maintentant, configurez et lancer la construction:

export PATH=$HOME/build/demo-rust/qemu/arm/host/usr/bin:$PATH
export RUST_TARGET_PATH=$HOME/build/demo-rust/qemu/arm/host/etc/rustc
./configure --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \
            --localstatedir=$HOME/build/demo-rust/qemu/arm/host/var/lib \
            --sysconfdir=$HOME/build/demo-rust/qemu/arm/host/etc \
            --target=arm-buildroot-linux-gnueabihf
make -j8 VERBOSE=1
make install
popd

La construction prend un certain temps, car LLVM est compilé avec le support de toutes les architectures, et rustc n'est pas très rapide à se compiler lui-même. Quand tout est fini, vérifiez que l'installation est OK:

$ ls -l $HOME/build/demo-rust/qemu/arm/host/usr/bin
rust-gdb  rustc  rustdoc
$ $HOME/build/demo-rust/qemu/arm/host/bin/rustc --version
rustc 1.7.0-dev
$ ls -1 $HOME/build/demo-rust/qemu/arm/host/usr/lib/rustlib
arm-buildroot-linux-gnueabihf
components
etc
install.log
manifest-rust-docs
manifest-rust-std-arm-buildroot-linux-gnueabihf
manifest-rust-std-x86_64-unknown-linux-gnu
manifest-rustc
rust-installer-version
uninstall.sh
x86_64-unknown-linux-gnu

Comme on peut le voir, la bibliothèque standard est disponible pour les architectures ARM and x86_64.

Construction d'un Programme de Test

Il est temps de tester le compilateur. Créez un fichier source Rust pour le programme "Hello World":

mkdir -p $HOME/src/hello-rust
cat <<EOF > $HOME/src/hello-rust/main.rs
fn main() {
    println!("Hello World!");
}
EOF

Pour construire le programme de test hello-rust, exécutez:

export PATH=$HOME/build/demo-rust/qemu/arm/host/usr/bin:$PATH
$HOME/build/demo-rust/qemu/arm/host/usr/bin/rustc \
    --target=arm-buildroot-linux-gnueabihf \
    -C linker=arm-buildroot-linux-gnueabihf-gcc \
    -o $HOME/build/demo-rust/qemu/arm/target/usr/bin/hello-rust \
    $HOME/src/hello-rust/main.rs

Exécution du Programme de Test sur le Système

Reconstruisez l'image système:

make O=$HOME/build/demo-rust/qemu/arm

Maintenant, vous pouvez démarrer votre système avec QEMU:

qemu-system-arm \
    -M vexpress-a9 \
    -m 256 \
    -kernel $HOME/build/demo-rust/qemu/arm/images/zImage \
    -dtb $HOME/build/demo-rust/qemu/arm/images/vexpress-v2p-ca9.dtb \
    -drive file=$HOME/build/demo-rust/qemu/arm/images/rootfs.ext2,if=sd,format=raw \
    -append "console=ttyAMA0,115200 root=/dev/mmcblk0" \
    -serial stdio \
    -net nic,model=lan9118 \
    -net user

Connectez-vous en tant que "root" (pas de mot de passe) et exécutez le programme de test:

Welcome to Buildroot
buildroot login: root
# hello-rust
Hello World!

Parfait! Une fois de plus, vous venez d'exécuter un programme en Rust sur un système Linux embarqué (emulé), mais cette fois-ci en construisant tout de bout-en-bout.

Utiliser Rust avec Buildroot (binaires pré-compilés)

Rust est un langage de programmation moderne, compilé, orienté vers la sécurité, le contrôle des accès mémoire et les accès concurrents. Ces performances sont comparables au C++.

Les fonctionnalités de Rust en font un bon candidat pour l'écriture de programmes pour des systèmes embarqués.

Dans cet article, nous construirons un système pour QEMU ARM Vexpress en utilisant Buildroot, puis nous ajouterons le support pour Rust en installant la chaîne de compilation pré-compilée dans l'environement Buildroot. Enfin, nous écrirons un programme de test en Rust, qui sera compilé puis exécuté sur le système généré.

Construction de l'Image Système

Premièrement, récupérez le code source de Buildroot et initialisez la configuration:

# Clonage du dépôt de Buildroot
git clone https://git.buildroot.net/buildroot
cd buildroot
# Configuration pour le système désiré
make O=$HOME/build/demo-rust/qemu/arm qemu_arm_vexpress_defconfig

Ensuite, modifiez la configuration:

make O=$HOME/build/demo-rust/qemu/arm menuconfig

Allez dans le menu "Toolchain" et sélectionnez "glibc" à la place de "uclibc" en tant que bibliothèque C. Sélectionnez aussi le support du C++. Sauvegardez votre configuration et sortez, puis démarrez la construction:

make O=$HOME/build/demo-rust/qemu/arm

Installation de la Chaîne de Compilation Rust Pré-compilée

Téléchargez les fichiers binaires du compilateur Rust pour la machine hôte (PC x86_64) et la bibliothèque standard compilée pour la cible "arm-unknown-linux-gnueabihf" (compatible avec la machine ARM Vexpress).

push dl
wget https://static.rust-lang.org/dist/rust-1.7.0-x86_64-unknown-linux-gnu.tar.gz
wget https://static.rust-lang.org/dist/rust-std-1.7.0-arm-unknown-linux-gnueabihf.tar.gz
popd

Préparez l'installation:

# Extraction des fichiers binaires du compilateur
mkdir -p $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0
tar -xzf dl/rust-1.7.0-x86_64-unknown-linux-gnu.tar.gz \
    -C $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0 \
    --strip-components=1
# Extraction de la version ARM de la bibliothèque standard
mkdir -p $HOME/build/demo-rust/qemu/arm/build/host-rust-std-1.7.0
tar -xzf dl/rust-std-1.7.0-arm-unknown-linux-gnueabihf.tar.gz \
    -C $HOME/build/demo-rust/qemu/arm/build/host-rust-std-1.7.0 \
    --strip-components=1

Ensuite, installez le compilateur:

pushd $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0
./install.sh --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \
             --disable-ldconfig
popd

Enfin, installez la bibliothèque standard:

pushd $HOME/build/demo-rust/qemu/arm/build/host-rust-std-1.7.0
./install.sh --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \
             --disable-ldconfig
popd

Construction d'un Programme de Test

Il est temps de tester le compilateur. Créez un fichier source Rust pour le programme "Hello World":

mkdir -p $HOME/src/hello-rust
cat <<EOF > $HOME/src/hello-rust/main.rs
fn main() {
    println!("Hello World!");
}
EOF

Pour construire le programme de test hello-rust, exécutez:

export PATH=$PATH:$HOME/build/demo-rust/qemu/arm/host/usr/bin
$HOME/build/demo-rust/qemu/arm/host/usr/bin/rustc \
    --target=arm-unknown-linux-gnueabihf \
    -C linker=arm-buildroot-linux-gnueabihf-gcc \
    -o $HOME/build/demo-rust/qemu/arm/target/usr/bin/hello-rust \
    $HOME/src/hello-rust/main.rs

Notez que le triplet utilisé avec l'option --target est le même que celui de la bibliothèque standard téléchargée, mais est différent de celui de l'éditeur de lien utilisé.

Exécution du Programme de Test sur le Système

Reconstruisez l'image système:

make O=$HOME/build/demo-rust/qemu/arm

Maintenant, vous pouvez démarrer votre système avec QEMU:

qemu-system-arm \
    -M vexpress-a9 \
    -m 256 \
    -kernel $HOME/build/demo-rust/qemu/arm/images/zImage \
    -dtb $HOME/build/demo-rust/qemu/arm/images/vexpress-v2p-ca9.dtb \
    -drive file=$HOME/build/demo-rust/qemu/arm/images/rootfs.ext2,if=sd,format=raw \
    -append "console=ttyAMA0,115200 root=/dev/mmcblk0" \
    -serial stdio \
    -net nic,model=lan9118 \
    -net user

Connectez-vous en tant que "root" (pas de mot de passe) et exécutez le programme de test:

Welcome to Buildroot
buildroot login: root
# hello-rust
Hello World!

Félicitations! Vous venez d'exécuter votre premier programme en Rust sur un système Linux embarqué (emulé). N'hésitez pas à adapter l'exemple pour exécuter le programme sur un véritable système ARM, tel que le Raspberry Pi.