Dans cet article vous allez comprendre comment est segmentée et sectionnée la mémoire lorsqu’un programme est chargé ! Chouette non ? Cela va vous permettre de mieux comprendre l’organisation de votre programme en mémoire, et quoi sert à quoi.
Les segments sont simplement des regroupements de sections qui ont leur propres flags, base address etc. Pour les obtenir les segments d’un programme vous pouvez utiliser la commande
$ readelf -l ./program
Donc voici les différents segments (ici pour la liste complète):
Les sections sont les composantes des segments. Nous commencerons des adresses basses (0x0) vers les adresse hautes (0xf).
Adresses | Segment | Section | Description |
---|---|---|---|
0x0 | LOAD #2 | .plt | Procedure Linkage Table, elle est utilisée pour appeler les fonctions/procédures externes, comme printf@plt |
.text | Contient les opcodes (Operations Codes) de l'architecture CPU (Control Process Unit), ce qui signifie que cette section contient le code source du programme | ||
LOAD #3 | .rodata | Constantes en RO | |
LOAD #4 | .ctors/init_array | Liste des constructeurs (pointeurs sur fonction), c'est une liste de pointeurs sur fonction qui sont appelés avant l'appel de main (elle se définit avec l'attribut pré-déclaratif __attribute__((constructor)) ) |
|
.dtors/fini_array | Liste des destructeurs (pointeurs sur fonction), c'est une liste de pointeurs sur fonction qui sont appelés après l'appel de main, comme atexit(3) (elle se définit avec l'attribut pré-déclaratif __attribute__((destructor)) ) |
||
.got | Global Offset Table, elle est utilisée pour pointer sur des variables globales importées de bibliothèques (comme EXIT_SUCCESS de stdlib.h) | ||
.got.plt | C'est la GOT de la PLT, donc elle agit sur les fonctions/procédures externes, comme printf@libc | ||
.data | Comme .rodata mais en RW (Read-Write) | ||
.bss | Comme .data mais ici c'est juste un chunk de 0 pour les variables globales non initialisées | ||
N/A | [heap] | Sur le DS (Data Segment), c'est l'espace mémoire réservé aux allocations dynamiques, elle grandit vers les adresses hautes, c'est les adresses retournées par malloc(3),brk(2),etc | |
N/A | [libs] | C'est après la heap que les bibliothèques sont chargées (libc, OpenCL, GTK, ...), elles ont leur propres segments/sections, ce sont des programmes à part entière | |
GNU_STACK | [stack] | Allocation statique, elle grandit vers les adresses basses, elle contient les variables locales, les retours d'adresses et les sauvegardes des registres de stack comme RBP (EBP en Intel x86) | |
0xf | N/A | [kernel] | Evidemment on ne charge pas le kernel lors du lancement d'un programme quelconque, mais il faut savoir que le kernel est par défaut chargé dans les adresses les plus hautes |
Imaginons que nous soyons dans .text, que EIP (le pointeur sur l’instruction courante) pointe vers une instruction call sur printf@plt, nous savons qu’en résultat, printf(3) sera appelée. En réalité voici ce qu’il se passe: printf@plt appelle printf@got.plt qui appelle printf@libc.
Dans le cas où l’on compile avec le flag -static, voici un exemple d’appel sur printf:
La suite ici !