2010
10.25

Afin de compiler du C pour le faire tourner sur une Fonera+, il est nécessaire d’avoir tous les outils adéquats. Parmi ces outils, le compilateur C est le plus évident, mais il ne servira à rien sans un assembleur et un linker (qui rassemble plusieurs fichiers assemblés en un binaire final). Un debugger est aussi un luxe dont on aura du mal à se passer. Heureusement pour nous, le projet GNU a créé des logiciels libres qui font tout ça : les GNU binutils, GCC et GDB. Il se trouve que le support de la cross-compilation avec ces outils est très bon, ce qui va bien nous arranger.

Tout d’abord, il a fallu décider de l’architecture précise pour laquelle compiler. En effet, le MIPS 4KEc de la Fonera+ peut fonctionner en mode little endian ou en mode big endian, au choix, et il n’est pas pratique de mélanger les deux. J’ai décidé d’utiliser pour DMMS le mode big endian, mais j’essaierai de temps en temps de compiler en mode little endian et de tester ça sur ma Fonera+.

Ensuite, on peut commencer à build les outils dont on aura besoin. J’ai trouvé pas mal de documentation sur le wiki de Linux MIPS ainsi que dans les PKGBUILD des paquets Archlinux sur l’Archlinux User Repository. Je vais tout de même tout résumer ici.

Comme vous le savez surement, la compilation d’un projet GNU se fait en général en deux étapes : la configuration (via le script ./configure) et la compilation (make). Lors de la configuration, on peut spécifier de compiler pour une certaine architecture cible, via l’option --target. L’architecture est désignée par ce que l’on appelle le target triplet qui est souvent sous la forme architecture-os-format. Dans notre cas, l’architecture sera mips, l’OS unknown (car on ne compile pas pour un OS connu) et le format elf, ce qui donne un triplet mips-unknown-elf. Si on voulait compiler pour du MIPS little endian, ça serait mipsel-unknown-elf (le « el » signifiant « little endian » mais inversé, notez l’humour très fin).

Maintenant que tout est expliqué, commençons. On va exporter quelques variables d’environnement dont on va se servir par la suite, notamment le chemin où installer les binaires compilés et le type de système pour lequel on compile :
$ export TARGET=mips-unknown-elf
$ export PREFIX=/usr/cross/fonera

Ensuite, on se place dans un dossier où il y a de l’espace disque (il faut environ 1.5G), puis on télécharge et on désarchive la dernière version de tous les outils qu’ils nous faut :
$ wget http://ftp.gnu.org/gnu/binutils/binutils-2.20.1.tar.bz2
$ wget http://ftp.gnu.org/gnu/gcc/gcc-4.5.1/gcc-core-4.5.1.tar.bz2
$ wget http://ftp.gnu.org/gnu/gdb/gdb-7.2.tar.bz2
$ for f in *.tar.bz2; do tar xjvf $f; rm $f; done;

C’est parti pour la compilation des différents logiciels. Tout d’abord, pour les binutils, rien de spécial :
$ ./configure --target=$TARGET --prefix=$PREFIX
$ make -j3 # environ 2 minutes sur mon i7
$ sudo make install

Vu que je suis curieux et impatient, testons l’assemblage d’une toute petite fonction en assembleur MIPS, qui fait simplement une boucle infinie (on écrit une instruction qui saute vers elle-même) :

$ cat > test.s <<EOF
loop_forever:
    j loop_forever
EOF

On assemble le code en utilisant les binutils que l’on vient de compiler :
$ /usr/cross/fonera/bin/mips-unknown-elf-as test.s -o test.o

Puis pour tester et être sûr que tout est bon, on va désassembler avec objdump, qui fait lui aussi partie des binutils :

$ /usr/cross/fonera/bin/mips-unknown-elf-objdump -d test.o
test.o:     file format elf32-bigmips
Disassembly of section .text:
00000000 <loop_forever>:
   0:   08000000        j       0 <loop_forever>
   4:   00000000        nop

L’output d’objdump nous dit que notre fichier compilé est au format elf32-bigmips (donc du MIPS big endian, impeccable) et on retrouve le code que l’on a mis dans notre test.s. Tout semble bon, on peut continuer la compilation des outils.

