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 ?

2010
10.23

Vouloir développer un noyau de système d’exploitation pour une machine dont on ne connait presque rien, c’est une chose. Le faire, c’en est une autre. Jusqu’à aujourd’hui je ne connaissais rien du tout de l’intérieur de la Fonera+, comment booter un système, et toutes ces choses nécessaires pour pouvoir commencer à effectivement coder un noyau. Fort heureusement, on trouve beaucoup de documentation sur le net, notamment sur les wikis des firmwares alternatifs de routeur. Du coup, j’ai pu glaner un grand nombre d’informations que je vais résumer dans cet article.

Commençons par le matériel qui se trouve dans la Fonera+. La page de wiki d’OpenWRT parle en détail du matériel de la Fonera normale, et d’après une comparaison sur Tom’s Hardware la seule différence entre la Fonera et la Fonera+ est dans le contrôleur Ethernet. Le CPU est un MIPS 4KEc, donc un CPU MIPS 32 bits qui fonctionne aussi bien en little endian qu’en big endian. D’ailleurs, comme l’indique le wiki officiel de Fon, leur firmware officiel tourne en big endian alors qu’OpenWRT tourne normalement en little endian. Le routeur est doté de 16 Mio de RAM et 8 Mio de Flash. Le reste nous importe peu pour le moment.

Pour exécuter notre noyau sur la Fonera+, il faut trouver un moyen de le transférer sur le routeur puis de lui faire exécuter le code. Sur un PC classique, le BIOS exécute par défaut le premier secteur du premier disque dur pour booter l’OS. En général, il y a un intermédiaire nommé le bootloader (dont GRUB, NTLDR, LILO et quelques autres sont des exemples) qui se charge de booter un noyau qui est sur une partition du disque et pas dans le premier secteur (car c’est plus pratique pour tout le monde que le noyau soit un fichier comme les autres). La Fonera+ a elle aussi un bootloader, nommé RedBoot. Il boote par défaut sur un fichier de la ROM, mais il peut faire plein d’autres choses. Voyons tout d’abord comment on peut y accèder et le contrôler.

On peut rentrer dans la console série de RedBoot de deux manières : via un port série que l’on bricole et que l’on branche sur des bornes de la carte de la Fonera+ (vraiment pas pratique et je suis pas très bricolo), ou simplement en ethernet ! Par défaut, RedBoot sur la Fonera+ est accessible via telnet sur le port 9000 et l’IP 192.168.1.1 pendant 2s avant de démarrer l’OS par défaut de la Fonera+. En s’y connectant pendant les 2s on peut interrompre le chargement de l’OS (via un simple ^C) et rentrer dans la console de RedBoot qui permet de faire tout plein de choses. Sous Linux, en supposant que le routeur est branché directement via un cable ethernet à l’interface eth0 de la machine, la marche à suivre exacte est la suivante :

  • Configurer le réseau sur la machine pour faire transiter tout 192.168.1.0 vers le port ethernet qui nous intéresse : sudo ip route add 192.168.1.0/24 via eth0
  • Débrancher l’alimentation de la Fonera+ pour lui faire subir un reboot complet
  • Créer un fichier ~/.telnetrc via les commandes suivantes afin de configurer telnet en mode bufferisé par ligne (nécessaire pour que le ^C fonctionne !) : echo -e "192.168.1.1\n\tmode line" >> ~/.telnetrc
  • Préparer la machine à se connecter à la Fonera+ dès qu’elle sera accessible : sudo arping -f 192.168.1.1; telnet 192.168.1.1 9000
  • Brancher l’alimentation de la Fonera+ et se préparer à faire ^C dès que la connexion telnet sera établie.

Si tout se passe bien, il devrait s’afficher quelque chose ressemblant à ceci sur la machine locale : ARPING 192.168.1.1 from 192.168.2.202 eth0
Unicast reply from 192.168.1.1 [00:18:84:A0:BF:D0] 1.048ms
Sent 8 probes (8 broadcast(s))
Received 1 response(s)
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
== Executing boot script in 1.030 seconds - enter ^C to abort
^C
RedBoot>

Nous sommes donc dans la console de RedBoot. À partir de là, on peut faire plein de choses utiles dont je ne parlerai pas forcèment (pour ça il y a la commande help). Parmi ce qui nous intéresse, on peut afficher la configuration de RedBoot via la commande fconfig -l -n. Sur ma Fonera+, ça renvoie ceci : RedBoot> fconfig -l -n
boot_script: true
boot_script_data:
.. fis load -l vmlinux.bin.l7
.. exec
boot_script_timeout: 2
bootp: false
bootp_my_gateway_ip: 0.0.0.0
bootp_my_ip: 192.168.1.1
bootp_my_ip_mask: 255.255.255.0
bootp_server_ip: 192.168.1.254
console_baud_rate: 9600
gdb_port: 9000
info_console_force: false
net_debug: false

On peut remarquer là dedans les choses suivantes :

  • On peut redéfinir le script RedBoot lancé au démarrage du routeur. Quand on aura un truc qui fonctionne bien, on pourra le faire se lancer automatiquement avec ça.
  • On peut définir un serveur BOOTP. BOOTP, c’est l’ancêtre de DHCP, et le principe est bien expliqué sur la page Wikipédia du protocole. C’est très bon, ça veut dire qu’on peut potentiellement faire des tests de boot sans avoir à mettre notre noyau sur la ROM à chaque modification. RedBoot ira simplement demander le noyau sur le réseau et on lui renverra le fichier qui va bien.
  • RedBoot se comporte comme un remote target gdb. Cela signifie qu’on peut utiliser le debugger GDB pour debugger notre noyau pendant qu’il est en fonctionnement sur le routeur. Encore une fois, ça sera très pratique quand on devra programmer notre noyau et qu’on aura des bugs louches.

