Suche…


Realer Modus

Bei der Entwicklung des ursprünglichen x86, des 8086 (und des 8088-Derivats) von Intel wurde die Segmentierung verwendet, damit der 16-Bit-Prozessor auf Adressen mit mehr als 16 Bit zugreifen kann. Sie taten dies, indem sie die 16-Bit-Adressen relativ zu einem bestimmten 16-Bit-Segmentregister machten, von dem sie vier definierten: Codesegment ( CS ), Datensegment ( DS ), Extra-Segment ( ES ) und Stapelsegment ( SS ). .

Die meisten Anweisungen implizierten, welches Segmentregister verwendet werden sollte: Anweisungen wurden aus dem Codesegment übernommen, PUSH und POP implizierten das Stapelsegment, und einfache Datenreferenzen implizierten das Datensegment - obwohl dies für den Zugriff auf den Speicher in einem der anderen Segmente überschrieben werden konnte.

Die Implementierung war einfach: Für jeden Speicherzugriff würde die CPU das implizierte (oder explizite) Segmentregister übernehmen, um vier Stellen nach links verschieben und dann die angegebene Adresse hinzufügen:

        +-------------------+---------+
Segment | 16-bit value      | 0 0 0 0 |
        +-------------------+---------+
                   PLUS
        +---------+-------------------+
Address | 0 0 0 0 | 16-bit value      |
        +---------+-------------------+
                  EQUALS
        +-----------------------------+
Result  |  20-bit memory address      |
        +-----------------------------+

Dies ermöglichte verschiedene Techniken:

  • Zulassen, dass Code, Daten und Stack gegenseitig zugänglich sind ( CS , DS und SS alle denselben Wert);
  • Wenn Code, Daten und Stack vollständig voneinander getrennt sind ( CS , DS und SS alle 4K (oder mehr) voneinander getrennt - denken Sie daran, dass sie mit 16 multipliziert werden, also 64K).

Es erlaubte auch bizarre Überlappungen und allerlei seltsame Dinge!

Als der 80286 erfunden wurde, unterstützte er diesen Legacy-Modus (jetzt "Real Mode"), fügte jedoch einen neuen Modus hinzu, der als "Protected Mode" (qv) bezeichnet wird.

Das Wichtigste, was zu beachten ist, ist das im Real-Modus:

  • Auf jede Speicheradresse konnte zugegriffen werden, indem einfach der korrekte Wert in ein Segmentregister eingegeben und auf die 16-Bit-Adresse zugegriffen wurde.
  • Das Ausmaß des "Schutzes" sollte es dem Programmierer ermöglichen, verschiedene Speicherbereiche für verschiedene Zwecke zu trennen, und es schwieriger zu machen, versehentlich auf die falschen Daten zu schreiben - und dies dennoch zu ermöglichen.

Mit anderen Worten ... überhaupt nicht sehr geschützt!

Sicherheitsmodus

Einführung

Als der 80286 erfunden wurde, unterstützte er die alte 8086-Segmentierung (jetzt "Real Mode") und fügte einen neuen Modus hinzu, der als "Protected Mode" bezeichnet wird. Dieser Modus wurde seitdem in jedem x86-Prozessor verwendet, jedoch mit verschiedenen Verbesserungen, wie z. B. 32- und 64-Bit-Adressierung, verbessert.

Design

Im geschützten Modus wurde auf das einfache "Hinzufügen der Adresse zum Registerwert des verschobenen Segmentes" vollständig verzichtet. Sie behielten die Segmentregister, aber anstatt sie zur Berechnung einer Adresse zu verwenden, verwendeten sie sie zum Indexieren in eine Tabelle (eigentlich eine von zwei ...), die das Segment definiert, auf das zugegriffen werden soll. In dieser Definition wurde nicht nur beschrieben, wo sich das Segment im Speicher befand (mithilfe von Basis und Limit), sondern auch, um welche Art von Segment es sich handelt (Code, Daten, Stack oder sogar System) und auf welche Arten von Programmen (OS-Kernel, normales Programm) , Gerätetreiber usw.).

Segmentregister

Jedes 16-Bit-Segmentregister hat folgende Form:

+------------+-----+------+
| 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 / lokal

Das Global / Local-Bit definiert, ob der Zugriff auf eine Globale Tabelle von Deskriptoren (nicht überraschend als globale Deskriptortabelle oder GDT bezeichnet) oder eine lokale Deskriptortabelle (LDT) erfolgt. Die Idee für die LDT war, dass jedes Programm eine eigene Deskriptortabelle haben könnte - das Betriebssystem würde einen globalen Satz von Segmenten definieren, und jedes Programm hätte seinen eigenen Satz von lokalem Code, Daten- und Stapelsegmenten. Das Betriebssystem würde den Speicher zwischen den verschiedenen Deskriptortabellen verwalten.

Deskriptortabelle

Jede Deskriptortabelle (global oder lokal) bestand aus einem 64-KByte-Array mit 8.192 Deskriptoren: jeweils einem 8-Byte-Datensatz, der mehrere Aspekte des von ihm beschriebenen Segments definierte. Die Deskriptor-Indexfelder der Segmentregister erlaubten 8.192 Deskriptoren: kein Zufall!

