wiki:SujetTP4

ALMO TP n°4 - Génération de code avec GCC et exécution sur architecture matérielle mono-processeur

Préambule

Ce TP s'appuie sur le contenu du TD4, où tous les outils nécessaires à la génération des deux codes binaires, pour le système d'exploitation GIET et pour l'application logicielle "Hello World!" ont été présentés.

Nous utiliserons dans ce TP le simulateur simul_almo_generic, qui permet de simuler un système minimal (tel qu'illustré par la figure ci-contre) comportant :

  • un processeur MIPS32, avec ses deux caches de premier niveau (instructions et données),
  • un cache de deuxième niveau permettant d'accéder à la mémoire externe,
  • une mémoire ROM (non inscriptible et non volatile) contenant le code de démarrage,
  • un périphérique contrôlant un terminal texte (TTY : clavier/écran).
  • un bus permettant la communication entre un unique maître et trois cibles.

Le simulateur simul_almo_generic reproduit fidèlement le comportement des échanges d'information entre le processeur et les trois composants matériels périphériques. Comme vous le verrez dans les TP suivants, ce simulateur est générique, puisqu' on peut faire varier certaines caractéristiques de l'architecture matérielle telles que le nombre de processeurs, ou le nombre de périphériques connectés sur le bus.

On rappelle que les huit segments de l'espace adressable utilisés contiennent respectivement :

  1. le code de reset, à l'adresse 0xBFC00000
  2. le code du système, à l'adresse 0x80000000
  3. les données du système, à l'adresse 0x81000000
  4. les données non cachables du système, à l'adresse 0x82000000
  5. les registres adressables du TTY, à l'adresse 0x90000000
  6. le code utilisateur de l'application, à l'adresse 0x00400000
  7. les données de l'application, à l'adresse 0x10000000
  8. la pile d'exécution, à l'adresse 0x20000000

L'objectif du TP est d'écrire, de compiler et d'exécuter (en simulation) sur cette architecture matérielle le système d'exploitation GIET et une application logicielle, écrite en langage C, qui affiche le célèbre message "Hello World!" sur l'écran du terminal.

Terminologie

Le simulateur simul_almo_generic est, comme le simulateur XSPIM, un code binaire compilé pour une machine Linux possédant un processeur Pentium. A la différence du simulateur XSPIM, il simule fidèlement tout se qui se passe dans la plateforme matérielle (cycle par cycle, et non pas instruction par instruction), y compris les cycles où le processeur est gelé en cas de MISS sur les caches, et le temps passé dans les échanges de données sur le bus, entre le processeur et la mémoire ou les périphériques.

Dans ce TP, vous devrez générer deux autres fichiers binaires, sys.bin et app.bin qui contiendront aussi du code binaire exécutable, mais ce code binaire est destiné à être exécuté par un processeur MIPS32. Ces fichiers binaires seront générés par la chaîne de compilation croisée GNU/GCC.

Le simulateur simul_almo_generic lit les deux codes binaires contenus dans les deux fichiers sys.bin et app.bin, puis charge ces codes binaires dans les mémoires ROM et RAM de l'architecture matérielle qu'on veut simuler. Ce code binaire sera ensuite exécuté par le processeur MIPS32, qui devra donc effectuer des transactions sur le bus pour lire les instructions dans les mémoires, ou envoyer des commandes au contrôleur TTY.

On rappelle que la génération des codes binaires exécutables (fichiers sys.bin et app.bin) à partir de fichiers source écrits en C ou en assembleur nécessite généralement trois étapes :

  • La première étape consiste à utiliser le compilateur GCC de la chaîne de compilation pour générer un fichier assembleur (extension .s) pour chaque fichier source écrit en C (extension .c).
  • La seconde étape consiste à utiliser l'assembleur AS de la chaîne de compilation et à générer, pour chaque fichier assembleur, un fichier objet (extension .o). Le fichier objet est déjà du code binaire, mais les adresses n'ont pas été résolues, et le code n'est pas encore exécutable.
  • La troisième étape consiste à utiliser l'éditeur de lien LD de la chaîne de compilation pour rassembler les différents fichiers objet constituant le logiciel à exécuter et générer un fichier binaire exécutable . Il y a deux éditions de liens séparées à effectuer, pour la génération des deux exécutables sys.bin et app.bin.

Le répertoire /Infos/lmd/2019/licence/ue/LU3IN004-2019oct/sesi-almo/soft/tp04/sujet contient les différents fichiers source spécifiques pour ce TP :

$ ls
app.ld  config.h  main.c  reset.s  seg.ld  sys.ld

Commencez par recopier ce répertoire dans votre compte, et placez-vous dans ce répertoire (vous pouvez copier/coller une commande en sélectionnant le texte, en vous plaçant dans le terminal et en cliquant sur le bouton du milieu) :

$ cp -r /Infos/lmd/2019/licence/ue/LU3IN004-2019oct/sesi-almo/soft/tp04/sujet   ./tp04
$ cd tp04

1. Génération du code binaire exécutable du GIET : sys.bin

Dans cette première partie, vous allez générer le code binaire exécutable du système d'exploitation GIET en suivant les indications qui vous sont données. Mais tout d'abord, vérifiez que le compilateur croisé est bien disponible à l'aide de la commande suivante :

$ mipsel-unknown-elf-gcc --version

Si le terminal ne trouve pas la commande, c'est que vous n'avez pas configuré correctement votre environnement de travail : veuillez suivre le manuel de configuration.

Vérifiez également que la variable d'environnement $GIET est bien définie pour pointer vers le code source du GIET :

$ echo $GIET

Le code source est divisé en deux parties :

  • dans $GIET/sys, on trouve le code source du noyau qui s'exécute en mode noyau : fichiers common.c, common.h, ctx_handler.c, ctx_handler.h, drivers.c, drivers.h, exc_handler.c, exc_handler.h, giet.s, hwr_mapping.h, irq_handler.c, irq_handler.h, sys_handler.c et sys_handler.h.
  • dans $GIET/app, on trouve le code source des fichiers stdio.c et stdio.h. Ce code, utilisé par l'application logicielle (donc côté utilisateur), constitue une implémentation minimale de ce que l'on appelle la bibliothèque standard de C et contient dans notre cas l'ensemble des appels systèmes permettant à un programme utilisateur de demander des services au système d'exploitation.

Important : ne recopiez pas ces différents fichiers système dans votre répertoire personel. S'ils sont amenés à changer (en cas d'évolution du code système) pendant le déroulement de l'U.E., vous n'aurez pas une version à jour...

