Compilation en mode statique : avantages, inconvénients et mise en œuvre

La plupart du temps, lorsque vous compilez un programme avec g++, votre exécutable est en mode dynamique. Ainsi lorsque vous lancez votre exécutable, celui-ci va charger les bibliothèques qui contiennent le code des fonctions que vous n’avez pas codées (par exemple la méthode cout). Ces bibliothèques dynamiques sont des fichiers « .so » sous GNU/Linux ou « .dll » sous Microsoft Windows.

Lorsque vous compilez un programme en mode statique, le code des fonctions externes est inclus dans l’exécutable final. Vous ne dépendez plus alors de bibliothèques externes, votre exécutable est autonome.

Détaillons un peu plus ce qui précède…
Imprimer cet article Imprimer cet article

La compilation dynamique

Prenons l’exemple d’un programme qui fonctionnera sur une carte RaspberryPi avec la bibliothèque WiringPi. Il ne sera pas repris ici comment compiler la bibliothèque WiringPi mais il faut juste se souvenir que vous lancez la compilation et l’installation de la bibliothèque à l’aide du script « build ». Le résultat est la génération, entre autre, des bibliothèques dynamiques qui sont placées dans « /usr/lib » :

Compilons maintenant un exécutable qui utilisera ces bibliothèques :

Compilons cet exemple et regardons de plus prés cet exécutable :

La compilation fait appel à la bibliothèque wiringPi (paramètre -lwiringPi de la ligne de commande). La taille de départ est de 6628 octets. La commande « file » nous donne des renseignements sur le fichier « testWiringPi » : format 32 bits pour architecture ARM, lié dynamiquement avec des bibliothèques partagées (dynamically linked uses shared libs) et enfin les symboles de déverminage sont toujours présents (not stripped).

Enlevons les symboles de déverminage à l’aide de la commande « strip ». La nouvelle taille est alors de 3916 octets. Regardons maintenant les bibliothèques partagées utilisées dans l’exécutable grâce à la commande « ldd » :

Vous remarquez le nombre important de bibliothèques utilisés. Ces bibliothèques devront être impérativement présentes sur le système si vous voulez que le programme fonctionne.

Conclusion partielle : si vous compilez en mode dynamique un programme, il faudra que toutes les bibliothèques soient disponibles sur le système pour que le programme concerné fonctionne. Si l’une d’entre-elles est absente alors le programme ne fonctionnera pas. Cela peut être considéré comme un désavantage. Cependant la taille de l’exécutable est plus faible, si une erreur est décelée dans une bibliothèque, il suffit d’installer une nouvelle version de la bibliothèque SANS recompiler votre programme. Enfin dernière remarque, le temps de chargement d’un exécutable lié dynamiquement est généralement plus long que le même programme en mode statique du fait qu’il faut charger et reloger les bibliothèques dynamiques.

La compilation statique

Pour réaliser un compilation statique, il faut que les bibliothèques que vous utilisez soient elles aussi en mode statique. Dans notre cas, il faut compiler la librairie wiringPi en mode statique.

Les bibliothèques statiques de wiringPi sont alors disponibles dans « /usr/local/lib » :

Vous remarquez que l’extension d’une bibliothèque statique est « .a » ! Compilons alors le programme précédent en mode statique avec la ligne de commande suivante :

Sur la ligne de compilation, vous remarquez le paramètre « -static » qui permet la compilation en mode statique. Il faut spécifier ensuite les bibliothèques utilisées dans le programme (le compilateur prendra tout seul les versions « statiques ») : paramètres « -lwiringPi » et « -lpthread ».

La taille de cet exécutable est alors de 830Ko. Grâce à la commande « file », vous remarquez qu’il s’agit toujours d’un exécutable 32 bits mais cette fois-ci lié statiquement (statically linked). Enlevons les symboles de déverminage : la taille est tout de même de 534Ko. Testons la commande « ldd » sur cet exécutable :

L’exécutable n’est pas lié dynamiquement, il est totalement autonome. Cet exécutable pourra être exécuté dans un environnement minimal.

Mise en œuvre avec un cross-compilateur (arm-linux-) et NetBeans 7.4

