QCM Pile et appels de fonctions

De Ensiwiki
Aller à : navigation, rechercher

1. L'instruction pushl %eax est équivalente à :

movl %eax, (%esp)
Non, pushl va aussi modifier la valeur de %esp

subl $4, %esp
movl %eax, (%esp)
Oui, c'est la formulation la plus simple

addl $4, %esp
movl %eax, (%esp)
Attention, quand on dessine la pile, on met en général l'adresse 0 en haut, et 0xFFFFFFFF en bas. « Monter » dans la pile signifie donc « descendre » dans les adresses.

movl %eax, (%esp)
subl $4, %esp
Non, la convention est que %esp pointe sur la dernière cas occupée. En faisant ceci, on écraserait la dernière valeur de la pile ...

2. Soit le morceau de code suivant :

.globl main

main:
	pushl %ebp
	movl %esp, %ebp
	subl $8, %esp

	movl $chaine, %ecx
	
	// On n'utilise que %bl, mais on remplit
	// le reste de %ebx avec des 0.
	movl $0, %ebx
while:
	cmpb $0, (%ecx)
	je fin_while
	movb (%ecx), %bl
	pushl %ebx
	pushl $fmt
	call printf
	incl %ecx // équivalent à addl $1, %ecx
	jmp while

fin_while:
	leave
	ret
.data
chaine: .asciz "Hello\n"
fmt:	.asciz "%c"

Les problèmes avec ce programme sont :

On utilise %ecx, et sa valeur est écrasée par call printf
 %ecx est un registre scratch, donc effectivement, printf va écraser sa valeur. On aura probablement un segfault au deuxième tour de boucle.
On utilise %ebx, et sa valeur est écrasée par call printf
 %ebx étant un registre non-scratch, printf n'écrase pas sa valeur.
Il manque un addl $8, %esp après le call printf
En effet, c'est nécessaire pour annuler l'effet des deux push sur le registre %esp
On utilise %ebx, sans faire de sauvegarde/restauration de sa valeur.
Oui, %ebx est un registre non-scratch, si on l'utilise, on doit le sauvegarder (pushl %ebx apres pushl %ebp) et le restaurer (movl -4(%ebp), %ebx avant leave).
Voici une version du programme qui marche :
	.globl main

main:
	pushl %ebp
	movl %esp, %ebp
	// Sauvegarde du registre non-scratch par
	// l'appelé
	pushl %ebx
	// On fait de la place pour :
	// 2 arguments du printf
	// 1 sauvegarde de registres
	subl $(8+4), %esp

	movl $chaine, %ecx
	
	// On n'utilise que %bl, mais on remplit
	// le reste de %ebx avec des 0.
	movl $0, %ebx
while:
	cmpb $0, (%ecx)
	je fin_while
	movb (%ecx), %bl
	// On empile les arguments de printf
	// Sauvegarde de %ecx
	pushl %ecx
	pushl %ebx
	pushl $fmt
	call printf
        // Dépilage des arguments
        addl $8, %esp
	// Restauration de %ecx
	popl %ecx
	incl %ecx // équivalent à addl $1, %ecx
	jmp while

fin_while:
	// Restauration de registres non-scratch.
	popl %ebx
	leave
	ret
.data
chaine: .asciz "Hello\n"
fmt:	.asciz "%c"

3. Dans une fonction f(int x, int y), pour traduire x = y, on peut écrire :

movl 8(%ebp), %eax
movl 12(%ebp), %ecx
movl %ecx, %eax
Non, cette version est grossièrement fausse : on n'écrit que dans des registres, et jamais dans la mémoire. D'une manière générale, on peut voir en un coup d'œil qu'il y a quelque chose de louche en remarquant que tous les opérandes droits sont des registres.

movl 12(%ebp), %eax
movl %eax, 8(%ebp)
Oui, c'est la manière standard.

movl 12(%ebp), 8(%ebp)
 
C'est tentant d'écrire cela, mais l'assembleur Pentium n'accepte pas d'avoir deux modes d'adressages registre/mémoire dans une seule instruction. Cette version est refusée par l'assembleur.

Votre pointage est 0 / 0