1.1 Compilation et assemblage du fichier drivers.c

  • Compilez le fichier drivers.c avec la commande suivante :
$ mipsel-unknown-elf-gcc -ffreestanding -mno-gpopt -mips32 -I$GIET/sys  -I. -c -o drivers.o $GIET/sys/drivers.c

Vous devriez obtenir, dans votre répertoire de travail, un nouveau fichier nommé drivers.o. Ce fichier va être utilisé plus tard, lors de la phase d'édition des liens.

  • Le logiciel mipsel-unknown-elf-objdump permet de désassembler le code binaire et de de visualiser le contenu d'un fichier binaire. Vérifiez que drivers.o contient bien les deux sections nommées .text et .unckdata. Pour cela, lancez la commande suivante permettant de générer le fichier texte drivers.o.txt:
$ mipsel-unknown-elf-objdump -D drivers.o > drivers.o.txt

Note : pour afficher le résultat du désassemblage directement dans un éditeur de texte, on pourra utiliser un pipe:

$ mipsel-unknown-elf-objdump -D drivers.o | less
  • Pourquoi ces 2 sections, qui composent drivers.o (les autres sections présentes ne sont pas utilisées, et peuvent donc être ignorées), commencent-elles à l'adresse 0x00000000 ?

1.2 Compilation et assemblage des autres fichiers systèmes .c

  • En suivant la même procédure que pour la compilation et l'assemblage du fichier drivers.c, compilez les fichiers common.c, irq_handler.c, ctx_handler.c, exc_handler.c et sys_handler.c afin d'obtenir tous les fichiers objet correspondants.
  • Vérifiez à chaque fois, grâce au désassemblage, que le code est bien situé dans la section .text.

