(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 */
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 */