Ici nous choisissons de compiler sur une machine de type intel pour avoir un environnement de développement agréable. Nous allons travailler avec la bibliothèque wiringPi aussi il faut l’installer. Pour mémoire on travaille sous Debian Jessie. L’installation de la bibliothèque se fait avec une commande git puis on rentre dans le répertoire wiringPi/wiringPi :

On remarque le fichier Makefile dans ce répertoire. Si on visualise le début de ce fichier on voit le compilateur employé et pour le mode static les commandes employées :

CC est positionné à gcc, pour la cible compilation statique (STATIC) on utilise les commandes ar et ranlib. Mais on doit cross compiler, donc ces commandes ne sont pas adaptées sur la plateforme sur laquelle on travaille (pour mémoire intel). Il faut donc les remplacer respectivement par :

  • gcc par arm-linux-gnueabi-gcc-4.9
  • ar par arm-linux-gnueabi-gcc-ar-4.9
  • ranlib par arm-linux-gnueabi-gcc-ranlib-4.9

A partir de ce moment, la bibliothèque « libwiringPi.a » sera compilé pour une architecture ARM. La compilation sera lancée par la commande suivante :

Cette bibliothèque statique sera employée dans nos futurs projets de cross-compilation avec NetBeans. Par la même occasion on copiera le fichier « wiringPi.h » dans le projet NetBeans. Passons donc dans Netbeans et créons un nouveau projet C++ avec comme chaîne de cross compilation « arm-linux-gnueabi ». Ce qui donne :

TestWiringPiStaticArmelComme fichier de base, je prends un des fichiers d’exemple fourni avec wiringPi : blink.c et je copie la librairie statique libwiringPi.a dans le répertoire du projet :

En passant…vous pouvez compiler depuis la ligne de commande avec une ligne comme ci-dessous. J’ai du mettre en dur le chemin pour la librairie statique libpthread.a malgré l’utilisation du paramètre -L qui permet de spécifier un chemin de recherche supplémentaire pour l’étape de lien :

Il suffit alors de transférer cet exécutable sur une RaspberryPi avec une commande scp. Mais revenons à nos moutons…et continuons la configuration de NetBeans.

Rajoutez dans l’arbre de projet le fichier « blink.c ». Editez-le pour changer la ligne « #include <wiringPi.h> » en « #include « wiringPi.h ». Puis modifiez les propriétés du projet en faisant un clic droit sur le nom du projet puis en cliquant sur « propriétés ». Cliquez sur « Build » puis « C compiler » (bien sur à adapter si vous avez un compilateur C++). Fixez les paramètres comme dans la capture ci-dessus :

TestWiringPiStaticArmel_OptionCompilateurCModifiez ensuite les paramètres du « linker » comme ci-dessous :

TestWiringPiStaticArmel_OptionLinkerDans le champ « Libraries » il faut rajouter un fichier (libwiringPi.a) et une bibliothèque (Posix Thread). Vous pouvez alors lancer une compilation et si tout se passe bien vous aurez un résultat qui peut être celui-ci :

TestWiringPiStaticArmel_BuildReussiIl suffira alors de transférer l’exécutable qui se trouve dans le répertoire ‘dist’ vers votre raspberryPi avec une commande scp.

1 réflexion sur « Compilation en mode statique : avantages, inconvénients et mise en œuvre »

  1. Fabien Chatenet

    Bonjour !
    Article très complet et intéressant, sur GNU/Linux la commande « readelf » (paquet binutils) peut également s’avérer intéressante sur les fichiers objets compilés, pour une analyse plus poussée.
    Il est vrai que la compilation statique est très avantageuse dans un environnement contrôler tel qu’un raspberryPi, c’est cependant une autre paire de manche sur un exécutable qui se veut compatible sur de multiples architectures…
    La sécurité et un autre avantage majeur mais souvent méconnu de la compilation statique,
    beaucoup de hacks softwares sont basé sur le remplacement de librairies dynamiques.
    Une fois chargée par le soft ces librairies potentiellement modifiées pourrais donner accès à certaines zones mémoire du programme. C’est relativement répandu sur Windows.
    Cela dit même avec un linking statique quelqu’un de très déterminer peut toujours désassembler l’exécutable mais bon…
    En tout cas merci pour cet article et bonne continuation !

    Très cordialement

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.