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 :
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.