dimanche 29 octobre 2017

Chapitre 7 : Entrées de données :Pipe et ligne de commande



Reprenons le premier programme pour saisir une chaine de caractères puis utilisons la puissance des commandes Unix pour envoyer les données d’un ls vers notre programme en utilisant un pipe comme ceci
Ls|Monprogramme  et regardons le résultat :


Nous retrouvons dans notre buffer la liste des fichiers telle qu’elle est affichée dans la commande ls. Et notre programme peut maintenant traiter comme il l’entend cette liste de fichiers : formidable non ? Remarquez que le programme affiche bien l’invite de saisie mais qu’il ne demande rien dans la console et qu’il affiche le résultat sans attendre de saisie.
Une autre façon de passer des données à un programme c’est de les passer en paramètre dans la ligne de commande comme :
Monprogramme param1 param2.
Comment faire pour les récupérer dans un programme assembleur ? et bien ils sont stockés sur la pile comme lorsque nous avons passé des paramètres à une de nos sous routines.
Voyons le programme :
Comme déjà vu, en début de programme nous sauvegardons les 2 registres fp et lr puis nous stockons dans le pointeur de contexte fp, l’adresse de la pile augmentée de 8 octets, ce qui fait que fp pointe sur l’adresse de la pile du début du programme. A cette adresse nous trouvons le nombre de paramètres contenus dans la ligne de commande (y compris le nom du programme). Nous le récupérons et nous le stockons dans r4. Puis à l’adresse fp + 4 octets, nous trouvons l’adresse du premier paramètre (c’est une chaine terminée par 0 binaire). Plus précisement nous trouvons toutes les adresses des paramètres tous les 4 octets.
Nous allons donc effectuer une boucle qui va afficher ces paramètres directement. Et comme nous n’avons pas préparé de ligne de message, nous affichons après un retour ligne pour améliorer l’affichage. Ensuite nous incrémentons le compteur de boucle et nous le comparons à r4 qui contient le nombre de paramètre. Pour la boucle j’ai utilisé un label alphabétique.
Voici le résultat : 

Recupparam est le nom de mon programme et param1 param2 les 2 paramètres saisis dans la ligne de commande  . Dans r4,  nous trouvons bien 3 paramètres, et dans r5 une adresse situé à 12 octets de l’adresse de la pile.
C’est tout pour aujourd’hui.

Exercices: Passer un nombre dans la ligne de commande puis programmer une saisie d’un autre nombre (par READ),  faire la multiplication des deux et afficher le résultat.
Lancer un pipe ls –l | nomdeprogramme   qui extraira la taille de chaque fichier, fera la somme et affichera le résultat.

vendredi 27 octobre 2017

Chapitre 6 : un peu de couleur



