Chapitre 3. Programmation structurée en assembleur

Table des matières
3.1. La pile
3.2. Les fonctions
3.3. Les macros

3.1. La pile

On utilise la pile pour stocker des données ou des adresses de manière temporaire. Elle peut être vue comme un lieu d'échange dans lequel on dépose et on reprend des éléments. Elle fonctionne sur le mode LIFO[1]. C'est pour cela que tout ce qu'on déposera sur la pile via l'instruction push devra obligatoirement en être retiré dès que possible dans l'ordre inverse à l'ordre de dépôt via l'instruction pop.


	  push eax
	  push ebx
	  push ecx
	  
	  [...]
	  
	  pop ecx
	  pop ebx
	  pop eax
	
Le code ci-dessus vaut si vous avez besoin de retrouver dans les mêmes registres les valeurs sauvegardées sur la pile. Nous aurions très bien pu récupérer la valeur poussée en premier (celle de eax dans ebx en modifiant l'ordre des pop).

Il y a également des fois ou l'on se moque complètement de récupérer les valeurs empilées[2]. Dans ce cas l'utilisation de pop est inutile, et même mauvaise pour les performances. Il faudra donc pouvoir accéder directement à la pile en nous servant de la valeur contenue dans le registre esp.

En fait, lorsque le programme se lance, esp pointe sur la fin du segment principal[3] : le début de la pile pour notre programme. Les opérations faites par push feront descendre le pointeur, et celles effectuées par pop le feront remonter. Il faudra donc toujours penser qu'ajouter quelque chose sur la pile (en fait, en-dessous), décrémentera le pointeur courant esp. Nous venons de voir que l'on se sert habituellement de pop pour rétablir l'état de la pile, mais qu'il existe d'autres moyens. On peut très bien, puisqu'il ne s'agit en fait que de déplacer le pointeur dans la pile, augmenter la valeur de ce pointeur directement.

Lorsque, par exemple, j'empile eax, je diminue la valeur du pointeur de 4 octets (32 bits), et lorsque je fais un pop, je récupère la valeur et le pointeur est incrémenté de 4 octets pour retrouver son emplacement d'origine en haut de la pile. Cette opération étant lourde au niveau des performances[4] on pourra dès que possible vouloir juste faire en sorte que le pointeur retrouve son emplacement :


	  push dword arg2
	  push dword arg1
	  
	  call ma_fonction
	  
	  add sp,8
	
La fonction ma_fonction nécessitant deux arguments et les attendant sur la pile[5], nous les empilons avans l'appel. Au retour de la fonction nous faisons le ménage en incrémentant le pointeur de pile de 8 octets[6].

Cela ne vaut évidemment que lorsque nous appelons une fonction qui respecte les conventions du C au niveau du passage d'arguments[7].

En ce qui concerne la pile, nous en savons bien assez pour aborder les fonctions.

Notes

[1]

« Last In First Out ». Il faut voir ça comme un empilement d'assiettes un peu spécial, par exemple, auquel on ajouterait et retirerait des assiettes par en-dessous. L'exemple des assiettes est un peu trompeur tout de même ; en effet, on peut très bien accéder à n'importe quel élément de la pile en y faisant référence de manière indirecte via esp, comme nous le verrons.

[2]

Dans le cas des appels de fonctions avec passage d'arguments, par exemple.

[3]

Si nous n'étions pas en mode protégé, le segment relatif à la pile serait ss.

[4]

Toute opération concernant la pile est plus lourde que les opérations avec les registres, c'est pourquoi il est préférable lorsque cela est possible de sauvegarder les valeurs dans des registres au lieu de les empiler/dépiler.

[5]

Nous aurions tout aussi bien pu lui passer via deux registres, ce qui aurait d'ailleurs été plus rapide.

[6]

Chaque valeur poussée sur la pile occupant ici 4 octets (dword).

[7]

Il existe deux conventions pour l'empilement/dépilement des arguments passés à une fonction : la convention C (c'est à l'appelant de nettoyer la pile — cela permet une gestion plus aisée des arguments variables), et la convention Pascal (c'est à la fonction appelée de nettoyer la pile).