L’autre commande qui va surement être intéressante, c’est load. D’après sa documentation, elle permet de télécharger un binaire dans la RAM pour ensuite l’exécuter. Pour ça, RedBoot peut utiliser soit le protocole TFTP, soit le protocole HTTP. Apparemment HTTP plante dans la version de RedBoot installée sur la Fonera+, tant pis.

Vu que je suis curieux de voir comment ça marche, j’ai mis en place un serveur TFTP sur ma machine pour faire télécharger un fichier à RedBoot. Pour cela, j’ai créé un fichier rempli de zéros avec dd if=/dev/zero of=dmms.bin count=42, puis je l’ai placé dans le dossier /var/tftpboot. J’ai configuré l’interface eth0 de ma machine en point to point avec la commande sudo ifconfig eth0 192.168.1.2 pointopoint 192.168.1.1 puis j’ai lancé /etc/rc.d/tftpd start pour servir le fichier. Ensuite, sur la Fonera+, j’ai lancé load -h 192.168.1.2 dmms.bin, ce qui a effectivement téléchargé le fichier, mais le format n’est pas reconnu (normal, que des zéros) : RedBoot> load -h 192.168.1.2 dmms.bin
Using default protocol (TFTP)
Unrecognized image type: 0x0

On a donc maintenant une méthode permettant d’exécuter du code sur la bête. La prochaine étape sera d’exécuter quelque chose d’un peu plus utile. Peut-être un hello world ? 🙂

Voir aussi :

2010
10.22

Il y a 3 ans, Mickaël Thomas (mickael9) a gagné une Fonera+ lors d’un concours sur Net-Actuality. Ne sachant pas quoi en faire, il a donc décidé de me la donner (merci à lui !). Il se trouve que je n’avais pas non plus d’utilisation pour ce routeur à l’époque, et il est donc resté dans un carton jusqu’au début de ce mois d’Octobre.

Pour ceux qui ne connaitraient pas la Fonera+, c’est un routeur vendu par Fon qui est sensé fournir un access point privé chiffré (classique), mais également un access point public pour tous les utilisateurs d’une Fonera. Ainsi, si je vais en vacances quelque part et que je partage ma Fonera, je peux utiliser la connexion d’autres gens partageant également leur Fonera. C’est un peu le principe du FreeWifi, mais c’est bien plus vieux ! Sur un plan technique de base, la Fonera+ (aussi connue sous le nom de FON2201) est un routeur muni d’une antenne wifi et de deux ports ethernet, un utilisé pour l’upstream et un downstream. Le microgiciel tournant sur ce routeur est une variante verrouillée d’OpenWRT, c’est donc du Linux derrière.

Pour parler un peu de moi, j’utilise dans mon appart un routeur Linksys WRT54GC. Très petit, assez configurable, wifi, 5 ports ethernet, que du bonheur. Le seul problème c’est que le WRT54GC n’a pas assez de RAM ni de ROM (« disque dur » du routeur, permettant de stocker l’OS et la configuration) pour faire tourner un firmware alternatif comme OpenWRT ou DD-WRT. Or je suis actuellement dans ma période où je m’amuse beaucoup avec le réseau, et il y a 2 semaines j’ai eu envie de connecter mon routeur à mon VPN en lançant OpenVPN dessus. Sauf que le WRT54GC ne supporte pas ça. Je me suis donc souvenu que j’avais une Fonera+ dans un carton, qui elle permet de faire tourner OpenWRT. Du coup, j’ai flashé ça avec un outil que j’ai trouvé sur le net, et j’ai un OpenWRT tout propre tout neuf dessus. Mais en fait, la connexion de ma résidence est trop crappy pour avoir un VPN qui a moins de 400ms de ping, donc j’ai abandonné cette idée.

Par contre, une Fonera+, c’est un système que je ne connais pas du tout. Le hardware m’est totalement inconnu, et je n’ai jamais vraiment programmé sur d’autres choses que des architectures Intel classiques (x86 ou x86_64). Du coup, cette Fonera+ est une bonne occasion de m’amuser à programmer pour un système « exotique » (pas tant que ça, mais pour moi si). Et quoi de mieux pour maitriser une plateforme que de coder un noyau de système d’exploitation pour la machine en question ?

Maintenant que j’ai raconté ma vie, je vais donc présenter mon projet : DMMS. Cet acronyme veut simplement dire delroth’s Minimalist MIPS System et sera le nom par lequel je me référerai à ce noyau de système d’exploitation pour ma Fonera+. À noter le « MIPS » dans l’acronyme : la Fonera+ est basée sur un CPU d’architecture MIPS 4KEc. Ça tombe bien, j’ai eu à bosser un peu avec du MIPS l’an dernier quand je programmais un recompilateur de binaire PlayStation (projet de l’échec, mais ça n’est pas le sujet de ce billet). Je n’ai pas vraiment d’objectifs avec ce noyau. Je coderai ce qui me passe par la tête et à un rythme plus ou moins régulier. Disons que j’aurai réalisé un premier objectif quand j’aurai un serveur telnet qui tournera sur ma Fonera+. Mais avant ça il faudra programmer un driver réseau, une pile IP et énormément d’autres choses.

À chaque fois que je jugerai intéressant de le faire, j’écrirai un article sur l’avancement du projet. Je tiendrai compte des commentaires sur les billets et je tâcherai d’y répondre, j’espère donc que cette série ne sera pas que du write-only pour moi 🙂 .

Sur ce, à bientôt pour un article sur l’étape 0 de l’écriture de DMMS : mes expérimentations avec RedBoot, le bootloader de la Fonera+.