Langage d'assemblage - Procédures (Sous-programmes)

Une procédure est un mécanisme du langage assembleur permettant de mettre en œuvre la réutilisation de fonctions/sous-routines, rendant le code structuré et réutilisable. Comprendre la relation entre les procédures et la pile est une étape importante de la programmation en assembleur.


Qu'est-ce qu'une procédure

Une procédureest un bloc de code qui peut être appelé plusieurs fois, semblable aux fonctions ou sous-routines des langages de haut niveau.

En assembleur, une procédure est appelée viaCALLl'instruction CALL, et retourne viaRETl'instruction RET.CALLElle empile l'adresse de retour sur la pile,RETet elle dépile l'adresse de retour de la pile et saute en retour.

Exemple

; Chemin du fichier : simple_proc.asm
; Exemple le plus simple d'un appel de procédure

section .data
    msg db 'Hello, example!', 0xA
    len equ $ - msg

section .text
    global _start

_start:
    call print_message           ; Appeler la procédure (empiler l'adresse de l'instruction suivante sur la pile)
    call print_message           ; Appeler à nouveau

    mov eax, 1
    mov ebx, 0
    int 0x80

; Procédure : afficher le message
print_message:
    push eax                     ; Sauvegarder les registres à modifier
    push ebx
    push ecx
    push edx

    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80

    pop edx                      ; Restaurer les registres (dans l'ordre inverse)
    pop ecx
    pop ebx
    pop eax
    ret                          ; Revenir à l'appelant (dépiler l'adresse de retour de la pile)

À l'intérieur d'une procédure, vous devez sauvegarder et restaurer tous les registres modifiés (sauf EAX lorsqu'il contient la valeur de retour). C'est une bonne pratique élémentaire en programmation assembleur. Si vous ne le faites pas, les valeurs des registres de l'appelant seront modifiées de façon inattendue.


Principes de CALL et RET

CALLetRETIls fonctionnent par paire et s'appuient sur la pile pour gérer les adresses de retour :

InstructionOpération réellement effectuée
CALL labelpush eip(Enregistrer l'adresse de l'instruction suivante) +jmp label
RETpop eip(Dépiler l'adresse de la pile et sauter)

Passage de paramètres via les registres

Avant d'appeler une procédure, placez les paramètres dans les registres convenus :

Exemple

; Chemin du fichier : proc_params_reg.asm
; Passage de paramètres via les registres

section .data
    newline db 0xA

section .text
    global _start

_start:
    ; Appeler la procédure add_two : calculer 10 + 20
    mov eax, 10                  ; Placer le premier paramètre dans eax
    mov ebx, 20                  ; Placer le deuxième paramètre dans ebx
    call add_two                 ; Appeler la procédure
    ; La valeur de retour est dans eax = 30

    mov ebx, eax                 ; Code de sortie = 30
    mov eax, 1
    int 0x80

; Procédure : calculer eax + ebx, renvoyer le résultat dans eax
add_two:
    add eax, ebx                 ; eax = eax + ebx
    ret

Passage de paramètres via la pile

Le passage de paramètres via la pile est plus flexible et constitue la méthode standard pour les langages de haut niveau tels que C :

Exemple

; Chemin du fichier : proc_params_stack.asm
; Passage de paramètres via la pile (style cdecl)

section .data
    msg db 'La somme est : '
    msg_len equ $ - msg
    newline db 0xA

section .bss
    result_buf resb 4

section .text
    global _start

_start:
    ; Appeler la procédure de somme : calculer 100 + 200
    push dword 200               ; Empiler le deuxième paramètre
    push dword 100               ; Empiler le premier paramètre
    call sum                     ; Appeler la procédure
    add esp, 8                   ; ★ L'appelant nettoie la pile (convention cdecl)
    ; La valeur de retour est dans eax = 300

    mov ebx, eax
    mov eax, 1
    int 0x80

; Procédure : sum(a, b) = a + b
sum:
    push ebp                     ; ★ Sauvegarder l'ancien pointeur de base
    mov ebp, esp                 ; ★ Initialiser la nouvelle base du cadre de pile

    ; Organisation de la pile (de l'adresse haute à l'adresse basse) :
    ; [ebp+12] = paramètre b (200)
    ; [ebp+8] = paramètre a (100)
    ; [ebp+4] = adresse de retour
    ; [ebp] = ancien ebp
    ; [ebp-4] = espace de variables locales

    mov eax, [ebp + 8]           ; Récupérer le premier paramètre (a)
    add eax, [ebp + 12]          ; Ajouter le deuxième paramètre (b)

    pop ebp                      ; ★ Restaurer l'ancien pointeur de base
    ret                          ; Retour

Schéma de la structure du cadre de pile :

栈帧结构图

Voici l'illustration textuelle correspondante :

高地址
+------------------+
| 参数2 (200)      |  <-- [ebp + 12]
+------------------+
| 参数1 (100)      |  <-- [ebp + 8]
+------------------+
| 返回地址          |  <-- [ebp + 4]
+------------------+
| 旧的 EBP         |  <-- [ebp]  (当前 EBP 指向这里)
+------------------+
| 局部变量区        |  <-- [ebp - 4], [ebp - 8], ...
+------------------+  <-- ESP 指向这里
低地址

Prologue de fonctionpush ebp; mov ebp, espet Épiloguepop ebp; ret(ouleave; ret) est le modèle standard de gestion des cadres de pile ; presque toutes les fonctions en assembleur commencent et se terminent par cette structure.


Valeurs de retour des procédures

Les valeurs de retour sont généralement stockées dans le registre EAX (valeur 32 bits) ou EDX:EAX (valeur 64 bits) :

Exemple

; Exemple de valeur de retour

    ; Retourne un int
    call get_answer
    ; eax = 42

    ; Retourne une valeur 64 bits (par ex., long long)
    call get_big_value
    ; edx:eax = résultat 64 bits

get_answer:
    mov eax, 42                  ; Valeur de retour placée dans eax
    ret

get_big_value:
    mov eax, 0x00000001          ; 32 bits de poids faible
    mov edx, 0x00000000          ; 32 bits de poids fort
    ret

Variables locales

Les variables locales utilisent l'espace de pile et sont allouées en décrémentant ESP dans le prologue de la procédure :

Exemple

; Chemin du fichier : local_vars.asm
; Utilisation de variables locales dans une procédure

section .text
    global _start

_start:
    push dword 10
    push dword 20
    call multiply_add
    add esp, 8
    ; eax = 10 * 20 + 10 + 20 = 230

    mov ebx, eax
    mov eax, 1
    int 0x80

; Procédure : multiply_add(x, y) = x*y + x + y
multiply_add:
    push ebp
    mov ebp, esp
    sub esp, 8                   ; ★ Allouer 8 octets d'espace de variable locale sur la pile

    ; Maintenant [ebp-4] et [ebp-8] sont des variables locales disponibles

    mov eax, [ebp + 8]           ; x
    mov ebx, [ebp + 12]          ; y

    ; [ebp-4] = x * y
    imul eax, ebx
    mov [ebp - 4], eax           ; Variable locale 1 = x * y

    ; [ebp-8] = x + y
    mov eax, [ebp + 8]
    add eax, [ebp + 12]
    mov [ebp - 8], eax           ; Variable locale 2 = x + y

    ; Valeur de retour = [ebp-4] + [ebp-8]
    mov eax, [ebp - 4]
    add eax, [ebp - 8]

    ; ★ Nettoyer l'espace des variables locales et restaurer
    mov esp, ebp                  ; Équivalent à add esp, 8
    pop ebp
    ret

Les variables locales sont allouées dans le cadre de pile et automatiquement libérées au retour de la procédure. En utilisantleavel'instruction peut remplacermov esp, ebp; pop ebp, avec un effet équivalent.


Comparaison des conventions d'appel de procédure

ConventionPassage de paramètresNettoyage de la pilePréservation des registresValeur de retour
cdeclPile (empilée de droite à gauche)AppelantEAX, ECX, EDX sont sauvegardés par l'appelantEAX
stdcallPile (empilée de droite à gauche)AppeléEAX, ECX, EDX sont sauvegardés par l'appelantEAX
fastcallECX, EDX + pileAppeléEAX, ECX, EDX sont sauvegardés par l'appelantEAX
Autres extensions