lundi 28 octobre 2019

Chapitre 66 : Programmer en assembleur ARM 64 bits avec le raspberry pi 3B+


Il y a plusieurs mois, j’avais acheté un Raspberry pi 3B+ en pensant pouvoir apprendre l’assembleur arm 64 bits. J’avais échoué à le faire fonctionner avec un Linux 64 bits suite à de nombreux problèmes de configuration. Par exemple avec une version Barnani améliorée, le Wifi ne fonctionnait pas !!.
Mais je gardais un œil sur les évolutions des OS 64 bits pour le Raspberry et il y a quelques jours j’ai découvert une version https://github.com/sakaki-/raspbian-nspawn-64. Il m’a fallu moins de  2 heures pour faire fonctionner celle çi sur le Raspberry puis d’arriver à compiler un programme assembleur en 64  bits mais il m’a fallu plus de temps pour que le programme se termine sans erreur !!

Voyons la démarche : tout d’abord, j’ai suivi la documentation (en anglais) pour télécharger l’image sur un pc windows 10  et la flasher grâce à Etcher sur une carte SD de 16GO.
Ensuite pour éviter les problèmes rencontrés avec mon écran LCD et mon petit clavier, j’ai relié le Raspberry au PC grâce au câble de liaison TTL /USB. Dans le fichier config.txt accessible depuis le PC dans la partition boot j’ai ajouté l’option enable_uart=1. Puis j’ai inséré cette nouvelle carte dans le Rapsberry qui a booté sans problème. Dans le logiciel Putty du PC, j’ai vu apparaitre tous les messages d’installation et la demande du user et du mot de passe (j’ai saisi ceux par défaut). J’ai modifié le fichier /etc/wpa_supplicant/wpa_supplicant.conf pour prendre en compte ma livebox et rebooter. Hélas, j’avais oublié d’autoriser le ssh et donc il a fallu à partir de la console Putty série redémarrer le raspberry après avoir autoriser le ssh (dans sudo raspi-config) et créer un fichier ssh dans la partition boot. Il est aussi préférable de modifier le mot de passe de l'utilisateur pi.

Ensuite j’ai pu me connecter par une connexion ssh wifi avec Putty et supprimer le câble de liaison TTL. J’ai donc vérifié quelques commandes unix, crée les répertoires de travail, transféré un source assembleur 32 bits puis le compiler avec as et l’exécuter sans problème. Donc je pourrais toujours écrire et tester des programmes 32 bits.
Ensuite j’ai écrit un premier programme qui utilise les registres x0 et w0 pour le 64 bits.  Évidement la compilation habituelle génère des erreurs. Mais comment compiler en assembleur 64 bits ? Une relecture complète de la documentation de cette version m’indique qu’il faut utiliser un container spécial pour lancer des programmes 64 bits. Il faut donc taper ds64-shell pour lancer debian-buster-64.
Les commandes unix habituelles se déroulent sans problème mais pas le compilateur as !! Après recherche, je m’aperçois qu’il faut installer les packages 64 bits en restant dans ce container. Le compilateur as se trouve dans le package binutils qu’il suffit d’installer comme les autres packages 32 bits (sudo apt-get install binutils).
Cette fois ci la compilation as accepte bien les nouveaux registres mais rejette les commentaires qui commencent par @. Je les remplace par les // et la compilation se termine sans erreur.
Lancement de l’exécution et bien entendu l’erreur bien connue Segmentation fault apparait.
Evidemment, il n'y a aucune visibilité pour savoir d’où vient le problème mais en supprimant toutes les instructions une à une, je trouve qu'il s’agit d’une erreur de l’appel système svc pour terminer le programme. Bien sûr, les codes des call system Linux sont différents entre le 32 bits et le 64 bits. Sur Internet je trouve différentes tables dont les codes pour le EXIT sont différents et ne fonctionnent pas. Puis la recherche sur le fichier unistd.h du raspberry ne donne rien dans le container 64 bits.
Après plusieurs heures de recherche et de test, je trouve enfin un site qui me donne le bon code 93 et j’arrive enfin à terminer normalement le programme. L’affichage d'un message standard devient un jeu d’enfant.
Vous pouvez aussi vérifier que le code retour est bien 5 avec la commande echo $?
Voici le programme ici.
On remarque déjà que les valeurs immédiates ne sont plus précédées du signe #, que les adresses ne sont plus de la forme .int mais .quad (double soit 64 bits ce qui est normal).
Le code du call système doit être passé dans le registre A8 (et non plus r7 comme en 32 bits).
Maintenant il nous reste à découvrir tout le reste !! Mais déjà je découvre que nous avons 31 registres de 64 bits et 31 registres de 32 bits à notre disposition. On va pouvoir s’amuser !!   Non,non Grosse erreur : il n’y a que 31 registres, les registres 32 bits ne sont que la partie basse des registres 64 bits (cf chapitre suivant où j’ai effectué la vérification).

Ah, j’oubliais tous les messages sont en anglais et l’heure n’est pas bonne. Il faut aller à nouveau dans raspi-config pour mettre en place le bon paramétrage local !!

Pour continuer, dans le programme affTexte, nous allons créer une routine pour calculer la longueur de la chaine avant de l’afficher. Je découvre que les instructions push et pop n’existent plus et même les instructions stm et ldm. On ne peut utiliser que des ldr et ldp, cette dernière permettant de stocker 2 registres d’un coup. Il faut de plus gérer l’avancement (ou plutôt le retrait) de la pile donc ici 2 fois 8 octets.
Un petit mot sur les registres : ceux de 64 bits commencent par la lettre x, ceux de 32 bits par la lettre w. Le registre lr est le x30, et le fp est le x29. La pile sp est un registre supplémentaire. Pour les appels de routines, les registre x0 à x7 servent pour le passage des paramètres et ne sont pas conservés. Les registre x8 à x18 servent pour les calculs temporaires et ne sont pas sauvegardés. Les registres x19 à x28 sont sauvegardés. Tout cela est encore à vérifier !!

Autre surprise lors de l’écriture de la routine, l’instruction ldrb n’est valide que si le registre récepteur est un registre 32 bits. Il n’est plus possible de mettre les instructions de calcul de manière conditionnelle (addeq n’est pas autorisée) mais les branch peuvent l’être.
Le retour de la sous procédure se fait simplement par l’instruction ret qui utilise le registre lr (r30) comme adresse de retour.

Prochaine étape : affichage d’un registre.

Aucun commentaire:

Enregistrer un commentaire