Deskriptor

Ein Deskriptor enthielt die folgenden Informationen. Beachten Sie, dass sich das Format des Deskriptors mit der Veröffentlichung neuer Prozessoren geändert hat. In beiden Fällen wurde jedoch dieselbe Art von Information beibehalten:

  • Base
    Dies definiert die Startadresse des Speichersegments.
  • Grenze
    Dies definierte die Größe des Speichersegments - irgendwie. Sie mussten eine Entscheidung treffen: Würde eine Größe von 0x0000 eine Größe von 0 bedeuten, also nicht erreichbar? Oder maximale größe?
    Stattdessen wählten sie eine dritte Option: Das Limit-Feld war der letzte adressierbare Ort innerhalb des Segments. Das bedeutete, dass ein One-Bye-Segment definiert werden konnte; oder eine maximale Größe für die Adressgröße.
  • Art
    Es gab verschiedene Arten von Segmenten: den herkömmlichen Code, Daten und Stack (siehe unten), aber auch andere Systemsegmente wurden definiert:
    • Lokale Deskriptortabellensegmente definiert, auf wie viele lokale Deskriptoren zugegriffen werden kann;
    • Taskstatus-Segmente können für den durch Hardware verwalteten Kontextwechsel verwendet werden.
    • Kontrollierte "Call Gates", mit denen Programme das Betriebssystem aufrufen können - jedoch nur durch sorgfältig verwaltete Einstiegspunkte.
  • Attribute
    Bestimmte Attribute des Segments wurden gegebenenfalls ebenfalls gepflegt:
    • Schreibgeschützt vs. Lese- / Schreibzugriff;
    • Ob das Segment aktuell vorhanden ist oder nicht - ermöglicht eine bedarfsgerechte Speicherverwaltung;
    • Welche Codeebene (Betriebssystem vs. Treiber vs. Programm) kann auf dieses Segment zugreifen.

Endlich wahrer Schutz!

Wenn das Betriebssystem die Deskriptortabellen in Segmenten aufbewahrte, auf die nur Programme zugreifen konnten, konnte es genau festlegen, welche Segmente definiert wurden und welcher Speicher für jeden zugewiesen und zugänglich war. Ein Programm könnte jeden beliebigen Segment-Register-Wert herstellen - aber wenn es die Kühnheit hätte , es tatsächlich in ein Segment-Register zu laden ! ... würde die CPU-Hardware erkennen, dass der vorgeschlagene Descriptor-Wert eine der vielen Regeln brach, und Anstatt die Anforderung abzuschließen, wird eine Ausnahme an das Betriebssystem ausgegeben, damit das fehlerhafte Programm verarbeitet werden kann.

Diese Ausnahme war normalerweise die Nummer 13, die allgemeine Schutzausnahme, die durch Microsoft Windows weltbekannt wurde ... (Wer glaubt, dass ein Intel-Ingenieur Aberglaube war?)

Fehler

Zu den möglichen Fehlern gehören:

  • Wenn der vorgeschlagene Deskriptorindex größer als die Größe der Tabelle war;

  • Wenn der vorgeschlagene Deskriptor ein Systemdeskriptor und nicht Code, Daten oder Stack war.

  • Wenn der vorgeschlagene Deskriptor privilegierter war als das anfordernde Programm;

  • Wenn der vorgeschlagene Deskriptor als nicht lesbar markiert wurde (z. B. ein Codesegment), wurde jedoch versucht, gelesen und nicht ausgeführt zu werden.

  • Wenn der vorgeschlagene Deskriptor als Nicht vorhanden markiert wurde.

    Beachten Sie, dass das letzte nicht ein schwerwiegendes Problem für das Programm sein kann: Das Betriebssystem könnte das Flag merken, das Segment wiederherstellen, es als jetzt Vorhanden markieren und dann den fehlerhaften Befehl erfolgreich ablaufen lassen.

Oder der Deskriptor wurde möglicherweise erfolgreich in ein Segmentregister geladen, aber dann löste ein zukünftiger Zugriff damit eine der folgenden Regeln:

  • Das Segmentregister wurde mit dem 0x0000 Descriptor Index für die GDT geladen. Dies wurde von der Hardware als NULL reserviert;
  • Wenn der geladene Deskriptor als schreibgeschützt markiert wurde, wurde jedoch ein Schreibvorgang ausgeführt.
  • Wenn sich ein Teil des Zugriffs (1, 2, 4 oder mehr Bytes) außerhalb der Begrenzung des Segments befand.

In den geschützten Modus wechseln

Das Umschalten in den geschützten Modus ist einfach: Sie müssen nur ein einzelnes Bit in einem Steuerregister setzen. Um im geschützten Modus zu bleiben , ohne dass die CPU ihre Hände hochwirft und sich selbst zurückstellt, weil sie nicht weiß, was als nächstes zu tun ist, ist viel Vorbereitung erforderlich.