Nous voyons que l’affichage dans la console Linux est tristounet et rien ne permet dans l’appel system WRITE de modifier les couleurs des caractères à afficher. Mais comme souvent en informatique il y a une solution : c’est d’utiliser les caractères de contrôle standards qui permettent de jouer avec le strict affichage d’une chaine.
Je vous renvoie donc sur des cours et des présentations des caractères possibles sur Internet par exemple :
http://manpagesfr.free.fr/man/man4/console_codes.4.html
Voici un petit programme qui montre quelques utilisations. 
Tout d’abord nous déclarons différentes lignes de messages à afficher. La zone sMessFinal commence par la suite de caractères de contrôle « \033[0m » ce qui indique au système d’affichage de réinitialiser la couleur de l’affichage. Ensuite nous déclarons une série de codes pour modifier la couleur de la ligne affichée ainsi qu’une suite de caractères pour effacer la console (Clear).
Dans la partie code, nous commençons par afficher la série de code qui permet d’effacer l’écran en les passant à notre sous-routine d’affichage (voir les chapitres précédents). Puis nous entrons dans une boucle qui va afficher le message de demande de saisie puis effectuer l’appel système pour lire la chaine saisie au clavier et qui sera stockée dans le buffer. Ensuite pour déterminer l’action à accomplir, nous devons comparer 2 chaines : celle saisie et celle stockée en mémoire et qui contient le nom de la couleur. Nous écrivons une sous routine comparaison qui balaye les caractères de chaque chaine et qui retourne 0 si les 2 chaines sont égales ou -1 autrement. La comparaison s’arrête lorsque nous rencontrons le caractère 0 binaire. Mais il nous faut régler un petit problème : comme nous l’avons vu au chapitre précédent, la chaine saisie se termine par 0A hexa et donc il nous faut remplacer ce caractère par 0 binaire pour que notre sous routine fonctionne correctement. Heureusement l’appel système READ retourne dans le registre r0 le nombre de caractères lus. Il nous suffit donc de faire -1 sur ce registre puis de l’ajouter à l »adresse de début du buffer pour avoir la position exacte du caractère 0A et ainsi de le remplacer par 0.
Ensuite, nous avons une série de tests pour déterminer quelle couleur a été choisie et afficher le code correspondant avant d’afficher le libelle de la couleur saisie. Si la couleur n’est pas définie dans notre programme, nous affichons un message indicatif. Après l’affichage nous bouclons jusqu’à ce que l’utilisateur saisisse le mot fin. Ah oui aussi, nous comptons le nombre de saisies dans le registre r6 et nous affichons ce compteur en fin de programme. Bien sûr il a fallu le convertir en caractères ascii et en décimal par une sous-routine que nous avons vu précédemment.
Lors de mes tests, j’ai laissé l’affichage mémoire de la zone du buffer et je me suis aperçu que si l’on saisit une chaine plus courte que la saisie précédente, le buffer contient les caractères supplémentaires. Ceci peut entrainer des effets secondaires si la fin de la chaine est mal testée.

Exercices :   Compléter le programme avec d’autres couleurs.
                 Modifier le programme avec d’autres codes de contrôle (tabulation par exemple, couleur de fond etc.).
                Ce programme ne gère pas la différence majuscules/minuscules,  le modifier pour qu'l fonctionne quelle que soit la saisie.

jeudi 26 octobre 2017

Chapitre 5 : Saisie d'un nombre entier



Nous avons vu la saisie d’une chaine de caractères au chapitre précédent et rien n’indique dans l’appel système READ utilisé comment saisir un nombre directement récupérable dans un registre. Et non aucune instruction ne permet cela et donc c’est à nous à programmer une conversion de la chaine saisie en nombre. Avec plusieurs petites questions à résoudre : nombres négatifs, dépassement de capacité, nombres avec virgule etc. Dans ce chapitre nous n’allons pas nous occuper des nombres décimaux car je n’ai pas encore découvert cette partie sur les processeurs ARM mais ça viendra !!!.
Donc voici un programme avec une routine de conversion qui permet de saisir un nombre (positif ou négatif) et de le stocker dans un registre. Sa valeur sera donc comprise entre -1073741824 et +1073741824.
Lien vers le source du programme.
Dans ce programme, et pour simplifier toutes les routines vues précédemment ont été déportées dans le fichier routinesARM.o.
Rien de nouveau dans la section .data. dans la section bss, on ajoute une zone iValeur de 4 octets qui servira à stocker la valeur saisie en mémoire après conversion. Il faut aligner cette zone sur une frontière de double mot avec l’instruction .align 4. Je n’ai pas rencontré d’erreur si cela n’est pas fait et je pense qu’il s’agit plutôt d’une amélioration de performance du processeur.
Dans le programme principal, on retrouve les affichages et l’appel systéme pour la saisie. Après celle çi et s’il n’y a pas d’erreur nous appelons la routine conversionAtoD puis nous affichons les registres et la zone mémoire pour vérification. Enfin nous stockons la valeur retournée dans le registre r0 en mémoire avec les instructions

               ldr r1,adresse_valeur  
                str r0,[r1]

Sans d’autre précision, ces instructions travaillent sur des registres de 32 bits. Donc la première stocke dans le registre r1, l’adresse contenue dans la zone adresse-valeur et la seconde stocke le contenu du registre r0 à l’adresse contenue dans r1. Comprenez bien ces instructions parce que vous allez les utiliser souvent. Il était possible de faire un accès direct à la zone par :

ldr r1,= iValeur
str r0,[r1]

Maintenant voyons la sous-routine de conversion de la chaine de caractères en valeur numérique. Après les sauvegardes habituelles des registres, nous initialisons les registres qui vont être utilisés. Puis nous trouvons une boucle pour sauter tous les caractères blancs pouvant se trouver au début de la saisie. S’il n’y a que des blancs et que nous rencontrons le caractère 0 binaire ou 0A nous allons directement en fin de routine. Sinon au premier caractère non blanc rencontré nous vérifions s’il ne s’agit pas du caractère – et dans ce cas nous mettons 1 dans le registre r6. Puis nous continuons par une boucle qui va contrôler si les caractères sont bien numériques en ascii et va enlever la valeur 48 pour retrouver un chiffre binaire qui sera ajouté au résultat précédent. Mais d’abord il faudra multiplier ce résultat précèdent par 10 puisque chaque chiffre saisi entraine un résultat 10 fois plus grand.
Nous vérifions aussi que le registre r0 ne dépasse pas la valeur maximum car le processeur n’indique en rien ce dépassement. Il y a bien un drapeau v pour overflow mais celui ci n’est pas mis pour la multiplication : curieux non ?
Puis nous continuons la boucle d’analyse jusqu’à rencontrer le caractère 0 binaire ou 0A pour la terminer.
Il nous reste une dernière petit opération, c’est de comparer le registre r6 à 1 ce qui indique que la saisie est un nombre négatif et donc il faut multiplier le résultat par -1 pour avoir un résultat correct. Remarquez que l’instruction mul r0,r0,r1 entraine une erreur de compilation alors que l’instruction mul r0,r1,r0 est correcte.
Cette sous routine peut être améliorée pour la vérification des caractères saisies car par exemple 12aa24 donne le résultat 1224 (décimal)  dans le registre r0.
Exercice : essayer de nombreux nombres positifs ou négatif.
                Que se passe t-il si vous appuyer sur d’autres touches du clavier (tab ou F1 ou les flèches   de direction).
                Effectuer la saisie de 2 nombres, calculer leur produit et afficher le résultat
               Modifier le programme pour saisir une série de nombres (donc en nombre variable) et calculer leur somme.

mardi 24 octobre 2017

Chapitre 4 : saisie dans la console



Bon fini avec les affichages, mais cela nous a permis de créer 2 outils indispensables à la recherche des erreurs : le vidage de tous les registres et le vidage mémoire. Toutes ces routines très utiles sont mises dans un fichier externe et compiler avec as. (Remarque : en plus du code il faut déclarer les zones de la section .data utilisées et mettre le nom de la routine accessible à tous les programmes par la directive .global nomroutine).
Ensuite il suffit de modifier l’étape du link pour insérer le nom de l’objet crée comme ceci :

 ld –o $1 $1 ».o » ~/asm/routinesARM.o –e main

Mon fichier s’appelle routinesARM.o et il est situé au dessus des répertoires de mes sources ce qui permet de ne l’avoir qu’une fois.
Bon maintenant voyons comment saisir une chaine de caractère au clavier. Dans la partie .data, nous préparons un message d’invite que nous afficherons en début du programme. Nous préparons aussi deux messages pour afficher les erreurs rencontrées. Un message affichera le contenu en hexa et en décimal signé du registre r0. Pour varier les instructions, nous remplissons la 2ième zone en utilisant la directive .fill longueur, taille en octet, caractère de remplissage.
Lien vers le source du programme
Puis nous trouvons une nouvelle section .bss que j’ai appelée Données non initialisées. En fait, cela veut dire données non initialisées par le programmeur car les données de cette section vont être remplies de zéros au démarrage du programme. Ici nous déclarons et un buffer de 100 octets par la directive .skip. La longueur est donnée par une constante placée en début de programme ce qui facilite les évolutions futures.
Dans la partie code, nous commençons par sauvegarder les 2 registres fp et lr et a positionner le pointeur de contexte à l’adresse de la pile + 8 octets cad à la valeur de la pile à l’entrée du programme  exactement comme si notre programme principal était une routine. Bien que nous n’ayons pas d’utilité ici, c’est une bonne habitude de commence les programmes avec ces instructions.
Ensuite nous avons un appel à la routine vidtousregistres, c’est ma routine de vidage de tous les registres et qui se trouve maintenant dans mon fichier routinesARM.o et dont j’ai parlé plus haut. J’ai mis ce vidage là pour voir quelles étaient les valeurs des registres au démarrage d’un programme sous Linux. 
Puis nous trouvons l’appel pour afficher le message d’invite de saisie dans la console puis les instructions pour lire les données saisies. Pour cela nous utilisons l’appel système READ (valeur 3) et nous regardons dans la documentation pour connaitre son fonctionnement. J’ai trouvé le site http://syscalls.kernelgrok.com/ qui donne tous les appels système LINUX. Hélas je n’ai pas trouvé la doc pour ceux de Raspbian pour le Raspberry mais je suppose que les principaux seront les mêmes. Et re hélas, les registres indiqués pour le passage des paramètres sont ceux des processeurs intel 32 bits (comme eax). Mais c’est pas grave car il suffit de mettre le code fonction dans le registre r7 et les autres paramètres dans l’ordre r0, r1,r2 ,r3. Et évidement la nature des paramètres est prévu pour le C et donc il faudra un peu de réflexion. Si nous cliquons sur le libellé sys-read, nous trouvons tout le détail de cet appel : les paramètres, la description, la valeur retournée et les erreurs possibles.
Avec cela nous pouvons programmer l’appel en assembleur en mettant le code 3 dans le registre r7, le code de la console standard d’entrée soit 0 dans r0, l’adresse du buffer qui va recevoir les données dans r1 et la taille de ce buffer dans r2 (avec la même constante que la déclaration du buffer).  La documentation indique que des erreurs peuvent être retournées dans r0 sous forme d’un nombre négatif. Il est vivement souhaitable donc de tester le registre r0 et s’il est négatif ; d’afficher un message d’erreur et d’arrêter le programme. C’est que nous faisons ici, dans la routine afficheErreur qui prépare un message dont l’adresse est reçu dans le registre r1 puis affiche la valeur du registre r0 en hexa et en décimal.
Pour voir le contenu des registres après l’appel, nous refaisons un appel à la routine vidtousregistres et à la routine de vidage mémoire pour afficher le buffer et voir son contenu.
Lançons le programme puis après le message d’invite, saisissons des caractères. Ceux-ci s’affichent dans la console et il nous faut terminer la saisie en appuyant sur la touche <enter> . Notre programme affiche les registres et le contenu du buffer comme ceci :

Nous voyons que le registre r0 contient la longueur du texte saisi, que les autres registres r1 à R10 sont inchangés.
Et nous voyons que le buffer contient la chaine saisie suivie par le caractère 0A (retour ligne). Nous remarquons que la longueur dans r0 tient compte de ce caractère. Ces informations vont nous permettre de traiter la chaine comme nous le voulons.
Accessoirement nous remarquons que devant notre buffer, il y a d’autres caractères (des blancs suivis du retour ligne 0A) et je me demande bien d’où ils viennent (question à creuser ?).
Maintenant, nous allons effectuer quelques tests : d’abord générer une erreur volontaire en mettant 5 dans le registre r0 avant l’appel. 

Et la signification du code -9 est EBADF            9      /* Bad file number */  ce qui correspond bien à notre erreur.
Nous remarquons qu’en plus de la valeur 0 (STDIN) les valeurs 1 (STDOUT) et 2 (STDERR) fonctionnent.
Maintenant limitons la longueur du buffer à 10 caractères et effectuons une saisie de plus de 10 caractères :

Nous voyons que la saisie est tronquée à 10 caractères et se termine par le caractère 00 binaire. Et nous constatons que les caractères résiduels restent dans le buffer du clavier et sont envoyés à notre console en fin de programme. C’est pas beau et donc nous devrons penser à mettre une taille du buffer convenable pour chaque saisie.
Remarque : peut être vous demandez-vous pourquoi dans mon vidage des registres, le registre lr est marqué inconnu ? Et bien, si j’avais affiché le contenu du registre tel qu’il se trouve dans la sous routine d’affichage ce n’est pas la valeur qu’il avait avant l’appel de cette procédure. En effet il contient maintenant l’adresse de retour vers le programme principal. Et pour éviter toute mauvaise interprétation, je préfère mettre inconnu. Si j'ai besoin de vérifier la valeur de lr, il me suffit de le copier dans un autre registre inutilisé (par ex r11) avant d'appeler la routine de vidage.
Exercices :  modifier le programme pour enchainer 2 saisies : vérifier le contenu du buffer avec des saisies de différentes longueurs.
                    Afficher la chaine par votre sous routine d’affichage (à quoi faut-il penser ?).

lundi 23 octobre 2017

Chapitre 3 : affichage des zones de la mémoire



Vous avez bien programmé le dernier exercice du chapitre précédant ?  Car le vidage de tous les registres est une aide bien précieuse pour trouver les erreurs d’un programme. Personnellement je trouve cela plus simple que d’utiliser le débogueur GDB. Dans ce chapitre, nous allons voir l’affichage des zones de la mémoire car c’est le deuxième point important pour le débogage ou pour vérifier le contenu des zones après l’appel d’une fonction.
Nous afficherons sur une ligne : l’adresse du début d’un bloc de 16 octets (cad une adresse se terminant par 0), ce bloc contenant l’adresse de la zone demandée. Les 16 octets seront affichés d’abord en hexadécimal puis en ASCII.
Voici un exemple du résultat :

 Voici le  lien pour le source du programme.
La fonction d’affichage , nécessite le passage de l’adresse de la zone et le nombre de bloc de 16 octets à afficher.
Nous allons passer ces paramètres par la pile pour voir le fonctionnement de celle-ci. Mais les arguments pouvaient être passé par les registres r0 et r1.
Donc dans le programme principal, nous passons les 2 paramètres par un push {r0,r1} et nous appelons la routine d’affichage. Dans cette routine, nous sauvegardons les 2 registres fp et lr comme déjà vu puis nous alimentons le registre fp (Frame pointer ou pointeur de contexte) avec l’adresse de la pile (registre sp) plus 8 octets. Ces 8 octets correspondent à la sauvegarde des 2 registres fp et lr. Nous faisons une addition pour retrouver l’adresse de la pile qu’elle avait au début de la procédure. En effet, lors du stockage sur la pile, les adresses font en diminuant (voir la documentation).
Maintenant le registre fp contient l’adresse du premier paramètre passé à la fonction et fp+4octets contient l’adresse du deuxième paramètre car nous avons utilisé un seul push pour passer les 2 registres. Si nous avions effectué 2 push il faut inverser les 2 registres. J’ai mis ces instructions en commentaire mais vous pouvez les remettre pour faire des tests.
Mais d’abord, nous allons préparer l’affichage d’une identification du vidage. Je voulais afficher le N° de ligne du source mais hélas avec l’assembleur as, il n’existe pas d’instruction permettant d’identifier ce N° (dans d’autres assembleurs on trouve par exemple __LINE__). Donc je vais afficher l’instruction d’appel de notre routine et pour la trouver, nous allons nous servir du registre lr qui contient l’adresse de retour cad l’adresse se trouvant juste après l’adresse de l’appel. Donc il suffit d’enlever 4 octets à l’adresse contenue dans lr pour avoir notre résultat puis le convertir en hexa et le placer dans la ligne d’entête.
Nous convertissons aussi en hexa l’adresse demandée et contenue dans le registre fp. Nous plaçons le résultat dans la ligne d’entête puis nous affichons celle çi par la fonction générale d’affichage déjà décrite.
Ensuite nous stockons l’adresse du début du vidage (dans r2) et le nombre de bloc de 16 octets demandés (dans r6) à partir du registre fp et fp+4octets. Puis nous calculons le début d’un bloc sur un multiple de 16 octets et contenant le début du vidage demandé. Pour cela nous divisons par 16 en déplaçant les bits de r2 de 4 bits vers la droite. Puis nous multiplions par 16 en déplaçant 4 bits vers la gauche, ce qui a pour effet de mettre à zéro le dernier chiffre d’ l’adresse de vidage.

/*calculer debut du bloc de 16 octets*/
                mov r1, r2, ASR #4      /* r1 <- (r2/16) */
                mov r1, r1, LSL #4      /* r1 <- (r2*16) */

Pour signaler sur la ligne, l’octet correspondant exactement à l’adresse demandée, nous allons mettre une étoile juste devant. Pour cela nous calculons le déplacement entre l’adresse et le début du bloc précédemment calculé, nous le multiplions par 3.
Cela parait compliqué à comprendre mais revoir le résultat des 2 premières lignes plus haut.
Remarquez l’instruction strb r0,[r7] le b indique que nous ne stockons qu’un octet à l’adresse contenue dans le registre r7.
Maintenant, nous rentrons dans l’exécution de 3 boucles, une pour gérer l’affichage des blocs, une autre interne à la première pour gérer l’affichage des 16 octets en hexa (ce qui représente 2 chiffres) suivie d’une autre pour gérer l’affichage des 16 mêmes octets en Ascii si cela est possible.
Pour l’affichage en hexa, nous utilisons une autre méthode que celle vu précédemment car il n’y a que 2 chiffres à convertir et cela nous permet de manipuler les instructions.
Quand la ligne est complète, nous l 'affichons avec la routine habituelle et nous bouclons sur la préparation du bloc suivant si le nombre de bloc n’est pas atteint. Ah oui ! il faut d’abord effacer l’étoile pour qu’elle n’apparaisse pas sur les lignes suivantes.
En fin de routine, nous restaurons les registres utilisés puis les registres fp et lr et nous revenons au programme principal. La routine fonctionne correctement quand nous l’appelons du programme principal mais se termine par une erreur de segmentation si nous l’appelons d’une autre routine. Soit par le débuggeur soit en insérant la routine d’affichage des registres écrite au chapitre précédant, nous arrivons à la conclusion que l’anomalie n’est pas dans la routine d’affichage mais se produit lors du retour de la routine appelante. Plus exactement, nous nous rendons compte que l’adresse de la pile est différente avant l’appel de la routine affmemoire et après le retour de la routine ce qui entraine une mauvaise restauration du registre lr et une erreur lors du retour au programme appelant. Et là illumination : nous avons stocké sur la pile 2 paramètres que nous n’avons pas dépilés dans la routine. Il faut donc ajouter 8 octets à l’adresse de la pile tout en fin de routine affmemoire (instruction add sp, sp, #8) et tout fonctionne.
Exercice : Ajouter un 3 ième paramètre qui contient l’adresse d’une chaine d’identification. Faire apparaitre cette chaine à la place de l’adresse de l’instruction dans l’entête de l’affichage.
            Vider différents adresses de la Ram : certaines sont inaccessibles. En fait la question est : quelle est la plage d’adresses autorisée à mon programme (à ce jour je n’ai pas la réponse !!).
         Écrire une routine de vidage du registre d’état en indiquant l’état des drapeaux : Exemple :  Carry actif ou Carry inactif etc..
        Ecrire une routine qui vide 10 adresses avant l’adresse courante de la pile et 10 adresses après en hexa. Passer le nombre d’adresse en paramètre.