1.3 Assemblage du fichier giet.s

Le code d'entrée du GIET, qui gère les interruptions, les exceptions et les trappes, est contenu dans le fichier giet.s. C'est un des rares fichiers à être écrit en assembleur.

  • Assemblez le fichier giet.s avec la commande suivante :
$ mipsel-unknown-elf-as -mips32 -o giet.o $GIET/sys/giet.s
  • Désassemblez le fichier issu de l'assemblage, giet.o, et vérifiez que le code est bien stocké dans la section .giet.

1.4 Assemblage du fichier reset.s

Le code de boot, contenu dans le fichier reset.s, est le code exécuté lors de l'activation du signal nreset, ou lors du démarrage de la machine. Ce code est - entre autres - responsable de la configuration des périphériques et dépend donc du nombre ou du type de périphériques présents dans la plateforme matérielle. Il évoluera donc d'un TP à l'autre, lorsque nous introduirons de nouveaux périphériques matériels dans la plateforme.

  • Assemblez le fichier reset.s avec la commande suivante :
$ mipsel-unknown-elf-as -mips32 -o reset.o reset.s
  • Désassemblez le fichier issu de l'assemblage, reset.o, et vérifiez que le code est bien stocké dans la section .reset.

1.5 Édition de liens

L'édition de liens correspond à la phase où l'on prend tous les fichiers objet, common.o, ctx_handler.o, drivers.o, exc_handler.o, giet.o, irq_handler.o, reset.o et sys_handler.o, qui ont été compilés séparément, et on les rassemble de manière à obtenir un exécutable unique, sys.bin. Cette opération est complexe car il faut résoudre les références externes (c'est-à-dire remplacer les adresses symboliques -les labels- par des valeurs numériques). De plus, il faut regrouper les différentes sections des différents fichiers objet dans les bons segments mémoire.

Pour cela, l'éditeur de liens utilise des fichiers de script ld qui contiennent des directives de placement, et que vous devez éditer et compléter.

  • Complétez le fichier de script nommé seg.ld pour préciser les adresses de base des 8 segments utilisés dans la plateforme simul_almo_generic (en reprenant les indications données au début du sujet de TP).
  • Lancez l'édition de liens avec la commande suivante :
$ mipsel-unknown-elf-ld -o sys.bin -T sys.ld reset.o giet.o common.o  ctx_handler.o drivers.o exc_handler.o irq_handler.o sys_handler.o

On notera que le fichier seg.ld que vous avez complété n'est pas explicitement dans la ligne de commande ci-dessus, car le fichier seg.ld est inclus par le fichier sys.ld.

Le fichier exécutable, sys.bin, contenant le code binaire du GIET est maintenant généré dans votre répertoire de travail.

  • Désassemblez le fichier sys.bin à l'aide de la commande suivante :
$ mipsel-unknown-elf-objdump -D sys.bin > sys.bin.txt

Vous devriez obtenir un fichier nommé sys.bin.txt, lisible dans un éditeur de texte.

  • Vérifiez que les segments ont bien chacun le nom spécifié par les directives du fichier de script ld, et que ces segments sont rangés aux bonnes adresses.

2. Génération du code binaire exécutable de l'application logicielle : app.bin

  • Ouvrez le fichier main.c, qui décrit l'application logicielle, et complétez le code conformément à l'exemple donné dans le TD4.
  • Compilez les fichiers main.c et stdio.c en vous inspirant de la procédure de compilation que vous avez utilisée précédemment pour le GIET. Attention, le fichier main.c est local à votre répertoire de travail, tandis que stdio.c est situé dans le répertoire $GIET/app.

Vous devriez obtenir, dans votre répertoire de travail, deux nouveaux fichiers objet respectivement nommés main.o et stdio.o.

  • En vous inspirant de la procédure d'édition de liens utilisée précédemment pour le GIET, lancez l'édition de liens sur les fichiers objet main.o et stdio.o, en vue d'obtenir le fichier exécutable app.bin. Attention, le fichier de script ld que vous utiliserez pour cette édition de liens est app.ld.

Le fichier exécutable, app.bin, contenant le code binaire de l'application logicielle est maintenant généré dans votre répertoire de travail.

  • Désassemblez le fichier app.bin à l'aide de la commande suivante :
$ mipsel-unknown-elf-objdump -D app.bin > app.bin.txt

Vous devriez obtenir un fichier nommé app.bin.txt, lisible dans un éditeur de texte.

  • Vérifiez que les segments ont bien chacun le nom spécifié par les directives du fichier de script ld, et que ces segments sont aux bonnes adresses.
  • Dans le fichier app.bin.txt, trouvez à quelle adresse débute le code de la fonction main() dans le segment seg_code. Regardez maintenant le contenu du premier mot mémoire du segment seg_data. Qu'observez vous ?
  • Pour les fonctions caractérisées par l'attribut 'constructor' ('__attribute__((constructor))'), GCC créé automatiquement une section nommée '.ctors' contenant un tableau de pointeurs vers ces fonction. En analysant le fichier app.ld, expliquez l'observation que vous avez faite à la question précédente.
  • Expliquez alors en détail comment le code de boot peut se brancher au programme main(), alors que ce code a été compilé séparément du code de l'application utilisateur (et avant qu'on sache quelle est l'adresse du point d'entrée dans le code utilisateur).

