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 , DS et SS avaient tous la même valeur);
  • Garder le code, les données et la pile complètement séparés les uns des autres ( CS , DS et SS tous 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 de 0x0000 signifierait-elle une taille de 0 , 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' 0x0000 descripteur 0x0000 pour le GDT. Cela a été réservé par le matériel comme NULL ;
  • 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:

    1. Le zeroeth, descripteur NULL ;
    2. Un autre descripteur pour un segment de code;
    3. Un autre descripteur pour un segment de données.

      Cela peut être utilisé à la fois pour Data et Stack.

  • 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 PM dans CR0 doit ê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.

  1. 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.

  2. 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 fs et gs pour 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 lgdt ne 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 de GDT+7c00h .
  • Le programme ci-dessus est un bootloader (pour MBR, il n’a pas de BPB) qui définit cs / ds / ss tp 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 lgdt est 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 .



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow