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'appel | Description | Valeur de retour |
|---|---|---|
ebx = 0 | Obtenir l'adresse actuelle du program break | EAX = adresse de break actuelle |
ebx = 新地址 | Définir une nouvelle adresse de break | EAX = nouvelle adresse de break (renvoie l'adresse d'origine en cas d'échec) |
Exemple
; 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
; 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
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ègle | Description | Conséquence d'une violation |
|---|---|---|
| Pas d'accès hors limites | Ne pas lire/écrire plus que la taille de la variable/du tampon | Corruption de données, erreur de segmentation |
| Ne pas lire la mémoire non initialisée | Les valeurs dans la section .bss sont indéfinies | Résultats imprévisibles |
| Ne pas lire/écrire à des adresses invalides | S'assurer que le pointeur pointe vers une mémoire valide | Erreur de segmentation |
| Ne pas écrire dans une mémoire en lecture seule | La section .text et la zone des constantes ne sont pas modifiables | Erreur de segmentation |
| Alignement d'adresse | Les accès mot utilisent des adresses paires ; les accès double mot utilisent des multiples de 4 | Dégradation des performances ou erreur de bus |
Exemple
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
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éthode | Flexibilité | Complexité | Scénario d'application |
|---|---|---|---|
| Variables globales (.data/.bss) | Faible (fixe au moment de la compilation) | Le plus bas | Données de taille fixe |
| Allocation sur la pile (sub esp) | Moyen (dynamique dans la fonction) | Faible | Variables locales de fonction |
| brk/sbrk | Moyen (ne peut que grandir/rétrécir) | Moyenne | Région de mémoire contiguë |
| mmap | Élevé (allocation/libération arbitraire) | Élevée | Lorsqu'une gestion flexible de la mémoire est nécessaire |