Maintenant, la bête : GCC. Comme il n’aime pas trop qu’on le compile directement dans le dossier qui contient ses sources, on va simplement créer un dossier gcc-build et se mettre dedans pour compiler. Au niveau des options du configure, on désactive une tonne de choses qui ne nous serviront pas : le support des threads (on ne compile pas pour linux ou pour windows, il n’aurait de toute façon pas su faire), la libgcj utilisée pour le Java (je doute vraiment en avoir besoin pour le moment), les bibliothèques dynamiques (on ne supportera pas ça tout de suite), le support de la compilation pour du little endian et des variantes de MIPS qui ne nous intéressent pas (--disable-multilib), la bibliothèque de GCC qui s’occupe de détecter les stack corruption, et enfin l’utilisation des headers de la libc de l’architecture cible, vu qu’on n’a pas de libc pour le moment. On définit également le CPU pour lequel on compile par défaut (un MIPS 4Kec). Quel bordel.
$ mkdir gcc-build && cd gcc-build
$ ../gcc-4.5.1/configure --target=$TARGET --prefix=$PREFIX --disable-threads --disable-libgcj --disable-shared --disable-multilib --disable-libssp --without-headers --with-arch=4kec --with-tune=4kec
$ make -j 3 # environ 5 minutes
$ sudo make install

Testons ça de la même manière que les binutils : compilons un test.c basique qui fait une boucle infinie, et désassemblons le :

$ cat > test.c <<EOF
void infinite_loop(void)
{
    for (;;)
        ;
}
EOF
$ /usr/cross/fonera/bin/mips-unknown-elf-gcc -c test.c
$ /usr/cross/fonera/bin/mips-unknown-elf-objdump -d test.o
test.o:     file format elf32-bigmips
Disassembly of section .text:
00000000 <infinite_loop>:
   0:   27bdfff8        addiu   sp,sp,-8
   4:   afbe0004        sw      s8,4(sp)
   8:   03a0f021        move    s8,sp
   c:   08000003        j       c <infinite_loop+0xc>
  10:   00000000        nop

Ça semble bien fonctionner ! Reste donc à compiler GDB, le debugger GNU. C’est en gros la même chose que pour les binutils :
$ ./configure --target=$TARGET --prefix=$PREFIX
$ make -j3 # environ 2 minutes
$ sudo make install

Voilà, nous avons maintenant toute une flopée d’outils pour travailler avec une architecture MIPS : un assembleur, un désassembleur, un linker, un compilateur, un debugger, un stripper, etc. Et c’est donc fini pour cette étape 0.5, dont j’avais prévu de mettre le contenu avec d’autres choses mais ça s’est avéré un peu trop long. Peut-être qu’on va enfin pouvoir exécuter un truc sur ce routeur la prochaine fois ?

6 comments so far

Add Your Comment
  1. Interessant, j’attend la suite. 😉

    • Malheureusement je suis chef de groupe sur deux projets différents en ce moment et j’ai très peu de temps pour moi (c’est limite si j’ai le temps de dormir en fait). La suite attendra donc un peu que le calme soit revenu (bientôt, j’espère).

      En plus de ça je bloque sur l’utilisation de GDB via Redboot : apparemment utiliser ma Fonera+ comme remote target de debug semble faire simplement planter Redboot (genre je ne peux plus m’y connecter ni rien y faire, il faut éteindre et rallumer le routeur). Du coup je regarde mes possibilités : soit installer une nouvelle version de Redboot sur ma Fonera+ (trop risqué pour que je prenne le risque, je peux potentiellement briquer le machin si je me rate), soit utiliser QEMU pour faire les premiers tests et me concentrer au maximum sur le dev d’une pile réseau minimale et d’un driver pour le chipset réseau intégré. Je suis plutôt partant de la deuxième solution, mais ça me donne encore un problème de plus.

  2. Le calme revient que l’on puisse profiter de la suite ? 🙂

    • Rendu ce soir à 23h42, puis atelier C++ toute la semaine prochaine. J’essaierai de trouver du temps bientôt, c’est promis ! 😀

  3. C’est suffisamment intéressant pour être déçu de ne pas avoir de suite. Parce que je ne rêve pas trop, après 6 mois … =”(
    Surtout avec tous les autres articles parus depuis.
    Bref, dommage, c’était très prenant (je sais, je ne sais pas parler).

    • Oups, je viens de me relier pas hasard, de de remarquer que les guillements que j’attendais anglais pour l smiley étaient passé français. Donc, le =”( doit bien être interprété comme un smiley triste : ='(
      Désolé du dérangement.

*