La suite d’outils GCC Présentation générale La suite d’outils GCC (GNU Compiler Collection) est un composant clé de la chaîne d’outils GNU ( → GNU Toolchain). Cette chaîne d’outils a vu le jour dans les années 80 dans le cadre du projet GNU lancé par Richard Stallman et dont le but consistait à créer un système de type UNIX complètement libre de droit. GNU est l’abbréviation de Gnu is Not Unix. C’est ce qu’on appelle un “acronyme récursif” : GNU (GNU (GNU (…) is Not Unix) is Not Unix) is Not Unix. Linus Torvalds a, par la suite, conçu un noyau de système d’exploitation nommé (modestement…) Linux qui, combiné à la chaîne d’outils GNU, a donné naissance au système d’exploitation GNU/Linux que l’on désigne plus souvent et plus simplement par le seul nom du noyau c’est-à-dire Linux. Contrairement à ce que l’on pourrait penser, GCC ne fourni pas uniquement un compilateur pour le langage C/C++. Il prend également en charge plusieurs autres langages : Fortran Ada Java Objective C (une variation du langage C/C++ utilisée par exemple par Apple). Le nom du compilateur java dans la suite GCC est gcj. Celui du compilateur C++ est g++ et celui du compilateur C est… gcc (→ c’est sûrement pour cette raison que l’on croit habituellement que la suite GCC se résume au seul compilateur gcc). Le processus de génération d’un fichier exécutable à partir d’un code source en langage C se décompose dans GCC en 4 étapes qu’il est INDISPENSABLE de connaître pour faire face aux différents cas de figure qui peuvent se présenter pour produire une application : Lorsqu’on compile un code source C, on exécute donc la commande gcc qui va en fait invoquer non seulement le compilateur mais également et successivement les différents outils mentionnés dans le schéma précédent (cpp → gcc → as → ld). C’est pour cette raison que gcc est parfois qualifié d'enchaîneur de passes. Détail des passes Pour détailler les différentes étapes, on va se baser sur le programme C suivant qui renvoie le minimum entre la valeur passée en argument au programme et une constante (→ SEUIL_MAX) myapp.h // Constante #define SEUIL_MAX 50 // Macro #define MIN(a,b) ((a) < (b) ? (a) : (b)) myapp.c #include <stdio.h> #include <stdlib.h> #include "myapp.h" int main(int argc, char *argv[]) { int val = atoi(argv[ 1 ]); int min; min = MIN(val, SEUIL_MAX); printf("Min(%d,%d) = %d\n", val, SEUIL_MAX, min); return min; } Résultat exécution $ ./myapp 6 Min(6,50) = 6 $ ./myapp 75 Min(75,50) = 50 $ Précompilation Pour stopper l’enchaineur de passe gcc juste après la précompilation il suffit de taper la commande : $ gcc -E -o myapp.i myapp.c (1) $ 1 On lance l’enchaineur de passe gcc sur le fichier myapp.c mais on l’arrête après la précompilation (→ option -E). Le résultat est stocké dans le fichier myapp.i (→ option -o myapp.i) La commande file appliquée sur le fichier obtenu indique toujours que c’est un code source en langage C. $ file myapp.i myapp.i: C source, ASCII text $ Son contenu est le suivant : myapp.i # 1 "myapp.c" # 1 "<built-in>" # 1 "<command-line>" # 31 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "<command-line>" 2 # 1 "myapp.c" # 1 "/usr/include/stdio.h" 1 3 4 [...] (1) # 5 "myapp.c" int main(int argc, char *argv[]) { int val = atoi(argv[ 1 ]); int min; min = ((val) < (50) ? (val) : (50)); (2)(3) printf("Min(%d,%d) = %d\n", val, 50, min);(3) return min; } 1 ici, on a le contenu de tous les fichiers d’entête inclus (→ stdio.h, stdlib.h, myapp.h) 2 expansion de la macro MIN(…) → ((a) < (b) ? (a) : (b)) 3 substitution de la constante SEUIL_MAX par sa valeur → 50 Compilation Pour stopper l’enchaineur de passe gcc après la compilation et juste avant l’appel de l’assembleur il suffit de taper la commande : $ gcc -S -o myapp.s myapp.c (1) $ 1 On lance l’enchaineur de passe gcc sur le fichier myapp.c mais on l’arrête avant l’appel à l’assembleur (→ option -S). Le résultat est stocké dans le fichier myapp.s (→ option -o myapp.s) La commande file appliquée sur le fichier obtenu indique cette fois-ci que c’est un code source en assembleur. $ file myapp.s myapp.s: assembler source, ASCII text $ Son contenu est le suivant : myapp.s [...] .type main, %function main: @ args = 0, pretend = 0, frame = 16 @ frame_needed = 1, uses_anonymous_args = 0 push {fp, lr} (1) add fp, sp, #4 sub sp, sp, #16 str r0, [fp, #-16] str r1, [fp, #-20] ldr r3, [fp, #-20] add r3, r3, #4 ldr r3, [r3] mov r0, r3 bl atoi str r0, [fp, #-8] ldr r3, [fp, #-8] cmp r3, #50 movlt r3, r3 movge r3, #50 str r3, [fp, #-12] ldr r3, [fp, #-12] mov r2, #50 ldr r1, [fp, #-8] ldr r0, .L3 bl printf ldr r3, [fp, #-12] mov r0, r3 sub sp, fp, #4 @ sp needed pop {fp, pc} .L4: .align 2 .L3: .word .LC0 .size main, .-main .ident "GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0" .section .note.GNU-stack,"",%progbits 1 Les instructions en langage C ont été traduites en instructions assembleur. Assemblage Pour stopper l’enchaineur de passe gcc après la phase d’assemblage (juste avant l’édition de lien), il suffit de taper la commande : $ gcc -c myapp.c (1) $ 1 On lance l’enchaineur de passe gcc sur le fichier myapp.c mais on l’arrête avant l’édition de liens (→ option -c). Le résultat est stocké par défaut dans le fichier myapp.o. La commande file appliquée sur le fichier obtenu indique cette fois-ci c’est un fichier “relogeable” appelé plus couramment fichier objet. $ file myapp.o myapp.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped $ Ce fichier est un fichier binaire et non un fichier texte comme ceux qu’on a pu rencontrer jusqu’ici : les octets qu’il contient n’ont pas vocation à coder des caractères. $ less myapp.o "myapp.o" may be a binary file. See it anyway? (1) ^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^A^(^@^A^@^@^@^@^@^@^@^@^@^@^@<8C>^B^@^@^@^@^@^E4 (2) ^@^@^@^@^@(^@^L^@^K^@^@H-<E9>^D<B0><8D><E2>^P<D0>M<E2>^P^@^K<E5>^T^P^K<E5>^T0ESC<E5>^D0<83><E2>^@0<93><E5>^C^@<A0><E1><FE><FF><FF><EB>^H^@^K<E5>^H0ESC<E5>2^@S <E3>^C0<A0><B1>20<A0><A3>^L0^K<E5>^L0ESC<E5>2 <A0><E3>^H^PESC<E5>^P^@<9F><E5> <FE><FF><FF><EB>^L0ESC<E5>^C^@<A0><E1>^D<D0>K<E2>^@<88><BD><E8>^@^@^@^@Min(%d,%d) = %d ^@^@GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0^@A.^@^@^@aeabi^@^A$^@^@^@^E6^@^F^F^H^A ^A [...] $ hexdump -C myapp.o (3) 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 01 00 28 00 01 00 00 00 00 00 00 00 00 00 00 00 |..(.............| 00000020 8c 02 00 00 00 00 00 05 34 00 00 00 00 00 28 00 |........4.....(.| 00000030 0c 00 0b 00 00 48 2d e9 04 b0 8d e2 10 d0 4d e2 |.....H-.......M.| 00000040 10 00 0b e5 14 10 0b e5 14 30 1b e5 04 30 83 e2 |.........0...0..| 00000050 00 30 93 e5 03 00 a0 e1 fe ff ff eb 08 00 0b e5 |.0..............| 00000060 08 30 1b e5 32 00 53 e3 03 30 a0 b1 32 30 a0 a3 |.0..2.S..0..20..| 00000070 0c 30 0b e5 0c 30 1b e5 32 20 a0 e3 08 10 1b e5 |.0...0..2 ......| 00000080 10 00 9f e5 fe ff ff eb 0c 30 1b e5 03 00 a0 e1 |.........0......| 00000090 04 d0 4b e2 00 88 bd e8 00 00 00 00 4d 69 6e 28 |..K.........Min(| 000000a0 25 64 2c 25 64 29 20 3d 20 25 64 0a 00 00 47 43 |%d,%d) = %d...GC| 000000b0 43 3a 20 28 52 61 73 70 62 69 61 6e 20 38 2e 33 |C: (Raspbian 8.3| [...] 1 la commande less détecte que c’est un fichier binaire et demande confirmation pour le visualiser 2 Si on confirme l’affichage du fichier binaire sous forme de texte, celui-ci est illisible 3 Pour afficher la représentation hexadécimale des octets du fichier, on peut utiliser la commande hexdump Édition de liens La dernière étape consiste à “combiner” le fichier myapp.o avec les librairies qu’il utilise ou les autres fichiers objet de l’application de façon à rendre possible l’appel à leurs fonctions, variables, objets… lors de son exécution. Ainsi, on voit ci-dessous, grâce à la commande nm, que notre fichier myapp.o fait référence à 2 fonctions (→ atoi et printf) qui ne sont pas définies dans le fichier (→ U comme Undefined) $ nm myapp.o U atoi 00000000 T main U printf $ Pour procéder à la génération de l’exécutable depuis le code source, il suffit de taper la commande : $ gcc -o myapp myapp.c (1) 1 l’exécutable généré est stocké dans le fichier myapp (→ option -o myapp). Sans cette option, l’exécutable sera nommé par défaut a.out La commande file appliquée sur le fichier obtenu indique bien que c’est un fichier exécutable. $ file myapp myapp: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=ea8e69c9683b1b490af34e099d3d77cba62d7256, not stripped $ 🞄 🞄 🞄 Passage par valeur vs. passage par référence Aide-mémoire C++/UML