Intel x86 Assembly Language & Microarchitecture
Modes réel vs protégé
Recherche…
Mode réel
Lorsque Intel a conçu le x86 d'origine, le 8086 (et le dérivé 8088), ils incluaient la segmentation pour permettre au processeur 16 bits d'accéder à plus de 16 bits d'adresse. Pour ce faire, les adresses à 16 bits ont été associées à un registre de segments à 16 bits, dont elles ont défini quatre: segment de code ( CS ), segment de données ( DS ), segment supplémentaire ( ES ) et segment de pile ( SS ). .
La plupart des instructions impliquaient le registre de segment à utiliser: les instructions étaient extraites du segment de code, PUSH et POP impliquaient le segment de pile, et les références de données simples impliquaient le segment de données - bien que cela puisse être remplacé pour accéder à la mémoire des autres segments.
L'implémentation était simple: pour chaque accès mémoire, le CPU prendrait le registre de segments implicite (ou explicite), le décalerait de quatre places à gauche, puis ajouterait l'adresse indiquée:
+-------------------+---------+
Segment | 16-bit value | 0 0 0 0 |
+-------------------+---------+
PLUS
+---------+-------------------+
Address | 0 0 0 0 | 16-bit value |
+---------+-------------------+
EQUALS
+-----------------------------+
Result | 20-bit memory address |
+-----------------------------+
Cela a permis diverses techniques:
- Autoriser le code, les données et la pile à tous être accessibles (
CS,DSetSSavaient tous la même valeur); - Garder le code, les données et la pile complètement séparés les uns des autres (
CS,DSetSStous les 4K (ou plus) sont séparés les uns des autres - rappelez-vous qu'il est multiplié par 16, soit 64K).
Il a également permis des chevauchements bizarres et toutes sortes de choses étranges!
Lorsque le 80286 a été inventé, il supportait ce mode hérité (maintenant appelé "Mode réel"), mais a ajouté un nouveau mode appelé "Mode protégé" (qv).
Les choses importantes à noter est que, en mode réel:
- Toute adresse mémoire était accessible, simplement en mettant la valeur correcte dans un registre de segment et en accédant à l'adresse 16 bits;
- L'étendue de la "protection" consistait à permettre au programmeur de séparer différentes zones de la mémoire à des fins différentes et de rendre plus difficile l' écriture accidentelle dans les mauvaises données, tout en permettant de le faire.
En d'autres termes ... pas très protégé du tout!
Mode protégé
introduction
Lorsque le 80286 a été inventé, il supportait l’ancienne segmentation 8086 (désormais appelée «mode réel»), et a ajouté un nouveau mode appelé «mode protégé». Ce mode a été dans tous les processeurs x86 depuis, mais a été amélioré avec diverses améliorations telles que l'adressage 32 et 64 bits.
Conception
En mode protégé, la simple "Ajouter une adresse à la valeur du registre de segments décalés" a été complètement supprimée. Ils ont conservé les registres de segments, mais au lieu de les utiliser pour calculer une adresse, ils les ont utilisés pour indexer dans une table (en fait une parmi deux ...) qui définissait le segment auquel accéder. Cette définition ne décrivait pas seulement où se trouvait le segment (en utilisant Base et Limit), mais aussi quel type de segment c'était (Code, Data, Stack ou même System) et quels types de programmes pouvaient y accéder (noyau OS, programme normal) , Pilote de périphérique, etc.).
Registre de segment
Chaque registre de segment 16 bits prend la forme suivante:
+------------+-----+------+
| Desc Index | G/L | Priv |
+------------+-----+------+
Desc Index = 13-bit index into a Descriptor Table (described below)
G/L = 1-bit flag for which Descriptor Table to Index: Global or Local
Priv = 2-bit field defining the Privilege level for access
Global / Local
Le bit Global / Local définit si l'accès se trouve dans une table globale de descripteurs (appelée sans surprise la table de descripteur globale ou GDT) ou une table de descripteur local (LDT). L'idée du LDT était que chaque programme puisse avoir sa propre table de descripteurs - le système d'exploitation définirait un ensemble global de segments et chaque programme aurait son propre ensemble de segments de code local, de données et de pile. Le système d'exploitation gérerait la mémoire entre les différentes tables de descripteurs.
Table de descripteur
Chaque table de descripteurs (globale ou locale) était un tableau de 64 Ko composé de 8 192 descripteurs: chacun un enregistrement de 8 octets définissant plusieurs aspects du segment qu'il décrivait. Les champs Index des descripteurs des registres de segments permettaient 8 192 descripteurs: pas de coïncidence!
Descripteur
Un descripteur contenait les informations suivantes - notez que le format du descripteur a changé à mesure que de nouveaux processeurs étaient publiés, mais que le même type d'informations était conservé dans chacun d'eux:
- Base
Ceci a défini l'adresse de départ du segment de mémoire. - Limite
Cela a défini la taille du segment de mémoire - en quelque sorte. Ils ont dû prendre une décision: une taille de0x0000signifierait-elle une taille de0, donc pas accessible? Ou taille maximale?
Au lieu de cela, ils ont choisi une troisième option: le champ Limite était le dernier emplacement accessible dans le segment. Cela signifiait qu'un segment unique pouvait être défini; ou une taille maximale pour la taille de l'adresse. - Type
Il y avait plusieurs types de segments: le code, les données et la pile traditionnels (voir ci-dessous), mais d'autres segments de système ont également été définis:- Les segments de la table de descripteurs locaux définissaient le nombre de descripteurs locaux accessibles.
- Les segments d'état de tâche pourraient être utilisés pour la commutation de contexte gérée par le matériel;
- Des «portes d'appel» contrôlées qui pourraient permettre aux programmes d'appeler le système d'exploitation, mais uniquement par le biais de points d'entrée soigneusement gérés.
- Les attributs
Certains attributs du segment ont également été conservés, le cas échéant:- Lecture seule vs lecture-écriture;
- Si le segment était actuellement présent ou non - permettant une gestion de la mémoire à la demande;
- Quel niveau de code (système d'exploitation vs pilote vs programme) pourrait accéder à ce segment.
La vraie protection enfin!
Si le système d’exploitation conservait les tables de descripteurs dans des segments auxquels il n’était pas possible d’accéder par de simples programmes, il pourrait alors gérer avec précision les segments définis et la mémoire attribuée et accessible à chacun. Un programme pourrait fabriquer n'importe quelle valeur de registre de segment qui lui plaisait - mais s'il avait l' audace de le charger réellement dans un registre de segment ! ..., le matériel CPU reconnaîtrait que la valeur de descripteur proposée enfreignait un grand nombre de règles. au lieu de compléter la demande, une exception au système d'exploitation serait levée pour lui permettre de gérer le programme errant.
Cette exception était généralement n ° 13, l'exception de protection générale - rendue mondialement célèbre par Microsoft Windows ... (quelqu'un pense-t-il qu'un ingénieur Intel était superstitieux?)
les erreurs
Les types d’erreurs pouvant survenir sont les suivants:
Si l'indice de descripteur proposé était plus grand que la taille de la table;
Si le descripteur proposé était un descripteur de système plutôt qu'un code, une donnée ou une pile;
Si le descripteur proposé était plus privilégié que le programme demandeur;
Si le descripteur proposé était marqué comme étant illisible (tel qu'un segment de code), mais qu'il a été tenté de le lire plutôt que de l'exécuter;
Si le descripteur proposé était marqué comme non présent.
Notez que le dernier peut ne pas être un problème fatal pour le programme: le système d'exploitation pourrait noter le drapeau, rétablir le segment, le marquer comme étant présent, puis laisser l'instruction d'instruction se poursuivre avec succès.
Ou, peut-être le descripteur a-t-il été chargé avec succès dans un registre de segments, mais un futur accès à celui-ci enfreint l'une des nombreuses règles suivantes:
- Le registre de segment a été chargé avec l'
0x0000descripteur0x0000pour le GDT. Cela a été réservé par le matériel commeNULL; - Si le descripteur chargé était marqué comme étant en lecture seule, une écriture y a été tentée.
- Si une partie de l'accès (1, 2, 4 octets ou plus) était en dehors de la limite du segment.
Passer en mode protégé
Passer en mode protégé est facile: il vous suffit de définir un seul bit dans un registre de contrôle. Mais rester en mode protégé, sans que le CPU ne lève les bras et se réinitialise en ne sachant pas quoi faire, demande beaucoup de préparation.
En bref, les étapes requises sont les suivantes:
Une zone de mémoire pour la table de descripteur globale doit être configurée pour définir un minimum de trois descripteurs:
- Le zeroeth, descripteur
NULL; - Un autre descripteur pour un segment de code;
- Un autre descripteur pour un segment de données.
Cela peut être utilisé à la fois pour Data et Stack.
- Le zeroeth, descripteur
Le registre
GDTR(Global Descriptor Table Register) doit être initialisé pour indiquer cette zone de mémoire définie.GDT_Ptr dw SIZE GDT dd OFFSET GDT ... lgdt [GDT_Ptr]Le bit
PMdansCR0doit être défini:mov eax, cr0 ; Get CR0 into register or eax, 0x01 ; Set the Protected Mode bit mov cr0, eax ; We're now in Protected Mode!Les registres de segment doivent être chargés depuis le GDT pour supprimer les valeurs actuelles du mode réel:
jmp 0x0008:NowInPM ; This is a FAR Jump. 0x0008 is the Code Descriptor NowInPM: mov ax, 0x0010 ; This is the Data Descriptor mov ds, ax mov es, ax mov ss, ax mov sp, 0x0000 ; Top of stack!
Notez que c'est le strict minimum, juste pour obtenir le CPU en mode protégé. Pour que l'ensemble du système soit prêt, plusieurs étapes peuvent être nécessaires. Par exemple:
- Les zones de mémoire supérieures doivent être activées - désactivation de la porte
A20; - Les interruptions doivent définitivement être désactivées, mais les différents gestionnaires de pannes peuvent peut-être être configurés avant d'entrer en mode protégé, afin de permettre des erreurs au début du traitement.
L'auteur original de cette section a écrit un didacticiel complet sur l'entrée du mode protégé et son utilisation.
Mode irréel
Le mode irréel exploite deux faits sur la façon dont les processeurs Intel et AMD chargent et enregistrent les informations pour décrire un segment.
Le processeur met en cache les informations de descripteur extraites lors d'un déplacement dans un registre de sélecteur en mode protégé.
Ces informations sont stockées dans une partie architecturale invisible du registre de sélection elles-mêmes.En mode réel, les registres de sélecteur sont appelés registres de segment mais, à part cela, ils désignent le même ensemble de registres et ont donc une partie invisible. Ces parties sont remplies avec des valeurs fixes, mais pour la base qui est dérivée de la valeur qui vient d'être chargée.
Dans une telle vue, le mode réel n'est qu'un cas particulier du mode protégé: où les informations d'un segment, telles que la base et la limite, sont extraites sans GDT / LDT mais toujours lues dans la partie cachée du registre de segment.
En commutant en mode protégé et en créant un GDT, il est possible de créer un segment avec les attributs souhaités, par exemple une base de 0 et une limite de 4 Go.
Grâce à un chargement successif d'un registre de sélecteur, de tels attributs sont mis en cache, il est alors possible de revenir en mode réel et d'avoir un registre de segment par lequel accède à l'espace d'adresse complet de 32 bits.
BITS 16
jmp 7c0h:__START__
__START__:
push cs
pop ds
push ds
pop ss
xor sp, sp
lgdt [GDT] ;Set the GDTR register
cli ;We don't have an IDT set, we can't handle interrupts
;Entering protected mode
mov eax, cr0
or ax, 01h ;Set bit PE (bit 0) of CR0
mov cr0, eax ;Apply
;We are now in Protected mode
mov bx, 08h ;Selector to use, RPL = 0, Table = 0 (GDT), Index = 1
mov fs, bx ;Load FS with descriptor 1 info
mov gs, bx ;Load GS with descriptor 1 info
;Exit protected mode
and ax, 0fffeh ;Clear bit PE (bit0) of CR0
mov cr0, eax ;Apply
sti
;Back to real mode
;Do nothing
cli
hlt
GDT:
;First entry, number 0
;Null descriptor
;Used to store a m16&32 object that tells the GDT start and size
dw 0fh ;Size in byte -1 of the GDT (2 descriptors = 16 bytes)
dd GDT + 7c00h ;Linear address of GDT start (24 bits)
dw 00h ;Pad
dd 0000ffffh ;Base[15:00] = 0, Limit[15:00] = 0ffffh
dd 00cf9200h ;Base[31:24] = 0, G = 1, B = 1, Limit[19:16] = 0fh,
;P = 1, DPL = 0, E = 0, W = 1, A = 0, Base[23:16] = 00h
TIMES 510-($-$$) db 00h
dw 0aa55h
Considérations
- Dès qu'un registre de segment est rechargé, même avec la même valeur, le processeur recharge les attributs masqués en fonction du mode en cours. C'est pourquoi le code ci-dessus utilise
fsetgspour contenir les segments "étendus": de tels registres sont moins susceptibles d'être utilisés / enregistrés / restaurés par les différents services 16 bits. - L'instruction
lgdtne charge pas un pointeur éloigné sur le GDT, mais charge une adresse linéaire de 24 bits (pouvant être remplacée par 32 bits). Ce n'est pas une adresse proche , c'est l' adresse physique (car la pagination doit être désactivée). C'est la raison deGDT+7c00h. - Le programme ci-dessus est un bootloader (pour MBR, il n’a pas de BPB) qui définit
cs/ds/sstp 7c00h et lance le compteur d’emplacement à partir de 0. Un octet au décalage X dans le fichier est au décalage X dans le segment 7c00h et à l'adresse linéaire 7c00h + X. - Les interruptions doivent être désactivées car un IDT n'est pas défini pour le court trajet aller-retour en mode protégé.
- Le code utilise un hack pour enregistrer 6 octets de code. La structure chargée par
lgdtest enregistrée dans le ... GDT lui-même, dans le descripteur null (le premier descripteur).
Pour une description des descripteurs GDT, voir le chapitre 3.4.3 du manuel Intel Volume 3A .