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/