mercredi 4 avril 2018

Chapitre 24 : et les macros ?


Une macro permet d’inclure dans un programme assembleur, une série d’instructions qui seront compilées en même temps que le programme (donc différent d’un objet qui est inclus au moment du link).
Les macros évitent de retaper plusieurs fois les mêmes instructions et il est possible de leur passer des paramètres.
Par exemple dans les premiers chapitres nous avons écrit une routine pour afficher un message dans la console, routine que nous avons inclus dans un autre programme contenant toutes les routines. Mais nous aurions pu écrire cette fonction sous forme de macro :

.macro  afficheMessage          @ définition de la macro
@ r0 contient l’adresse du message et il faut d’abord calculer sa longueu
              mov r1,r0   @ save adresse
    mov r2,#0   @ compteur de caractère
    1\@:         @ boucle de comptage de caractères
    ldrb r3,[r1,r2]   @ lecture un octet du message
    cmp r3,#0          @ c'est la fin ?
    addne r2,#1       @ non ajout de 1 au compteur
    bne 1\@b        @ et boucle
                @ appel systeme fonction affichage
    mov r0, #1                  @ r0 ← 1
    mov r7, #4                  @ write code 4  
    swi #0    
.endm   @ fin de la macro

L’appel de la macro dans le programme s’effectue simplement par le nom : afficheMessage
Les instructions utilisées sont les mêmes que l’assembleur sauf le label 1\@ qui est particulier. En effet une macro peut être insérée plusieurs fois et si nous avions mis un label comme 1 : ou Boucle1 : , la compilation aurait produit une erreur car elle aurait trouvé plusieurs labels identiques. Vous remarquerez que nous utilisons des registres sans les sauvegarder donc il faudra faire attention et les sauvegarder si nécessaire
Nous pouvons passer un ou plusieurs paramètres à une macro : dans la macro comparVal nous passons 2 adresses de message et une valeur à tester dont la valeur par défaut sera 0 (en respectant la syntaxe #0). L’utilisation de ces données dans le corps de la macro s’effectue en mettant un \ devant le nom. Nous pouvons appeler une autre macro dans la macro, ici en fonction du résultat de la comparaison le message affiché sera différent.
L’appel de la macro s’effectue simplement par comparVal r1, r2, #4 , Les registres r1 et r2 contiendront les 2 adresses nécessaires et la valeur de test sera passée comme une valeur immédiate.
Remarque : les macros ne sont pas à utiliser pour des actions trop compliquées !! Mais si vous aimez mes macros, vous pouvez vous constituer une collection et les grouper dans un fichier externe que vous appellerez avec l’instruction .include « nomdufichier ».
Voici un autre exemple : pour l’affichage des registres que j’utilisais depuis le début, j’affichais comme libellé l’adresse de l’instruction appelante car il n’est pas possible avec cet assembleur de récupérer le N° de ligne du source ce qui aurait été bien pratique. Avec une macro je peux donc récupérer un libellé comme paramètre, le stocker sur la pile pour l’afficher sans modifier les valeurs des registres. Bon il faut un peu bidouiller avec un registre puisque le push exige l’utilisation d’un registre. Voici le corps de la macro :
/* macro d'enrobage du vidage des registres  avec étiquette */
.macro vidregtit str
    push {r12}    @ save r12
                mrs r12,cpsr  /* copie du registre d'état  dans r12 */
                push {r12}   @ save du registre d’état sur la pile
                ldr r12,=lib1\@  @ recup libellé passé dans str
                push {r12}    @ passage argument sur la pile
                ldr r12,[sp,#8]   @ on remet en état r12 pour l'afficher correctement
                bl affregistres  @ affichage des registres  (cette routine restaure l’état de la pile)
                pop {r12}     @ on récupére le save du registre d’état
                msr cpsr,r12    /*restaur registre d'état */
                pop {r12}         @ on restaure R12 pour avoir une pile réalignée
                b smacro1vidregtit\@   @ pour sauter le stockage de la chaine.
lib1\@:  .asciz "\str"            @ stockage dans le corps du programme du libellé passé en paramètre
.align 4                       @ il faut aligner l’étiquette suivante car le libellé précedent n’est pas un multiple de 4
smacro1vidregtit\@:     @ pour continuer le reste du programme
.endm   @ fin de la macro
J’en ai aussi profité pour sauvegarder le registre d’état avant l’appel de la routine de vidage et le restauré à la fin pour que cette routine reste neutre.
L’appel se fait simplement par vidregtit  debut   ou vidregtit  Après_appel_compar_3
Et le résultat est le suivant :


Vous remarquerez que le registre r2 contient 0xD alors que l’on s’attendrait à trouver une adresse comme dans r1.
Et d’ailleurs r0 contient aussi 0xD !!  Donc il faut remonter les appels pour s’apercevoir que ComparVal utilise en dernier afficheMessage qui utilise r2 pour calculer la longueur du dernier message  donc ici 12 caractères (ne pas oublier de compter le \n final !!!). Et l’appel système Write (code 4) retourne dans le registre r0 la longueur du message affiché (revoir la doc des call system Linux). Donc c’est logique !!!

Remarque avril 2018: en utilisant cette macro sur un gros programme, le compilateur signale une erreur sur l'instruction  ldr r12,=lib1\@  @ recup libellé passé dans str . En effet dans un gros programme l'instruction de chargement d'une adresse avec le signe égal ne fonctionne pas. J'ai eu du mal à comprendre pourquoi puisque l'adresse du libellé ne se trouve que quelques instructions plus loin dans la même section. En fait l'instruction ldr r12,=lib1 est une pseudo instruction qui crée en fin du code programme, une variable supplémentaire contenant l'adresse de lib1. Et cette variable pour un gros programme peut se trouver au delà des 4K mots fatidiques. Donc j'ai remplacé cette instruction par l'autre pseudo instruction : adr r12,lib1   (et sans le =) qui fonctionne dans tous les cas. Cette instruction signifie de charger dans r12 l'adresse du libellé lib1.

Aucun commentaire:

Enregistrer un commentaire