J'apprends la programmation avec l'assembleur x86, la convention d'appel en C
Juste un petit rappel sur la façon dont le C/C++ appelle une fonction une fois traduite en assembleur.
On prend pour exemple un assembleur x86 de 16bits.
La convention pour le C est la suivante :
- on utilise la pile pour passer les paramètres
- les registres ax, cx et dx peuvent être modifiés par la fonction, à nous de les sauvegarder avant l'appel si nécessaire
- si une valeur entière est retournée par la fonction, elle sera dans ax
- tous les autres registres doivent être préservés : donc dans la fonction, si ils sont modifiés, il faut les restaurer avant de quitter la fonction
Je suis trop des piles...
Lors de l'appel de la fonction, notre pile aura cette tronche (avec chaque case qui correspond à 2 bytes) :
| adresse de retour | ← sp |
| 2 | |
| 'A' | |
| ... |
Puis on push bp pour le sauvegarder, et on l'initialise avec la valeur courante de sp :
mov bp, sp ; bp correspond au somment de la pile avant l'ajout des variables locales
| ancien bp | ← sp / bp |
| adresse de retour | |
| 2 | |
| 'A' | |
| ... |
On réserve la place pour la variable locale :
sub sp, 2 ; place réservée pour une variable locale 16bits
| variable locale | ← sp |
| ancien bp | ← bp |
| adresse de retour | |
| 2 | |
| 'A' | |
| ... |
Reste plus qu'à exploiter tour cela.
On arrive pile à lire...
Si on prend comme référence bp, on a maintenant :
| variable locale | ← bp - 2 |
| ancien bp | ← bp |
| adresse de retour | ← bp + 2 |
| 2 | ← bp + 4 |
| 'A' | ← bp + 6 |
Ainsi pour atteindre la valeur du second paramètre, on va lire la pile à la position bp + 4 :
mov ax, [bp+4] ; ax prend la valeur du second paramètre : bp + 4
Et pour accéder à la variable locale :
mov [bp-2], ax ; on met ax dans la variable locale
Dans notre exemple, la fonction calling_convention_c prend deux entiers comme paramètre :
- une code ASCII
- une valeur de répétition
Cette fonction, qui n'est pas la plus utile du monde, retourne dans ax le code ASCII passé en paramètre augmenté de deux fois la valeur de répétition.
Pour le plaisir d'apprendre©, on stockera la valeur de deux fois la répétition dans un variable locale, elle aussi allouée dans la pile.
Le code source de callingc.asm
; ========
; callingc
; ========
;
; C:\nasm -fbin callingc.asm -o callingc.com
org 0x100
global main
section .text
main:
push 'A' ; second paramètre
push 2 ; premier paramètre
call calling_convention_c
add sp,4 ; on supprime les paramètres de la pile
mov ah, 0x02
mov dl, al
int 0x21
mov ax, 0x4c00
int 0x21
; - on assume par convention que seuls ax, cx et dx
; peuvent être modifiés par la fonction
; - ax contient la valeur de retour si c'est un entier
calling_convention_c:
push bp ; bp ne doit pas être modifié par la fonction : on le sauve
mov bp, sp ; on sauvegarde notre position de pile
sub sp, 2 ; place réservée pour une variable locale 16bits
mov ax, [bp+4] ; premier paramètre dans ax qui peut être modifie par la fonction
shl ax, 1
mov [bp-2], ax ; deux fois le paramètre 1 dans notre variable locale
mov ax, [bp+6] ; second paramètre
mov cx, 1 ; cx peut etre modifié par la fonction
loop:
inc ax ; resultat entier dans ax
inc cx
cmp cx, [bp-2] ; pour le plaisir d'utiliser la variable locale
jbe loop
mov sp, bp ; on restaure notre position de pile : la variable locale est donc supprimée
pop bp
ret
Après exécution
C:\ callingc
E