lundi 28 mai 2018

Chapitre 10A : appels de fonctions


(remarque : ce chapitre est publié en retard car je l'avais oublié).
Après ces premiers chapitres, nous allons revenir sur les appels de fonctions en C et les appels système Linux pour vérifier le passage des paramètres.
Tout d’abord, nous allons essayer la fonction printf en lui passant 5 paramètres dans les registres r0 à r4 dans un petit programme :

       

/* passage de parametres  avec la fonction printf du C */
/* problème avec plus de 4 paramètres */
.data
szMessage1: .asciz "parametre 1:%d 2:%d 3:%d 4:%d\n "
.text             /* -- Code section */
.global main      /* point d'entrée du programme  */
main:             /* Programme principal */
    ldr r0, =szMessage1      /* adresse du message */
    mov r1,#1                /* paramétres */
    mov r2,#2
    mov r3,#3
    mov r4,#4
    bl printf          /* appel de la fonction du C */
    mov r0,#0  /* code retour r0 */
    mov r7, #1 /* code pour la fonction systeme EXIT */
    swi 0      /* appel system */
 
 


 r0 contient l’adresse du message et r1 à r4 les valeurs 1 à 4.
 Voici le résultat :
parametre 1:1 2:2 3:3 4:-1225867264
Nous remarquons que la 4ième valeur ne correspond plus à la valeur en entrée. La documentation du C explique que seuls les 4 premiers registres sont utilisés pour passer les 4 premiers paramètres. Pour les autres, il est indiqué de les passer par la pile.  Nous allons donc modifier ce programme pour vérifier le passage des paramètres par la pile. Nous en profitons aussi pour mettre des valeurs dans tous les autres registres (sauf bien sur sp,cp et lr) pour vérifier leur état après l’appel et nous allons aussi vérifier l’état de la pile.
       


/* programme  affichage avec la fonction printf du C */
/* pour verification ordre parametre par push */
/* et état de la pile au retour  */
.data
szMessage1: .asciz "parametre 1:%d 2:%d 3:%d 4:%d 5:%d \n "
.text             /* -- Code section */
.global main      /* point d'entrée du programme  */
main:             /* Programme principal */
    bl vidtousregistres
 mov r5,#5   /* pour verification des registres */
 mov r6,#6
 mov r7,#7
 mov r8,#8
 mov r9,#9
 mov r10,#10
 mov r11,#11
 mov r12,#12
    ldr r0, =szMessage1      
    mov r1,#1   @ param1
 mov r2,#2    @ param2
 mov r3,#3     @ param3
 mov r4,#5    
 push {r4}     @ param5
 mov r4,#4
 push {r4}     @ param4
    bl printf          /* appel de la fonction du C */
 //add sp,#8       /* alignement de la pile */
 bl vidtousregistres
    mov r0,#0  /* code retour r0 */
 mov r7, #1 /* code pour la fonction systeme EXIT */
    swi #0      /* appel system */

 
Voici le résultat :

Nous remarquons que l’affichage des 5 paramètres est cette fois correct et si vous regardez bien le programme, vous remarquerez que le paramètre 5 a été mis sur la pile avant le paramètre 4 : il ne faudra jamais l’oublier !!. Il est aussi conseillé de passer les paramètres en nombre pair car l’adresse de la pile doit toujours être un multiple de 8 octets (ou alors d’ajouter une instruction sub sp,#4 avant le passage du ou des paramètres en nombre impairs).
Comme indiqué, après l’appel, les valeurs des registre r0 à r3 ont été modifiées, les registres R4 à r11 sont bien conservés, le registre 12 a été modifié ( ce qui est contradictoire avec certaines documentations) et plus embêtant, l’adresse de la pile n’est plus à sa valeur d’origine. En effet, c’est au programme appelant à rétablir l’adresse de la pile après l’appel en fonction du nombre de paramètres passés sur la pile : ici il faut ajouter l’instruction add sp,#8 pour remettre la pile en l’état (8 octets car 2 paramètres de 32 bits). Ceci n’est jamais à oublier lors de l’appel à des fonctions à des bibliothèques extérieures. Pour vos propres fonctions, vous pouvez faire comme vous voulez !!!
Maintenant effectuons, la même démarche pour les call système Linux mais cela est moins facile de tester plus de 4 paramètres avec l’appel Write. J’ai donc cherché dans la documentation Linux et j’ai trouvé que les call système ne nécessitait au maximum que 7 paramètres et qu’ils étaient tous passés par les registres r0 à r6 (ce qui explique entre autre que le code fonction est passée dans le registre r7). Nous allons quand même vérifier l’état des registres et l’état de la pile après un appel à la fonction WRITE. Voici le source du programme param3.s.
       


/* programme hello  avec l'appel systeme Write de Linux */
/*  */
/********************************/
/*  Données initialisées        */
/********************************/
.data
szMessage1: .asciz "Bonjour le Monde.\n"
.equ LGMESSAGE1, . -  szMessage1 /* calcul de la longueur de la zone precedente */
/********************************/
/*  Code section                */
/********************************/
.text    
.global main      /* point d'entrée du programme  */
main:             /* Programme principal */
    mov r3,#3  /* pour verification des registres */
 mov r4,#4
 mov r5,#5  
 mov r6,#6
 mov r8,#8
 mov r9,#9
 mov r10,#10
 mov r11,#11
 mov r12,#12
    mov r0,#1      /* code pour écrire sur la sortie standard Linux */
    ldr r1, =szMessage1      /* adresse du message en r1 */
    mov r2,#LGMESSAGE1       /* longueur du message */
    mov r7, #4                  /* code de l'appel systeme 'write' */
 bl vidtousregistres
    swi #0                      /* appel systeme */
 
 bl vidtousregistres
 
 /* FIN programme */
    mov r0,#0  /* code retour r0 */
 mov r7, #1 /* code pour la fonction systeme EXIT */
    swi 0      /* appel system */
  
 

 et le résultat :


Nous pouvons vérifier que les registres r3 à r12 ne sont pas modifiés et que l’adresse de la pile reste inchangée dans ce cas. Vous remarquerez que le registre r0 contient 13 en hexa ce qui est le nombre de caractères affichés par la fonction.

Aucun commentaire:

Enregistrer un commentaire