Construire son environnement de "cross-compiling"
-------------------------------------------------

{ Auteur: Guillaume Thouvenin, initialement publié dans le journal LJNB }


    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 powerpc (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.

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.12 de binutils.

  [guill@leffe:src] $ tar zxvf binutils-2.12.tar.gz
  [guill@leffe:src] $ cd ../obj && mkdir binutils && cd binutils
  [guill@leffe:binutils] $ ../../src/binutils-2.12/configure \
  > --prefix=/home/guill/cross-compiler/ppc \
  > --target=powerpc-linux && 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 :

  [guill@leffe:binutils] $ cd ../../ppc
  [guill@leffe:ppc] $ ls -l
  total 28
  drwxr-xr-x    2 guill    users        4096 May  9 16:14 bin
  drwxr-xr-x    2 guill    users        4096 May  9 16:14 include
  drwxr-xr-x    2 guill    users        4096 May  9 16:14 info
  drwxr-xr-x    2 guill    users        4096 May  9 16:14 lib
  drwxr-xr-x    3 guill    users        4096 May  9 16:14 man
  drwxr-xr-x    4 guill    users        4096 May  9 16:14 powerpc-linux
  drwxr-xr-x    3 guill    users        4096 May  9 16:14 share
  [guill@leffe:ppc] $ ls -l bin/
  total 17384
  -rwxr-xr-x  1 guill    users     1398590 May  9 16:14 powerpc-linux-addr2line
  -rwxr-xr-x  2 guill    users     1279900 May  9 16:14 powerpc-linux-ar
  -rwxr-xr-x  2 guill    users     1823567 May  9 16:14 powerpc-linux-as
  -rwxr-xr-x  1 guill    users      177872 May  9 16:14 powerpc-linux-c++filt
  -rwxr-xr-x  1 guill    users      213991 May  9 16:14 powerpc-linux-gasp
  -rwxr-xr-x  2 guill    users     1858507 May  9 16:14 powerpc-linux-ld
  -rwxr-xr-x  2 guill    users     1378635 May  9 16:14 powerpc-linux-nm
  -rwxr-xr-x  1 guill    users     1732441 May  9 16:14 powerpc-linux-objcopy
  -rwxr-xr-x  1 guill    users     1887398 May  9 16:14 powerpc-linux-objdump
  -rwxr-xr-x  2 guill    users     1281171 May  9 16:14 powerpc-linux-ranlib
  -rwxr-xr-x  1 guill    users      477333 May  9 16:14 powerpc-linux-readelf
  -rwxr-xr-x  1 guill    users     1224513 May  9 16:14 powerpc-linux-size
  -rwxr-xr-x  1 guill    users     1253702 May  9 16:14 powerpc-linux-strings
  -rwxr-xr-x  2 guill    users     1732440 May  9 16:14 powerpc-linux-strip

  2.2 - Compilation de GCC
  ------------------------

  Pour la compilation de gcc nous avons utilisé gcc-2.95.3. 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-core-2.95.3.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 ppc.
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/ppc/bin:$PATH
  [guill@leffe:gcc] $ ../../src/gcc-2.95.3/configure \
  > --prefix=/home/guill/cross-compiler/ppc \
  > --target=powerpc-linux \
  > --enable-languages=c \
  > --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 :

  [guill@leffe:gcc] $ cd ../../ppc/bin
  [guill@leffe:bin] $ ls -l 
  total 18192
  -rwxr-xr-x  1 guill  users      210440 May  9 16:49 cpp
  -rwxr-xr-x  1 guill  users       74488 May  9 16:49 gcov
  -rwxr-xr-x  1 guill  users     1398590 May  9 16:14 powerpc-linux-addr2line
  -rwxr-xr-x  2 guill  users     1279900 May  9 16:14 powerpc-linux-ar
  -rwxr-xr-x  2 guill  users     1823567 May  9 16:14 powerpc-linux-as
  -rwxr-xr-x  1 guill  users      177872 May  9 16:14 powerpc-linux-c++filt
  -rwxr-xr-x  1 guill  users      213991 May  9 16:14 powerpc-linux-gasp
  -rwxr-xr-x  1 guill  users      207173 May  9 16:49 powerpc-linux-gcc
  -rwxr-xr-x  2 guill  users     1858507 May  9 16:14 powerpc-linux-ld
  -rwxr-xr-x  2 guill  users     1378635 May  9 16:14 powerpc-linux-nm
  -rwxr-xr-x  1 guill  users     1732441 May  9 16:14 powerpc-linux-objcopy
  -rwxr-xr-x  1 guill  users     1887398 May  9 16:14 powerpc-linux-objdump
  -rwxr-xr-x  1 guill  users      156101 May  9 16:49 powerpc-linux-protoize
  -rwxr-xr-x  2 guill  users     1281171 May  9 16:14 powerpc-linux-ranlib
  -rwxr-xr-x  1 guill  users      477333 May  9 16:14 powerpc-linux-readelf
  -rwxr-xr-x  1 guill  users     1224513 May  9 16:14 powerpc-linux-size
  -rwxr-xr-x  1 guill  users     1253702 May  9 16:14 powerpc-linux-strings
  -rwxr-xr-x  2 guill  users     1732440 May  9 16:14 powerpc-linux-strip
  -rwxr-xr-x  1 guill  users      145921 May  9 16:49 powerpc-linux-unprotoize

  2-3 Compilation de la Glibc
  ---------------------------

  Il reste la glibc. Nous avons utilisé la glibc-2.2.5 avec les threads que
l'on retrouve dans glibc-linuxthreads-2.2.5.tar.gz. Les fichiers incluent dans
la librairie C utilise certains fichiers d'en-têtes de linux. Avant de
compiler il faut donc créer un lien entre les fichiers include de
l'architecture cible dans l'arborescence de linux avec notre environnement de
dévelopement.

  [guill@leffe:bin] $ cd ../powerpc-linux/include 
  [guill@leffe:include] $ ln -s src/linux-2.4.18/include/asm-ppc asm
  [guill@leffe:include] $ ln -s src/linux-2.4.18/include/linux linux

  La compilation peut maintenant commencer (elle nécessiter un certain temps)
  
  [guill@leffe:include] $ cd ../../src
  [guill@leffe:src] $ tar zxvf glibc-2.2.5.tar.gz 
  [guill@leffe:src] $ cd glibc-2.2.5
  [guill@leffe:glibc-2.2.5] $ tar zxvf glibc-linuxthreads-2.2.5.tar.gz
  [guill@leffe:glibc-2.2.5] $ cd ../../obj/ && mkdir libc && cd libc
  [guill@leffe:libc] $ CC=powerpc-linux-gcc AR=powerpc-linux-ar \
  > RANLIB=powerpc-linux-ranlib \
  > ../../src/glibc-2.2.5/configure \
  > --host=powerpc-linux --enable-add-ons \
  > --with-headers=/home/guill/cross-compiler/ppc/powerpc-linux/include \
  > --prefix=/home/guill/cross-compiler/ppc/powerpc-linux \
  > make && make install 

  !!! ATTENTION !!! Si vous avez choisi de faire tout ce qui précède en root
et si vous ne faites pas attention aux chemins d'installation vous pourriez au
moment du "make install" détruire tout votre système car si vous vous trompez
vous pourriez écraser l'ancienne librairie C par celle que nous venons de
compiler et bien évidemment plus rien ne fonctionnerait sur votre machine...
Si vous avez bien suivi nos instructions vous n'aurez aucun problèmes et vous
disposez maintenant d'un environnement minimum sur votre i686 pour développer 
des applications s'exécutant sur un PowerPC

  2.4 - Compilation supplémentaires
  --------------------------------

 L'environnement que nous avons mis en place est le minimum pour pouvoir faire
du développement. A partir de maintenant vous pourriez recompiler d'autres 
choses comme par exemple le compilateur g++ ou un déboggeur.  

3 - Test
========

  Notre programme de test est très simple et utilise notre nouvelle libc. Il 
consiste à afficher les informations concernant la machine sur laquelle il 
s'exécute.

  [guill@leffe:test] $ vim prog.c
  #include 
  #include 

  int main()
  {
        struct utsname buf;
        int res;

        res = uname(&buf);

        if (res != 0) return -1;

        fprintf(stderr, "\nInformation about the current kernel:\n\n");
        fprintf(stderr, "\tsysname  = %s\n", buf.sysname);
        fprintf(stderr, "\tnodename = %s\n", buf.nodename);
        fprintf(stderr, "\trelease  = %s\n", buf.release);
        fprintf(stderr, "\tversion  = %s\n", buf.version);
        fprintf(stderr, "\tmachine  = %s\n", buf.machine);
        fprintf(stderr, "\n");

        return 0;
  }
  ~
  ~
  "prog.c" 22L, 504C
  [guill@leffe:test] $ gcc -o prog prog.c
  [guill@leffe:test] $ file prog
  prog: ELF 32-bit LSB executable, Intel 80386, version 1, dynamically 
  linked (uses shared libs), not stripped
  [guill@leffe:test] $ ./prog

  Information about the current kernel:

   	sysname  = Linux
	nodename = leffe
	release  = 2.4.17
	version  = #1 Sat Dec 22 11:04:17 EST 2001
	machine  = i686

  Maintenant le cross-compiling. Il ne faut pas oublier d'ajouter les
binaires dans le chemin des exécutables (si ce n'est déjà fait).

  [guill@leffe:test] $ export PATH=/home/guill/cross-compiler/ppc/bin/:$PATH
  [guill@leffe:test] $ powerpc-linux-gcc \
  -I/home/guill/cross-compiler/ppc/powerpc-linux/include \
  -L/home/guill/cross-compiler/ppc/powerpc-linux/lib     \
  -o prog prog.c
  [guill@leffe:test] $ file prog
  prog: ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), 
  dynamically linked (uses shared libs), not stripped

  Pour tester si l'exécutable produit fonctionne sur un power pc nous le 
copions sur une machine IBM RS/6000 avec un processeur PPC.

  guill@usf-cf-ppc-linux-1:~$ ./prog 

  Information about the current kernel:

	sysname  = Linux
	nodename = usf-cf-ppc-linux-1
	release  = 2.4.19-pre3-ben0
	version  = #2 Tue Mar 26 17:09:37 PST 2002
	machine  = ppc
 
4 - Conclusion
==============

  Tout fonctionne donc parfaitement :). Nous avons donc compilé un programme 
sur notre machine, une architecture i686, et ce programme s'exécute sur une 
autre machine qui elle possède un processeur PPC.

5 - 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/