mercredi 12 décembre 2018

Chapitre 52 : Baremetal Raspberry : executer un programme externe


Je n’ai toujours pas trouvé de solution au problème du clavier relié au port USB du Raspberry. Donc en attendant, nous allons voir comment exécuter un programme externe à partir de notre petit OS !! Pour cela nous créons un programme pgm1.s qui n’effectuera que l’envoi d’un message par l’Uart. Je laisse en plus quelques modules utilitaires pour vérification. Le programme est compilé et linké pour fournir un exécutable de type ELF32 (puisque mon raspberry est en 32 bits). Je n’ai pas regardé dans ce premier cas, toutes les contraintes à respecter et donc la seule est de linker les modules avec l’option –r qui va créer les données nécessaires à la <relocalisation> de l’exécutable. En effet, nous n’allons pas charger cet exécutable aux adresses définies par le linker (par exemple 8000h) mais à nos propres adresses et donc il est nécessaire de recalculer un certain nombre d’instructions pour avoir une exécution correcte.
L’exécutable (pgm1.elf) ainsi crée sera stocké dans le répertoire principal de la carte SD (avec le kernel.img) car lors d’un chapitre précédent sur la carte SD, je n’ai pas écrit de fonction pour lire un fichier dans des sous-répertoires de la carte SD (il faut bien que je vous laisse un peu de travail !!).
Vous remarquerez aussi que ce petit programme utilise les modules développes précédemment et donc va stocker en mémoire des instructions qui y figurent déjà. Il serait préférable de développer un mécanisme pour utiliser les fonctions déjà écrites et chargées dans le kernel.img. On verra peut être cela un autre jour !!!
L’utilitaire readelf (disponible sous raspbian ou windows) permet de visualiser les entités du programme. Voici par exemple le début des données de relocalisation :
 
Nous pouvons y voir que la fonction uart_send_string qui se trouve à l’emplacement 0x98c est utilisé au déplacement 0x140 du programme et que cette table commence à l’adresse 0x2ab4 et quelle contient 91 symboles qui seront à relocaliser.
Revenons au module kernel.s de notre OS. Nous y ajoutons une commande exec pour lancer l’exécution de notre petit programme externe (lancement par exec pgm1.elf) puis nous créerons un nouveau module execPgm.s qui contiendra les fonctions nécessaires à cette exécution.
Dans ce module, nous commençons à lire toutes les données du fichier contenant le programme à executer. Comme nous ne connaissons pas la taille, nous chargeons les données sur le tas. Le tas est la zone mémoire comprise entre la fin de la section bss et la pile et qui est complétement libre. Pour cela nous gérons une adresse du tas que nous ferons évoluer à chaque réservation de place nécessaire. Nous conservons l’adresse de début de stockage dans le registre r10 car cette adresse va servir de référence pour traiter toutes les donneés d’un programme de type ELF32.
Ensuite nous vérifions que les données lues correspondent à un format ELF, relogeable et pour un processeur ARM en récupérant les données dans l’entête du fichier grâce à la description d’une structure. Si le fichier est valide, nous récupérons aussi la valeur du point d’entrée du programme et les décalages des différentes tables (voir sur Internet la description d’un programme ELF32) à savoir la table des entêtes sections, la table des noms de section etc.
Puis nous allons analyser la table des entêtes sections pour identifier chaque section du programme. Pour certaines nous nous contentions de mettre à jour le décalage pour les retrouver et pour d’autres nous les recopions sur le tas (.text, .data). En effet, ces sections doivent être alignées sur des frontières particulières (multiple de 2 donnée par sectheaderElf_addralign). Bien entendu nous stockons ces nouvelles adresses dans la structure générale du programme.
Maintenant, il faut analyser la table des relocations pour recalculer les adresses de symboles en fonction des nouvelles adresses des sections .data et .text.  Pour chaque emplacement (décalage par rapport au début de la section) il faut retrouver le déplacement de la position du symbole pour remplacer son adresse. Suivant le type de relocation, il faut effectuer des opérations plus ou moins compliquées. Dans la documentation ARM nous retrouvons pour chaque type l’opération à effectuer. Par exemple pour le type R_ARM_ABS32 nous trouvons l’opération (S +A) | T, ce qui signifie ajouter la nouvelle valeur à l’ancienne et nous ne tenons pas compte de la valeur de T qui correspond à l’exécution d’instructions en thumb ce qui n’est jamais le cas ici.
Pour le type R_ARM_CALL c’est une opération plus compliquée et pour les autres types, je n’ai pas eu à les programmer car sur mes petits programmes je n’ai pas trouvé de cas. Si dans un programme, il y a un cas non traité, un message d’anomalie est affiché avec le code concerné et il faudra donc rechercher l’opération à effectuer et la programmer.
Dans ce programme, il y a un petit problème, car le résultat de la dernière opération est décalé de 4 octets et donc j’ai du les enlever avant d’exécuter le programme externe. Je n’ai pas trouvé l’origine de ce décalage : incompréhension ou erreur de ma part ?
Si tout est ok, on peut lancer le programme en effectuant simplement un saut au point d’entrée qui sera calculé.
Il faudrait améliorer le programme pour sauvegarder l’adresse de la pile  dans une zone mémoire fixe au cas où le programme appelé retourne une adresse de pile fausse !! Et aussi sauver et restaurer les registres si nécessaire.
Après l’exécution, nous remettons l’adresse du tas au début pour permettre l’exécution d’un autre programme sans perte d’espace mémoire.
Et tout le projet est ici.

Aucun commentaire:

Enregistrer un commentaire