Construire son environnement de "cross-compiling" ------------------------------------------------- { Auteur: Guillaume Thouvenin, initialement publié dans le journal LJNB } { http://home.nordnet.fr/rldt-adsl2/download/CrossCompiler/cross_compiler.htm } { Adaptation pour la cros-compilation MIPS : Christophe Cérin, 7 février 2008 } Plan 0) Introduction 1) Préliminaires 2) Mise en place du cross-compiler 2.1) Compilation de binutils 2.2) Compilation de GCC 2.3) Compilation de la Glibc 2.4) Compilation supplémentaires 3) Test 4) Conclusion 5) Références --------------------------------------------------------------------------- 0 - Introduction ================ Un "cross-compiler" est un compilateur qui produit du code pour une machine possédant une architecture différente de celle sur laquelle il s'exécute. Dans cet article nous allons voir comment configurer un environnement permettant de compiler un programme pour une architecture mips (la cible) en utilsant une architecture i686 (l'hôte). Nous présenterons les différentes étapes de la compilation avant d'entrer dans le vif du sujet. Dans le cours d'architecture, vous avez développé des programmes en assembleur MIPS, testés dans le simulateur SPIM (http://pages.cs.wisc.edu/~larus/spim.html) Que diriez vous de développer en language C et de demander à gcc de générer l'assembleur MIPS, puis de le tester dans le simulateur SPIM ? Voila ce dont nous discutons ici : compiler, sur une architecture x86, les sources de gcc pour qu'il produise du code assembleur mips. 1 - Préliminaires ================= Nous pouvons distinguer quatre étapes dans la compilation: Étape 1 - pré-traitement "preprocessing" (cpp0) qui traite les directives de compilation, les macros et en règle générale tout ce qui commence par #. Étape 2 - la compilation (cc1) qui produit du code assembleur (et donc spécifique à une architecture donnée) Étape 3 - l'assemblage (as) qui produit un fichier objet à partir du code assembleur issu de la compilation. L'un des formats les plus répandus est le format ELF [1]. Étape 4 - l'édition de lien (collect2, nm, strip, ld, ...) dont le rôle est de faire le lien entre les différentes fonctions utilisées dans les fichiers objets. C'est à cette étape que le fichier exécutable est généré Voici une compilation faites étapes par étapes : [guill@leffe] $ gcc -E prog1.c > prog1.i [guill@leffe] $ /usr/lib/gcc-lib/i386-redhat-linux/2.96/cc1 prog1.i main Execution times (seconds) parser : 0.01 (100%) usr 0.00 ( 0%) sys 0.01 (100%) wall varconst : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall jump : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall flow analysis : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall local alloc : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall global alloc : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall flow 2 : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall shorten branches : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall reg stack : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall final : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall symout : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall rest of compilation : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall TOTAL : 0.01 0.00 0.01 [guill@leffe] $ as -o prog1.o prog1.s [guill@leffe] $ file prog1.o prog1.o: ELF 32-bit LSB relocatable, Intel 80386, version 1, not stripped L'édition de liens est un peu plus compliquée puisqu'il faut indiquer ou se trouve les différentes librairies, le symbole de départ, etc... Si vous voulez absolument le faire nous vous conseillons de regarder une trace de compilation (strace). Bien sûr, lorsque nous compilons un programme il n'est pas nécessaire de faire ces étapes manuellement puisque le compilateur de GNU (GCC) s'occupe d'ordonnancer ces appels avec les bons paramètres. Si nous avons expliqué tout ça c'est pour bien comprendre les programmes que nous devons recompiler afin de mettre en oeuvre notre plateforme de développement. Les outils "as" et "ld" utilisés dans les étapes 3 et 4 sont fournis par le paquetage binutils [2]. Le compilateur GCC [3] fournit les outils "cpp", "cc" et "collect2" utilisés lors des étapes 1, 2 et 4. De plus, nous allons avoir besoin de recompiler la librairie C. Nous avons choisi la glibc [4] mais il existe d'autres librairies plus compactes comme par exemple la newlib [5] qui est surtout utilisée dans les systèmes embarqués. Pour récapituler, nous allons avoir besoin de recompiler au minimum trois choses : binutils, gcc et la libc. L'ordre de compilation est important car comme nous l'avons vu le programme gcc s'occupe d'organiser les différentes étapes de la compilation et notamment. Il a donc besoin de connaître les emplacements de certains programmes comme par exemple le gestionnaire d'archives ar fournit par binutil. Donc, nous allons commencer par compiler binutils, ensuite nous compilerons gcc et enfin la librairie C. Si vous souhaitez compiler g++, il faudra le faire après la compilation de la libc puisque celle-ci est nécessaire à sa compilation. Vous devrez donc en premier lieu compiler gcc avec uniquement le support du C après les binutils et après la compilation des librairies C vous pourrez ajouter le support pour C++, Objective C ou autre. 2 - Mise en place du cross-compiler =================================== La première chose que nous faisons est la création d'un espace de travail. Nous avons créé un répertoire de travail dans le /home/guill que nous avons appelé cross-compiler. Nous avons voulu éviter d'être administrateur pour la suite de l'installation (ce qui évite d'écraser par erreur sa libc-i386 par la libc-ppc que nous verrons plus tard). Le répertoire accueillant notre cross-compiler sera /home/guill/cross-compiler/ppc. Nous allons aussi créer un répertoire src et obj qui contiendront respectivement les sources et les fichiers de compilations des différentes parties de notre environnement. [guill@leffe:cross-compiler] $ pwd /home/guill/cross-compiler [guill@leffe:cross-compiler] $ mkdir ppc [guill@leffe:cross-compiler] $ mkdir src [guill@leffe:cross-compiler] $ mkdir obj 2.1 - Compilation de binutils ----------------------------- Nous avons utilisé la version 2.18 de binutils. [guill@leffe:src] $ tar zxvf binutils-2.18.tar.gz [guill@leffe:src] $ cd ../obj && mkdir binutils && cd binutils [guill@leffe:binutils] $ ../../src/binutils-2.18/configure \ > --prefix=/home/guill/cross-compiler/mips \ > --target=mips-elf && make && make install Pour binutils nous voyons que les options sont simples. L'option "prefix" indique l'endroit ou nous installerons les programmes et l'option "target" précise l'architecture cible. Nous n'avons pas précisé l'architecture de la machine hôte car celle-ce sera détectée par le programme de configuration. Si la machine hôte est différente de celle sur utilisée pour la mise en place de l'environnement de cross-compiling alors vous devrez utiliser l'option "host". Les programmes de binutils et de gcc ne sont pas forcement synchronisés et la dernière version de binutils ne fonctionnera peut-être pas avec la dernière version de gcc. Malheureusement pour le savoir la seule solution est d'essayer. Donc, si vous souhaitez compiler votre propre environnement de "cross-compiling" regardez sur le web ce qui marche ou ne marche pas. Lorsque la compilation et l'installation sont terminés vous devriez obtenir ça : [16:15:56: ~/cross-compiler/mips] $ ls -l total 28 drwxr-x--- 2 cerin ensinfo 4096 fév 7 16:10 bin drwxr-xr-x 2 cerin ensinfo 4096 fév 7 16:03 include drwxr-x--- 2 cerin ensinfo 4096 fév 7 16:03 info drwxr-x--- 3 cerin ensinfo 4096 fév 7 16:03 lib drwxr-x--- 4 cerin ensinfo 4096 fév 7 16:03 man drwxr-x--- 4 cerin ensinfo 4096 fév 7 14:30 mips-elf drwxr-x--- 3 cerin ensinfo 4096 fév 7 14:28 share 2.2 - Compilation de GCC ------------------------ Pour la compilation de gcc nous avons utilisé gcc-3.2. Si vous souhaitez compiler g++, ne le faites pas maintenant car comme nous l'avons dit, nous avons besoin des librairies pour l'architecture PowerPC qui n'est pas encore compilée. Pour la compiler, nous avons besoin du cross-compiler donc, la première chose est de compiler gcc avec le support C uniquement. [guill@leffe:ppc] $ cd ../src [guill@leffe:src] $ tar zxvf gcc-3.2.tar.gz [guill@leffe:src] $ cd ../obj && mkdir gcc && cd gcc Attention, ici gcc va avoir besoin de connaître l'emplacement des programmes de binutils s'exécutant sur i686 mais ayant pour cible l'architecture mips. Donc il faut ajouter l'emplacement de ces programmes dans le "path" (utiliser la commande export ou setenv ou ce qui va bien) [guill@leffe:gcc] $ export PATH=/home/guill/cross-compiler/mips/bin:$PATH [guill@leffe:gcc] $ ../../src/gcc-3.2/configure \ > --prefix=/home/guill/cross-compiler/mips \ > --target=mips-elf \ > --enable-languages=c \ > --with-gnu-as \ > --with-newlib && > make && make install Si tout c'est bien passé vous devriez avoir dans le répertoire de cross-compiling les fichiers et dossiers suivants : [16:16:05: ~/cross-compiler/mips] $ ls -l bin/ total 42132 -rwxr-xr-x 1 cerin ensinfo 2971786 fév 7 14:29 mips-elf-addr2line -rwxr-xr-x 2 cerin ensinfo 3169711 fév 7 14:29 mips-elf-ar -rwxr-xr-x 2 cerin ensinfo 5282008 fév 7 14:30 mips-elf-as -rwxr-xr-x 1 cerin ensinfo 2938826 fév 7 14:29 mips-elf-c++filt -rwxr-xr-x 1 cerin ensinfo 301813 fév 7 16:03 mips-elf-cpp -rwxr-xr-x 1 cerin ensinfo 299534 fév 7 16:03 mips-elf-gcc -rwxr-xr-x 1 cerin ensinfo 15757 fév 7 16:03 mips-elf-gccbug -rwxr-xr-x 1 cerin ensinfo 118014 fév 7 16:03 mips-elf-gcov -rwxr-xr-x 2 cerin ensinfo 3956516 fév 7 14:30 mips-elf-ld -rwxr-xr-x 2 cerin ensinfo 3007427 fév 7 14:29 mips-elf-nm -rwxr-xr-x 2 cerin ensinfo 3540276 fév 7 14:29 mips-elf-objcopy -rwxr-xr-x 2 cerin ensinfo 3820562 fév 7 14:29 mips-elf-objdump -rwxr-xr-x 1 cerin ensinfo 172718 fév 7 16:03 mips-elf-protoize -rwxr-xr-x 2 cerin ensinfo 3169742 fév 7 14:29 mips-elf-ranlib -rwxr-xr-x 1 cerin ensinfo 577902 fév 7 14:29 mips-elf-readelf -rwxr-xr-x 1 cerin ensinfo 3002809 fév 7 14:29 mips-elf-size -rwxr-xr-x 1 cerin ensinfo 2971816 fév 7 14:29 mips-elf-strings -rwxr-xr-x 2 cerin ensinfo 3540275 fév 7 14:29 mips-elf-strip -rwxr-xr-x 1 cerin ensinfo 165562 fév 7 16:03 mips-elf-unprotoize On peut maintenant compiler et générer notre premier assembleur mips. [16:22:06: ~/cross-compiler/mips/bin] $ ./mips-elf-gcc -S essai.c et le fichier essai.s contient alors : .file 1 "essai.c" .section .mdebug.abi32 .previous .text .align 2 .globl main .ent main main: .frame $fp,16,$31 # vars= 8, regs= 1/0, args= 0, extra= 0 .mask 0x40000000,-8 .fmask 0x00000000,0 subu $sp,$sp,16 sw $fp,8($sp) move $fp,$sp sw $0,0($fp) sw $0,4($fp) sw $0,0($fp) $L2: lw $2,0($fp) slt $2,$2,10 bne $2,$0,$L5 j $L3 $L5: lw $2,4($fp) addu $2,$2,10 sw $2,4($fp) lw $2,0($fp) addu $2,$2,1 sw $2,0($fp) j $L2 $L3: lw $2,4($fp) move $sp,$fp lw $fp,8($sp) addu $sp,$sp,16 j $31 .end main Ce fichier peut maintenant être repris dans SPIM (après avoir enlevé la deuxième et troisième ligne) : [16:24:06: ~/cross-compiler/mips/bin] $ spim SPIM Version 7.0 of July 7, 2004 Copyright 1990-2004 by James R. Larus (larus@cs.wisc.edu). All Rights Reserved. See the file README for a full copyright notice. Loaded: /var/lib/spim/exceptions.s (spim) load "essai.s" (spim) run (spim) On peut demander à voir les registres pour retrouver la valeur 100 qui est le résultat : (spim) print_all_regs PC = 00000000 EPC = 00000000 Cause = 00000000 BadVAddr= 00000000 Status = 3000ff10 HI = 00000000 LO = 00000000 General Registers R0 (r0) = 0 R8 (t0) = 0 R16 (s0) = 0 R24 (t8) = 0 R1 (at) = 0 R9 (t1) = 0 R17 (s1) = 0 R25 (t9) = 0 R2 (v0) = 10 R10 (t2) = 0 R18 (s2) = 0 R26 (k0) = 0 R3 (v1) = 0 R11 (t3) = 0 R19 (s3) = 0 R27 (k1) = 0 R4 (a0) = 0 R12 (t4) = 0 R20 (s4) = 0 R28 (gp) = 268468224 R5 (a1) = 2147476028 R13 (t5) = 0 R21 (s5) = 0 R29 (sp) = 2147476024 R6 (a2) = 2147476032 R14 (t6) = 0 R22 (s6) = 0 R30 (s8) = 0 R7 (a3) = 0 R15 (t7) = 0 R23 (s7) = 0 R31 (ra) = 4194328 FIR = 00009800 FCSR = 00000000 FCCR = 00000000 FEXR = 00000000 FENR = 00000000 Double Floating Point Registers FP0 = 0.00000 FP8 = 0.00000 FP16 = 0.00000 FP24 = 0.00000 FP2 = 0.00000 FP10 = 0.00000 FP18 = 0.00000 FP26 = 0.00000 FP4 = 0.00000 FP12 = 0.00000 FP20 = 0.00000 FP28 = 0.00000 FP6 = 0.00000 FP14 = 0.00000 FP22 = 0.00000 FP30 = 0.00000 Single Floating Point Registers FP0 = 0.00000 FP8 = 0.00000 FP16 = 0.00000 FP24 = 0.00000 FP1 = 0.00000 FP9 = 0.00000 FP17 = 0.00000 FP25 = 0.00000 FP2 = 0.00000 FP10 = 0.00000 FP18 = 0.00000 FP26 = 0.00000 FP3 = 0.00000 FP11 = 0.00000 FP19 = 0.00000 FP27 = 0.00000 FP4 = 0.00000 FP12 = 0.00000 FP20 = 0.00000 FP28 = 0.00000 FP5 = 0.00000 FP13 = 0.00000 FP21 = 0.00000 FP29 = 0.00000 FP6 = 0.00000 FP14 = 0.00000 FP22 = 0.00000 FP30 = 0.00000 FP7 = 0.00000 FP15 = 0.00000 FP23 = 0.00000 FP31 = 0.00000 On ne la retrouve pas ! Veuillez expliquer. Par contre, si on recompile avec l'option d'optimisation -O4: [16:34:25: ~/cross-compiler/mips/bin] $ ./mips-elf-gcc -O4 -S essai.c on obtient l'assembleur suivant où on remarque que le compilateur a déduit la valeur de sortie de boucle et n'implémente pas d'itération : .file 1 "essai.c" .section .mdebug.abi32 .previous .text .align 2 .globl main .ent main main: .frame $sp,0,$31 # vars= 0, regs= 0/0, args= 0, extra= 0 .mask 0x00000000,0 .fmask 0x00000000,0 li $2,9 # 0x9 addu $2,$2,-1 $L9: .set noreorder .set nomacro bgez $2,$L9 addu $2,$2,-1 .set macro .set reorder addu $2,$2,1 .set noreorder .set nomacro j $31 li $2,100 # 0x64 .set macro .set reorder .end main Note 1 : si votre code C contient des I/O (prinft par exemple), il faudra isoler dans l'assembleur produit par gcc l'appel et remplacer le morceau de code par l'interface adéquate SPIM (voir la doc SPIM). Idem pour la lecture au clavier. Note 2 : le code produit par notre gcc n'est pas tout a fait conforme a ce qui est attendu par SPIM concernant le préambule et le post-ambule (le code concernant la sortie du programme). Ces deux parties de code sont à retoucher à la main en se reportant à la documentation SPIM. 2-3 Compilation de la Glibc --------------------------- Les compilations précédentes sont suffisantes pour compiler un programme C jusqu'à la production de l'assembleur. Si vous voulez aller jusqu'à la génération d'un exécutable, gérer les #include, il vous faudra installer la glibc, la compiler avec notre nouveau gcc pour mips. Nous ne le faisons pas ici car c'est beaucoup plus difficile. 3 - Références ============== CrossGCC Frequently Asked Questions http://www.sthoward.com/CrossGCC/ penguinppc.org - The home of the linux/ppc port http://penguinppc.org/embedded/cross-compiling [1] ELF: Executable and Linking Format http://www.cs.ucdavis.edu/~haungs/paper/node10.html [2] ftp://ftp.kernel.org/pub/linux/devel/binutils/ [3] GNU C Compiler http://gcc.gnu.org/ [4] GNU C Library ftp://ftp.gnu.org/pub/gnu/glibc/ [5] Newlib C library http://sources.redhat.com/newlib/