Dans le chapitre précédent, nous avons vu qu'un programme travaille principalement avec des adresses virtuelles et que le processeur doit les traduire en adresses physiques.
En mode x86-64, cette traduction repose sur une hiérarchie de tables de pages.
Le chemin classique pour une page de 4 Kio est :
Code: Select all
Adresse virtuelle
|
v
CR3
|
v
PML4
|
v
PDPT
|
v
Page Directory
|
v
Page Table
|
v
Page physique + offset
1. Le registre CR3
Le processeur doit connaître le point de départ de la hiérarchie des tables de pages.
Sur x86-64, le registre de contrôle CR3 joue ce rôle.
Dans le modèle présenté ici, CR3 permet de retrouver physiquement la table de plus haut niveau :
Code: Select all
PML4 = Page Map Level 4
Code: Select all
CR3
|
| adresse physique
v
PML4
Cette entrée mène au niveau suivant.
Le même principe est répété jusqu'à obtenir la page physique contenant les données recherchées.
CR3 est donc un élément essentiel du contexte mémoire d'un processus. Lorsqu'un autre espace d'adressage doit être utilisé, le système doit faire en sorte que le processeur utilise les tables correspondantes.
2. Décomposition d'une adresse virtuelle x86-64
Dans le modèle classique à quatre niveaux présenté dans ce chapitre, tous les 64 bits ne servent pas directement d'index pour la traduction.
Une adresse virtuelle est découpée ainsi :
Code: Select all
63 48 47 39 38 30 29 21 20 12 11 0
+----------------------------+-----------+-----------+-----------+-----------+------------+
| extension / bits supérieurs| PML4 | PDPT | PD | PT | offset |
+----------------------------+-----------+-----------+-----------+-----------+------------+
9 bits 9 bits 9 bits 9 bits 12 bits
Code: Select all
PML4 index = bits 47-39
PDPT index = bits 38-30
PD index = bits 29-21
PT index = bits 20-12
offset = bits 11-0
Parce qu'une page classique contient :
Code: Select all
4096 octets = 2^12 octets
Pourquoi 9 bits pour chaque index ?
Code: Select all
2^9 = 512
3. Adresses canoniques
Dans le modèle x86-64 à 48 bits utilisé par le chapitre, les bits supérieurs d'une adresse ne sont pas simplement des bits arbitraires.
Les adresses utilisées doivent respecter la forme dite canonique.
Avec un espace virtuel sur 48 bits, les bits 63 à 48 correspondent à une extension du bit 47.
On obtient donc schématiquement deux grandes zones d'adresses canoniques séparées par une plage qui ne représente pas des adresses valides dans ce mode.
Pour comprendre la traduction décrite dans ce chapitre, les bits réellement utilisés pour choisir les quatre niveaux et l'offset sont donc :
Code: Select all
47 ... 0
La PML4 constitue le premier niveau de la traduction dans cette organisation à quatre niveaux.
CR3 permet d'en retrouver la base physique.
Les bits :
Code: Select all
47 - 39
Cet index comporte 9 bits :
Code: Select all
0 à 511
Code: Select all
CR3 = adresse physique de la PML4
index PML4 = 1
Conceptuellement :
Code: Select all
PML4
+---------------------+
| entrée 0 |
+---------------------+
| entrée 1 ---------- | ----> PDPT
+---------------------+
| entrée 2 |
+---------------------+
| ... |
+---------------------+
| entrée 511 |
+---------------------+
Ces bits peuvent notamment indiquer si l'entrée est valide/présente et quelles opérations sont autorisées.
5. Page Directory Pointer Table — PDPT
Une fois la bonne entrée de PML4 obtenue, le processeur atteint une :
Code: Select all
Page Directory Pointer Table
Les bits :
Code: Select all
38 - 30
Encore une fois :
Code: Select all
9 bits
2^9 = 512 possibilités
Le chemin devient :
Code: Select all
CR3
|
v
PML4[index 47-39]
|
v
PDPT[index 38-30]
Le niveau suivant est le Page Directory, souvent abrégé PD.
Les bits utilisés sont :
Code: Select all
29 - 21
L'entrée choisie permet, dans le cas classique d'une page de 4 Kio, de retrouver la Page Table suivante.
Code: Select all
CR3
|
v
PML4
|
v
PDPT
|
v
Page Directory
|
v
Page Table
7. Page Table
La Page Table constitue le dernier niveau pour une traduction classique vers une page de 4 Kio.
Les bits :
Code: Select all
20 - 12
Cette entrée permet de déterminer la page physique correspondante.
Il reste ensuite :
Code: Select all
bits 11 - 0
Le résultat peut être représenté ainsi :
Code: Select all
Adresse virtuelle
|
+--> PML4 index
+--> PDPT index
+--> PD index
+--> PT index
+--> offset
|
v
adresse dans la page
8. Traduction complète d'une adresse virtuelle
Prenons une adresse virtuelle quelconque :
Code: Select all
VA = 0x0000123456789012
Code: Select all
PML4 = (VA >> 39) & 0x1FF
PDPT = (VA >> 30) & 0x1FF
PD = (VA >> 21) & 0x1FF
PT = (VA >> 12) & 0x1FF
OFF = VA & 0xFFF
Code: Select all
0x1FF
Le masque :
Code: Select all
0xFFF
Le processeur effectue conceptuellement :
Code: Select all
1. Lire CR3
2. Trouver la PML4
3. Utiliser les bits 47-39
4. Trouver la PDPT
5. Utiliser les bits 38-30
6. Trouver le Page Directory
7. Utiliser les bits 29-21
8. Trouver la Page Table
9. Utiliser les bits 20-12
10. Obtenir la page physique
11. Ajouter les bits 11-0 comme offset
Code: Select all
9 + 9 + 9 + 9 + 12 = 48 bits
Pour comprendre concrètement le mécanisme, on peut écrire une petite logique équivalente en C/C++ :
Code: Select all
uint64_t address = 0x0000123456789012;
uint64_t pml4Index = (address >> 39) & 0x1FF;
uint64_t pdptIndex = (address >> 30) & 0x1FF;
uint64_t pdIndex = (address >> 21) & 0x1FF;
uint64_t ptIndex = (address >> 12) & 0x1FF;
uint64_t offset = address & 0xFFF;
Il ne fait qu'extraire les cinq champs de l'adresse.
Pour effectuer la traduction réelle, il faudrait également connaître et lire les tables de pages correspondantes.
10. Pourquoi plusieurs niveaux ?
On pourrait imaginer une gigantesque table contenant directement une entrée pour chaque page virtuelle.
Le problème serait sa taille.
Avec des pages de 4 Kio, un immense espace virtuel pourrait nécessiter un nombre gigantesque d'entrées, même si le processus n'utilise réellement qu'une petite partie de cet espace.
Une structure hiérarchique permet de ne créer les tables intermédiaires que lorsqu'elles sont nécessaires.
On obtient donc un compromis entre :
- taille des structures de traduction ;
- flexibilité ;
- gestion d'un espace virtuel immense ;
- coût de la traduction.
La page classique étudiée jusqu'ici possède une taille de :
Code: Select all
4 Kio
Un cas important est la page de :
Code: Select all
2 Mio = 2^21 octets
Code: Select all
21 bits
La traduction peut donc s'arrêter au niveau du Page Directory.
Schématiquement :
Code: Select all
Page 4 Kio :
CR3
-> PML4
-> PDPT
-> PD
-> PT
-> page 4 Kio
Page 2 Mio :
CR3
-> PML4
-> PDPT
-> PD
-> page 2 Mio
Code: Select all
20 - 0
Les grandes pages permettent notamment de couvrir davantage de mémoire avec moins d'entrées de traduction.
Cela peut réduire la pression exercée sur certaines structures de traduction, notamment le TLB.
En contrepartie, une page beaucoup plus grande est moins fine : réserver une grande page pour une petite quantité de données peut gaspiller davantage de mémoire.
12. TLB — Translation Lookaside Buffer
Le parcours :
Code: Select all
PML4 -> PDPT -> PD -> PT
Effectuer systématiquement tout ce parcours avant chaque accès aux données serait coûteux.
Les processeurs utilisent donc un cache spécialisé appelé :
TLB — Translation Lookaside Buffer
Le TLB mémorise des traductions récentes entre pages virtuelles et pages physiques.
Conceptuellement :
Code: Select all
CPU veut accéder à une adresse virtuelle
|
v
Cherche TLB
/ \
trouvé absent
| |
v v
TLB hit TLB miss
| |
| page walk
| |
+-------> traduction
La traduction recherchée est déjà présente dans le TLB.
Le processeur peut réutiliser rapidement l'information.
TLB miss
La traduction n'est pas présente dans le TLB.
Le processeur doit effectuer un page walk, c'est-à-dire parcourir les structures de pagination nécessaires pour retrouver la traduction.
La nouvelle traduction peut ensuite être mise en cache dans le TLB.
Le TLB possède évidemment une capacité limitée. Il ne peut pas conserver les traductions de toutes les pages virtuelles existantes.
13. Intérêt des grandes pages pour le TLB
Une entrée TLB correspondant à une page de 4 Kio couvre :
Code: Select all
4096 octets
Ainsi, pour certaines charges de travail manipulant de grandes zones mémoire, les grandes pages peuvent permettre à un nombre limité d'entrées TLB de couvrir davantage de mémoire.
C'est l'un des intérêts des large pages.
Cela ne signifie cependant pas que les grandes pages sont systématiquement meilleures : leur utilisation dépend du programme et de ses besoins.
14. Intérêt pour le reverse engineering
Dans un débogueur user mode comme x64dbg, on manipule principalement des adresses virtuelles.
Par exemple :
Code: Select all
00007FF6........
Pour la plupart des analyses user mode, il n'est pas nécessaire de refaire manuellement le parcours complet des tables de pages.
Cependant, connaître le mécanisme permet de comprendre ce qui se trouve derrière une adresse affichée par le débogueur.
Cela devient encore plus intéressant lorsqu'on travaille avec :
- WinDbg en kernel ;
- les structures mémoire du noyau ;
- les Page Table Entries ;
- la mémoire physique ;
- les protections de pages ;
- l'analyse bas niveau d'un processus.
En développement kernel, la distinction entre adresse virtuelle et adresse physique devient particulièrement importante.
Il faut retenir qu'une adresse comme :
Code: Select all
0xFFFF....
C'est une adresse virtuelle qui doit être interprétée dans le contexte de l'espace d'adressage et des tables de pages correspondantes.
La chaîne fondamentale à connaître est donc :
Code: Select all
CR3
|
v
PML4
|
v
PDPT
|
v
PD
|
v
PT
|
v
Page physique
16. Points essentiels à retenir
- x86-64 utilise une pagination hiérarchique ;
- CR3 permet de retrouver la racine des tables de pages ;
- dans le modèle à quatre niveaux, cette racine est la PML4 ;
- la traduction classique utilise PML4, PDPT, Page Directory et Page Table ;
- chaque index classique fait 9 bits ;
- 9 bits permettent de sélectionner 512 entrées ;
- une page de 4 Kio utilise un offset de 12 bits ;
- le découpage classique est 9 + 9 + 9 + 9 + 12 = 48 bits ;
- une entrée de table contient une adresse ainsi que des bits de contrôle ;
- les grandes pages permettent de terminer la traduction plus tôt ;
- une page de 2 Mio utilise un offset de 21 bits ;
- le TLB met en cache les traductions récentes ;
- un TLB miss peut entraîner un page walk ;
- ces notions sont particulièrement utiles en kernel, debugging et reverse engineering.
Exercice 1 — Taille d'une page
Une page possède une taille de :
Code: Select all
4096 octets
Exercice 2 — Nombre d'entrées
Un index de table de pages possède 9 bits.
Combien d'entrées différentes peut-il sélectionner ?
Exercice 3 — Extraction de l'offset
Pour l'adresse :
Code: Select all
0x0000123456789012
Exercice 4 — Décomposition
Décompose l'adresse virtuelle suivante :
Code: Select all
0x0000123456789012
Code: Select all
PML4 index
PDPT index
PD index
PT index
offset
Code: Select all
PML4 = (VA >> 39) & 0x1FF
PDPT = (VA >> 30) & 0x1FF
PD = (VA >> 21) & 0x1FF
PT = (VA >> 12) & 0x1FF
OFF = VA & 0xFFF
Une page possède maintenant une taille de :
Code: Select all
2 Mio = 2^21 octets
À quel niveau la traduction peut-elle s'arrêter dans le cas étudié dans ce chapitre ?
Exercice 6 — TLB
Explique la différence entre :
Code: Select all
TLB hit
TLB miss
Exercice 7 — Reverse/kernel
Dans un débogueur, tu observes :
Code: Select all
Adresse virtuelle -> données du processus
Décris ensuite le chemin général permettant au processeur d'obtenir la page physique :
Code: Select all
CR3 -> ? -> ? -> ? -> ? -> page physique
