TP3: Exemples de code manipulant la pile

Notation Polonaise Inverse

# calcul de a + (b - c) mrmovl a,%eax pushl %eax # empiler a mrmovl b,%eax pushl %eax # empiler b mrmovl c,%eax pushl %eax # empiler c popl %eax # récupérer c popl %ebx # récupérer b subl %eax,%ebx # calculer b-c pushl %ebx # stocker le résultat popl %eax # récupérer b-c popl %ebx # récupérer a addl %eax,%ebx # calculer a + (b-c) pushl %ebx # socker le résultat

Exemple de calcul d'expression en utilisant seulement deux registres, donc obligé d'utiliser la pile à la mode Polonaise

# a = ((((b^c)&(d^e))+((f^g)&(h^i))) irmovl Stack,%esp mrmovl b,%eax mrmovl c,%ebx xorl %eax,%ebx pushl %ebx mrmovl d,%eax mrmovl e,%ebx xorl %ebx,%eax popl %ebx andl %eax,%ebx pushl %ebx mrmovl f,%eax mrmovl g,%ebx xorl %eax,%ebx pushl %ebx mrmovl h,%eax mrmovl i,%ebx xorl %eax,%ebx popl %eax andl %eax,%ebx popl %eax addl %eax,%ebx rmmovl %ebx,a halt .pos 0x100 a: .long 0 b: ... i: .long 3 .pos 0x200 Stack:

Fonction simple

# main(void) { # res = sub(5,7); # } main: irmovl Stack,%esp # Initialiser esp irmovl 7,%eax # deuxième paramètre pushl %eax irmovl 5,%eax # premier paramètre pushl %eax call sub # valeur de retour dans eax popl %ecx # enlever les paramètres popl %ecx # de la pile rmmovl %eax,res halt # long sub(long i, long j) { # return i-j; # } sum: mrmovl 4(%esp),%eax # premier paramètre mrmovl 8(%esp),%ecx # deuxième paramètre subl %ecx,%eax # résultat dans eax ret .pos 0x100 res: .long 0 .pos 0x200 Stack:

Nota: le pointeur de pile esp démarre en 0x200 et est décrémenté, donc progresse vers 0x100, i.e. vers nos variables.

Fonction avec variable locale

# long sub(long i, long j) { # long x; # x = i-j; # ... # return x; # } sub: subl 4,%esp # réserver de la place pour la variable locale x: -4(%ebp) mrmovl 8(%esp),%eax # premier paramètre mrmovl 12(%esp),%ecx # deuxième paramètre subl %ecx,%eax rmmovl %eax,(%esp) # stocker dans la variable locale x ... mrmovl (%esp),%eax # récupérer x comme valeur de retour de sub ret

Ici, entre le calcul de i+j et le retour de la fonction, la fonction fait d'autres choses (...), on est donc obligé de stocker le résultat dans une variable locale.

Au lieu de décrémenter esp de 4 octets, on aurait aussi utiliser un pushl.

Appel de fonction

# long g(long i) { # return f(i,1) + f(i,2); # } g: pushl %ebx # sauver ebx pour pouvoir l'utiliser irmovl 1,%eax # deuxième paramètre 1 pushl %eax mrmovl 12(%esp),%eax # premier paramètre i pushl %eax call f # appel à f, attention eax est écrasé par le résultat rrmovl %eax,%ebx # sauver le résultat irmovl 2,%eax # deuxième paramètre 2 rmmovl %eax,4(%esp) mrmovl 16(%esp),%eax # premier paramètre i rmmovl %eax,(%esp) call f addl %ebx,%eax # additionner les résultats subl 8,%esp # nettoyer la pile popl %ebx # restaurer ebx ret

On effectue ici deux appels à la fonction f. On peut éviter de faire des pops puis push entre les deux appels en écrivant directement en 4(%esp) et (%esp) puisqu'on sait déjà que l'on a réservé 8 octets.

Appel de fonction, pointeurs, tableaux

# long g(long n) { # return n&1; # } # # long f(long *t) { # long *p; # for (p = t; *p; p++) # *p = g(*p); # } main: irmovl Stack,%esp irmovl tab,%eax pushl %eax # empiler le paramètre tab call f halt g: mrmovl 4(%esp),%eax iandl 1,%eax # n & 1 ret f: pushl %esi mrmovl 8(%esp), %esi # %esi: p = t loop: mrmovl (%esi),%ecx # %ecx: *p andl %ecx,%ecx # 0 ? je fin pushl %ecx # argument pour g call g popl %ecx # poubelle rmmovl %eax,(%esi) # *p = résultat de g iaddl 4,%esi # p++ jmp loop fin: popl %esi ret .pos 0x100 tab: .long 3 .long 5 .long 2 .long 6 .long 0 .pos 0x200 Stack:

Note 1: on garde dans le registre esi le pointeur p. On aurait aussi pu utiliser une variable locale sur la pile. L'avantage d'utiliser esi, c'est que c'est un registre callee-saved, et que donc g ne le sauvegardera que s'il a vraiment besoin de le faire, ce n'est d'ailleurs pas le cas ici. On évite ainsi d'utiliser des transferts mémoire. Il faut tout de même le sauvegarder/restaurer en début/fin de fonction (justement parce que c'est un registre callee-saved).

Note 2: au lieu d'effectuer des opérations pushl / popl à chaque itération de boucle pour donner l'argument à g, on pourrait décrémenter esp de 4 avant la boucle, et écrire simplement en (%esp) le paramètre à donner à g.

Appel de fonction, pointeurs, tableaux

# long max(long n, ...); max: irmovl 0x7fffffff,%eax # %eax: max mrmovl 4(%esp),%ecx # %ecx: n andl %ecx,%ecx jne doit # si pas 0, on doit vraiment calculer ret doit: pushl %ebx # sauvegardes pushl %esi pushl %edi irmovl 20,%edx # %edx: pointeur addl %esp,%edx # sur les parametres à partir du 2e loop: mrmovl (%edx), %esi # lire un paramètre rrmovl %esi, %edi subl %eax, %edi # comparer à max: %edi-max? jg notmax # résultat soustraction positif rrmovl %esi, %eax # nouveau max notmax: addl 4, %edx # paramètre suivant subl 1, %ecx # n-- jne loop popl %edi # restaurations popl %esi popl %ebx fin: ret