codefordumm!ies: the cool logo

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 :

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 :

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