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.