Kurz gesagt, die folgenden Schritte sind erforderlich:

  • Ein Speicherbereich für die globale Deskriptortabelle muss eingerichtet werden, um mindestens drei Deskriptoren zu definieren:

    1. Der nullte, NULL Deskriptor;
    2. Ein weiterer Deskriptor für ein Codesegment;
    3. Ein weiterer Deskriptor für ein Datensegment.

      Dies kann sowohl für Daten als auch für Stapel verwendet werden.

  • Das Global Descriptor Table Register ( GDTR ) muss initialisiert werden, um auf diesen definierten Speicherbereich zu zeigen.

     GDT_Ptr    dw      SIZE GDT
                dd      OFFSET GDT
    
                ...
    
                lgdt    [GDT_Ptr]
    
  • Das PM Bit in CR0 muss gesetzt sein:

         mov   eax, cr0      ; Get CR0 into register
         or    eax, 0x01     ; Set the Protected Mode bit
         mov   cr0, eax      ; We're now in Protected Mode!
    
  • Die Segmentregister müssen aus dem GDT geladen werden, um die aktuellen Werte des Real-Modus zu entfernen:

         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!
    

Beachten Sie, dass dies das absolute Minimum ist, um die CPU in den geschützten Modus zu bringen. Um das gesamte System tatsächlich vorzubereiten, sind möglicherweise viele weitere Schritte erforderlich. Zum Beispiel:

  • Möglicherweise müssen die oberen Speicherbereiche aktiviert werden. Schalten Sie das A20 Gate aus.
  • Die Interrupts sollten auf jeden Fall deaktiviert werden. Möglicherweise können jedoch die verschiedenen Fehlerhandler eingerichtet werden, bevor der geschützte Modus aktiviert wird, um Fehler in der Verarbeitung frühzeitig zu berücksichtigen.

Der ursprüngliche Autor dieses Abschnitts hat ein komplettes Lernprogramm geschrieben, in dem Sie den geschützten Modus aufrufen und damit arbeiten.

Unreal-Modus

Der Unreal-Modus nutzt zwei Fakten, wie sowohl Intel- als auch AMD-Prozessoren die Informationen laden und speichern, um ein Segment zu beschreiben.

  1. Der Prozessor speichert die während einer Verschiebung abgerufenen Deskriptorinformationen in einem Auswahlregister im geschützten Modus.
    Diese Informationen werden in einem architektonisch unsichtbaren Teil des Auswahlregisters selbst gespeichert.

  2. Im Real-Modus werden die Selektorregister als Segmentregister bezeichnet, ansonsten bezeichnen sie jedoch dieselbe Gruppe von Registern und haben daher auch einen unsichtbaren Teil. Diese Teile werden mit festen Werten gefüllt, jedoch für die Basis, die sich aus dem gerade geladenen Wert ergibt.

In einer solchen Ansicht ist der Realmodus nur ein Sonderfall des Protected Mode: Hier werden die Informationen eines Segments wie Basis und Limit ohne GDT / LDT abgerufen, aber immer noch aus dem verborgenen Teil des Segmentregisters gelesen.


Durch Umschalten in den geschützten Modus und Herstellen eines GDT ist es möglich, ein Segment mit den gewünschten Attributen zu erstellen, z. B. einer Basis von 0 und einem Limit von 4 GB.
Durch ein sukzessives Laden eines Auswahlregisters werden solche Attribute zwischengespeichert, es ist dann möglich, in den Realmodus zurückzuschalten und ein Segmentregister zu haben, durch das der gesamte 32-Bit-Adressraum zugänglich ist.

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 

Überlegungen

  • Sobald ein Segmentregister selbst mit demselben Wert erneut geladen wird, lädt der Prozessor die verborgenen Attribute gemäß dem aktuellen Modus neu. Aus diesem Grund verwendet der obige Code fs und gs , um die "erweiterten" Segmente zu speichern: Solche Register werden weniger wahrscheinlich von den verschiedenen 16-Bit-Diensten verwendet / gespeichert / wiederhergestellt.
  • Der lgdt keinen lgdt auf den GDT, sondern lädt eine lineare Adresse mit 24 Bit (kann auf 32 Bit überschrieben werden). Dies ist keine nahe Adresse , sondern die physische Adresse (da Paging deaktiviert werden muss). Das ist der Grund von GDT+7c00h .
  • Das obige Programm ist ein Bootloader (für MBR hat es keine BPB), der cs / ds / ss tp 7c00h setzt und den Positionszähler von 0 aus startet. Ein Byte am Offset X in der Datei ist also am Offset X im Segment 7c00h und an der linearen Adresse 7c00h + X.
  • Die Interrupts müssen deaktiviert werden, da im geschützten Modus keine IDT für die kurze Rundfahrt eingestellt ist.
  • Der Code verwendet einen Hack, um 6 Byte Code zu speichern. Die von lgdt geladene lgdt wird in der ... GDT selbst im lgdt (dem ersten Deskriptor) gespeichert.

Eine Beschreibung der GDT-Deskriptoren finden Sie in Kapitel 3.4.3 von Intel Manual Volume 3A .



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow