Langage d'assemblage - Gestion de la mémoire

La gestion de la mémoire est une partie importante de la programmation système. En langage d'assemblage, vous pouvez allouer et libérer dynamiquement de la mémoire via des appels système, manipuler directement les adresses mémoire et réaliser une gestion efficace de la mémoire.


Disposition de la mémoire d'un programme

Un programme en cours d'exécution est réparti en mémoire comme suit :

程序内存布局图

La disposition de la mémoire d'un processus Linux est la suivante :

高地址 (0xFFFFFFFF)
+----------------------+
|    内核空间           |  用户程序不可访问
+----------------------+  (0xC0000000)
|    栈 (Stack)        |  <-- ESP 指向栈顶
|    向下增长           |
+----------------------+
|                      |
|    内存映射区域        |  mmap 分配的区域
|                      |
+----------------------+
|    堆 (Heap)         |  <-- brk/sbrk 管理
|    向上增长           |
+----------------------+
|    BSS 段 (.bss)     |  未初始化全局变量
+----------------------+
|    数据段 (.data)     |  已初始化全局变量
+----------------------+
|    代码段 (.text)     |  程序指令(只读)
+----------------------+
低地址 (0x08048000)

Allocation dynamique de mémoire : appel système brk

sys_brk(Numéro d'appel système 45) en ajustant lalimite du segment de données du programme (program break)pour allouer/libérer de la mémoire.

Le program break est la position de fin du segment de données (y compris .data, .bss et le tas). Le programme ne peut pas accéder à la mémoire située au-dessus.

Méthode d'appelDescriptionValeur de retour
ebx = 0Obtenir l'adresse actuelle du program breakEAX = adresse de break actuelle
ebx = 新地址Définir une nouvelle adresse de breakEAX = nouvelle adresse de break (renvoie l'adresse d'origine en cas d'échec)

Exemple

; Chemin du fichier : brk_demo.asm
; Utiliser sys_brk pour allouer dynamiquement de la mémoire

section .data
    alloc_msg db 'Mémoire allouée avec succès à : 0x'
    alloc_len equ $ - alloc_msg
    newline db 0xA

section .bss
    orig_break resd 1            ; Enregistrer l'adresse de break d'origine

section .text
    global _start

_start:
    ; 1. Obtenir le break actuel du programme
    mov eax, 45                  ; sys_brk
    mov ebx, 0                   ; ebx=0 signifie interroger le break actuel
    int 0x80
    mov [orig_break], eax        ; Sauvegarder l'adresse de break d'origine

    ; 2. Allouer 4096 octets (4KB) de mémoire
    mov ebx, eax                 ; Adresse actuelle du break
    add ebx, 4096                ; Augmenter de 4096 octets
    mov eax, 45                  ; sys_brk
    int 0x80
    ; EAX = nouvelle adresse de break (c'est-à-dire la fin de la région allouée)

    ; 3. La mémoire nouvellement allouée se situe entre orig_break et eax-1
    ; Vous pouvez lire et écrire dans cette mémoire en toute sécurité
    mov ebx, [orig_break]        ; Obtenir l'adresse de début de la région allouée
    mov dword [ebx], 42          ; Écrire 42 dans la nouvelle mémoire
    mov eax, [ebx]               ; Relire cette valeur

    ; 4. Libérer la mémoire : restaurer le break à sa valeur d'origine
    mov eax, 45                  ; sys_brk
    mov ebx, [orig_break]        ; Restaurer au break d'origine
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

sys_brk ne peut ajuster que la limite du segment de données continu et ne peut pas libérer de mémoire au milieu. Pour libérer un bloc de mémoire allouée, vous devez d'abord libérer toute la mémoire allouée après lui. C'est pourquoi les programmes modernes utilisent plus souvent mmap ou les fonctions de bibliothèque malloc.


Utilisation de mmap pour allouer de la mémoire (recommandé)

sys_mmap2(Le numéro d'appel système 192) est plus flexible ; il peut allouer et libérer plusieurs blocs de mémoire de manière indépendante :

Exemple

; Chemin du fichier : mmap_demo.asm
; Utiliser mmap pour allouer de la mémoire pouvant être libérée indépendamment

section .data
    ; Constantes liées à mmap
    PROT_READ equ 1
    PROT_WRITE equ 2
    MAP_PRIVATE equ 2
    MAP_ANONYMOUS equ 0x20

section .bss
    mem_block resd 1             ; Sauvegarder l'adresse mémoire allouée

section .text
    global _start

_start:
    ; Appel mmap : allouer 4096 octets de mémoire anonyme
    mov eax, 192                 ; sys_mmap2
    mov ebx, 0                   ; Laisser le noyau choisir l'adresse
    mov ecx, 4096                ; Taille d'allocation : 4KB
    mov edx, PROT_READ | PROT_WRITE  ; Lisible et inscriptible
    mov esi, MAP_PRIVATE | MAP_ANONYMOUS  ; Mappage anonyme privé
    mov edi, -1                  ; Descripteur de fichier (utiliser -1 pour un mappage anonyme)
    mov ebp, 0                   ; Décalage (utiliser 0 pour un mappage anonyme)
    int 0x80
    ; EAX = adresse mémoire allouée (renvoie une valeur négative en cas d'échec)

    cmp eax, -4096               ; Vérifier si cela a échoué
    ja  mmap_failed              ; Si la valeur est entre -4095 et -1, l'appel a échoué

    mov [mem_block], eax         ; Enregistrer l'adresse mémoire

    ; Utiliser la mémoire allouée : écrire des données
    mov ebx, [mem_block]
    mov dword [ebx], 0x12345678  ; Écrire 4 octets
    mov dword [ebx + 4], 'runo'  ; Écrire "runo"
    mov dword [ebx + 8], 'ob!!'  ; Écrire "ob!!"

    ; Libérer la mémoire : munmap
    mov eax, 91                  ; sys_munmap
    mov ebx, [mem_block]         ; Adresse mémoire
    mov ecx, 4096                ; Taille à libérer
    int 0x80

    jmp exit

mmap_failed:
    ; Gérer les erreurs...

exit:
    mov eax, 1
    mov ebx, 0
    int 0x80

Opérations de lecture/écriture en mémoire

En langage assembleur, tous les accès mémoire se font viamovdes instructions combinées avec des crochets :

Exemple

; Opérations de base de lecture/écriture en mémoire

section .data
    var1 db 0x55                ; 1 byte
    var2 dw 0x1234              ; 2 bytes
    var3 dd 0x12345678          ; 4 bytes

section .text
    global _start

_start:
    ; Lecture de mémoire de différentes tailles
    mov al, [var1]              ; Lire 1 byte : al = 0x55
    mov ax, [var2]              ; Lire 2 bytes : ax = 0x1234
    mov eax, [var3]             ; Lire 4 bytes : eax = 0x12345678

    ; Écriture en mémoire de différentes tailles
    mov byte [var1], 0xAA       ; Écrire 1 byte
    mov word [var2], 0xABCD     ; Écrire 2 bytes
    mov dword [var3], 0xDEADBEEF ; Écrire 4 bytes

    ; Accès à la mémoire via des pointeurs
    mov ebx, var3               ; ebx pointe vers var3
    mov eax, [ebx]              ; Lire indirectement la valeur de var3
    add dword [ebx], 1          ; var3 = var3 + 1

    mov eax, 1
    mov ebx, 0
    int 0x80

Consignes de sécurité pour les opérations mémoire

Il n'y a pas de « filet de sécurité » lorsqu'on manipule la mémoire en assembleur ; gardez les règles suivantes à l'esprit :

RègleDescriptionConséquence d'une violation
Pas d'accès hors limitesNe pas lire/écrire plus que la taille de la variable/du tamponCorruption de données, erreur de segmentation
Ne pas lire la mémoire non initialiséeLes valeurs dans la section .bss sont indéfiniesRésultats imprévisibles
Ne pas lire/écrire à des adresses invalidesS'assurer que le pointeur pointe vers une mémoire valideErreur de segmentation
Ne pas écrire dans une mémoire en lecture seuleLa section .text et la zone des constantes ne sont pas modifiablesErreur de segmentation
Alignement d'adresseLes accès mot utilisent des adresses paires ; les accès double mot utilisent des multiples de 4Dégradation des performances ou erreur de bus

Exemple

; Exemples d'erreurs mémoire courantes (avertissement : provoquera des plantages !)

section .data
    small_buf db 0, 0, 0, 0     ; Seulement 4 octets

section .text
    global _start

_start:
    ; Opération dangereuse 1 : écriture hors limites
    ; mov dword [small_buf + 3], 0x12345678
    ; Cela écrase 3 octets de données après small_buf !

    ; Opération dangereuse 2 : écriture dans la section de code (lecture seule)
    ; mov dword [_start], 0x90 ; Tenter de modifier le code provoquera une erreur de segmentation

    ; Opération dangereuse 3 : pointeur nul / adresse invalide
    ; mov eax,
    ; mov

    ; Approche sûre : toujours opérer dans la plage de mémoire allouée
    mov dword [small_buf], 'runo'  ; Correct : opérer dans une plage de 4 octets

    mov eax, 1
    mov ebx, 0
    int 0x80

Gestion de la mémoire de pile

La pile est une autre zone de mémoire importante, gérée par les instructions PUSH/POP et le registre ESP :

Exemple

; Exemple complet d'opérations sur la pile

section .data
    original_esp dd 0

section .text
    global _start

_start:
    mov [original_esp], esp      ; Sauvegarder le pointeur de pile d'origine

    ; PUSH : empiler sur la pile (ESP -= 4, écrire les données)
    push dword 100               ; Empiler 100
    ; ESP = ESP - 4, [ESP] = 100

    push dword 200               ; Empiler 200
    push dword 300               ; Empiler 300

    ; Disposition de la pile :
    ; ESP+0 : 300
    ; ESP+4 : 200
    ; ESP+8 : 100

    ; POP : dépiler (lire les données, ESP += 4)
    pop eax                      ; eax = 300, ESP += 4
    pop ebx                      ; ebx = 200, ESP += 4
    pop ecx                      ; ecx = 100, ESP += 4

    ; Allouer de l'espace local sur la pile
    sub esp, 256                 ; Allouer 256 octets sur la pile
    ; Maintenant, les 256 octets de ESP à ESP+255 peuvent être utilisés en toute sécurité
    mov dword [esp], 42          ; Écrire 42 dans l'espace local
    add esp, 256                 ; Libérer l'espace local

    ; Assurez-vous que ESP retourne à sa position d'origine !
    mov eax, 1
    mov ebx, 0
    int 0x80

La règle la plus importante pour les opérations de pile :PUSH et POP doivent être appariés. Avant qu'une fonction ne retourne, vous devez vous assurer qu'ESP revient à la bonne position ; sinon RET dépilera la mauvaise adresse de retour, ce qui fera planter le programme ou exécutera du code arbitraire. C'est l'un des bugs les plus fatals en programmation assembleur.


Comparaison des méthodes de gestion de la mémoire

MéthodeFlexibilitéComplexitéScénario d'application
Variables globales (.data/.bss)Faible (fixe au moment de la compilation)Le plus basDonnées de taille fixe
Allocation sur la pile (sub esp)Moyen (dynamique dans la fonction)FaibleVariables locales de fonction
brk/sbrkMoyen (ne peut que grandir/rétrécir)MoyenneRégion de mémoire contiguë
mmapÉlevé (allocation/libération arbitraire)ÉlevéeLorsqu'une gestion flexible de la mémoire est nécessaire
Autres extensions