3. Exécution du code

Lors du lancement le simulateur simul_almo_generic, il est possible de définir différents paramètres sur la ligne de commande. Deux paramètres sont indispensables :

  • le chemin vers le code binaire exécutable du système, sys.bin (paramètre '-SYS').
  • le chemin vers le code binaire exécutable de l'application logicielle, app.bin (paramètre '-APP').
  • Simulez l'exécution du code que vous venez de générer, avec la commande :
$ simul_almo_generic -SYS sys.bin -APP app.bin

Une fenêtre de terminal XTERM (qui représente l'écran contrôlé par le périphérique TTY) doit normalement s'ouvrir et afficher le message "Hello World!", comme prévu.

L'application logicielle, déterminée par la fonction main(), contient un boucle infinie, et la fonction tty_getc() est bloquante. Par conséquent, le message est ré-affiché à chaque fois que vous appuyez une touche au clavier. On peut sortir de cette boucle en appuyant sur la touche 'q', ce qui déclenche la fin du programme (mais pas l'arrêt du simulateur). La fin du programme correspond effectivement à l'appel de la fonction système exit() qui contient une boucle infinie. Pour arrêter la simulation, il faut se placer dans la fenêtre de terminal dans laquelle a été lancé le simulateur (et non dans la fenêtre correspondant au périphérique de simulation TTY), et presser la combinaison de touche Control-C, ce qui force l'arrêt du processus LINUX correspondant au simulateur simul_almo_generic.

4. Automatisation de la génération : écriture d'un Makefile

  • Afin d'éviter de devoir re-saisir les différentes commandes de compilation et d'édition de liens, complétez le fichier 'Makefile' dont un squelette vous est fourni dans votre répertoire de travail pour automatiser la génération des fichiers sys.bin et app.bin.

Si vous n'êtes pas à l'aise avec avec la syntaxe des fichiers Makefile, vous pouvez commencer par lire la page wikipedia associée.

Une fois le fichier Makefile complet, la génération automatique se lance à l'aide de la commande :

$ make

Pour effacer tous les fichiers issus de cette génération automatique (fichiers objet, exécutables, etc.), vous pouvez utiliser la commande suivante :

$ make clean
  • Vous pouvez maintenant modifier l'application logicielle décrite dans le fichier main.c comme vous le souhaitez, par exemple pour afficher d'autres messages, et relancer le Makefile. Ceci devrait déclencher la compilation du fichier main.c et relancer l'édition de liens pour regénérer le code binaire exécutable app.bin. Le code binaire sys.bin n'a pas besoin d'être regénéré.
  • Vous pouvez alors ré-exécuter cette nouvelle application logicielle à l'aide du simulateur simul_almo_generic.
Last modified 5 years ago Last modified on Aug 26, 2019, 10:28:39 AM